"""Bundled Kimi provider catalog."""
from typing import Any, Mapping
from okstra_ctl.domain.provider import (
    LeadLaunchSpec,
    ModelSpec,
    ProviderSpec,
    ServedModelAttestation,
)
from okstra_ctl.domain.role import ALL_ROLE_TOKENS
from okstra_ctl.domain.worker_exec import (
    ExecCommand,
    PolicySupport,
    WorkerExecRequest,
)
from okstra_ctl.domain.worker_presentation import JsonEvents
from okstra_ctl.domain.worker_stream import content_block_events


KIMI = {
    "kimi-k3": ModelSpec("kimi-k3", "Kimi K3", "kimi-k3", aliases=("kimi k3",), pricing=(3.00, 0.30, 15.00)),
    # picker 에서는 감춘다: kimi-k3 와 표시명·실체가 같아 목록에 두 줄로 뜬다.
    # 단가는 kimi-k3 와 같다 — 같은 모델의 다른 슬러그이고, `_match_pricing` 은
    # `kimi-k3` 키를 `k3` 관측값에 맞추지 못하므로 행마다 값을 둔다. k3-256k 는
    # Moonshot 이 별도 단가를 공개하지 않아 K3 단가를 쓴다(2026-09-08 확인).
    "k3": ModelSpec("k3", "Kimi K3", "k3", pricing=(3.00, 0.30, 15.00), selectable=False),
    # picker 에서는 감춘다(사유는 codex 의 gpt-5.4-mini 와 동일).
    "k3-256k": ModelSpec("k3-256k", "Kimi K3 256K", "k3-256k", aliases=("kimi k3 256k",), pricing=(3.00, 0.30, 15.00), selectable=False),
}


def normalise_served_model(raw_model: str | None) -> ServedModelAttestation:
    if not raw_model or not raw_model.strip():
        return ServedModelAttestation.unknown()
    model_id = raw_model.strip().lower().removeprefix("kimi/")
    return ServedModelAttestation(
        raw_model, f"kimi/{model_id}", "exact", "provider-output"
    )


def observe_served_model(event: Mapping[str, Any]) -> str | None:
    model = event.get("model")
    if isinstance(model, str) and model.strip():
        return model
    message = event.get("message")
    if isinstance(message, Mapping):
        model = message.get("model")
        return model if isinstance(model, str) and model.strip() else None
    return None

KIMI_LEAD_LAUNCH = LeadLaunchSpec(
    executable="kimi",
    model_flag="--model",
    resume_session_id_flag="--session",
)


class KimiExecution:
    """kimi CLI invocation.

    No flag names the working directory, so the process's own is the only
    control. Returning it in the command keeps that fact inside the contract
    instead of leaving the runner to infer it.
    """

    def build_command(self, request: WorkerExecRequest) -> ExecCommand:
        cwd = request.worktree_path or request.project_root
        return ExecCommand(
            argv=(
                "kimi",
                "-p",
                request.prompt_text,
                "-m",
                request.model,
                "--output-format",
                "stream-json",
            ),
            stdin_text=None,
            cwd=cwd,
            presentation=JsonEvents(
                normalise=content_block_events,
                observe=observe_served_model,
            ),
        )

    def policy_support(self) -> PolicySupport:
        return PolicySupport(
            can_auto_approve=False,
            can_bound_write_scope=False,
            note="this CLI exposes no approval or sandbox flag",
        )


def create_provider() -> ProviderSpec:
    return ProviderSpec(
        provider="kimi",
        display_label="Kimi",
        models=KIMI,
        default_models={
            "lead": "kimi-k3",
            "analyser": "kimi-k3",
            "critic": "kimi-k3",
            "designer": "kimi-k3",
            "planner": "kimi-k3",
            "executor": "kimi-k3",
            "verifier": "kimi-k3",
            "report-writer": "kimi-k3",
            "translator": "kimi-k3",
        },
        wrapper="okstra-kimi-exec.sh",
        supported_roles=ALL_ROLE_TOKENS,
        execution_capabilities=frozenset(
            {
                "lead-session",
                "worker-artifact-io",
                "source-readonly",
                "extended-artifact-authoring",
                "project-mutation",
            }
        ),
        lead_launch=KIMI_LEAD_LAUNCH,
        exec_strategy=KimiExecution(),
        served_model_normalizer=normalise_served_model,
    )
