"""Bundled Codex provider catalog."""
from okstra_ctl.domain.provider import (
    LeadLaunchSpec,
    ModelSpec,
    ProviderSpec,
    ServedModelAttestation,
    snapshot_execution_normalizations,
)
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 SplitText
import okstra_ctl.model_discovery as model_discovery


# 모델 식별자와 순서는 Codex의 `~/.codex/models_cache.json`을 따른다
# (2026-09-07 확인, 클라이언트 0.153.4): astra / sol / terra / luna.
# `gpt-5.6` is not a slug that catalog offers at all, so it is gone from here;
# its rate moved to `_LEGACY_CODEX_PRICING` so past runs still price.
CODEX = {
    # 비용은 계정이 구독이든 API 든 공개 API 단가(입력·캐시 입력·출력 USD/1M)로
    # 추정한다 — 리포트가 답하는 것은 "얼마나 썼는가" 이지 "청구서에 얼마가
    # 찍히는가" 가 아니다. 단가를 비우면 그 워커의 비용이 `--` 로 빠지고, 접두어
    # 폴백(`_LEGACY_CODEX_PRICING`)에 걸리면 terra·luna 가 sol 단가로 과대
    # 계상된다(실측 2026-09-08). 출처: OpenAI 표준 등급, 2026-09 기준
    # (morphllm.com/openai-api-pricing, cloudzero.com/blog/openai-pricing,
    # layer3labs.io/guides/gpt-6-astra-api-pricing).
    "gpt-6-astra": ModelSpec("gpt-6-astra", "gpt-6-astra", "gpt-6-astra", pricing=(10.0, 1.0, 50.0)),
    "gpt-5.6-sol": ModelSpec("gpt-5.6-sol", "gpt-5.6-sol", "gpt-5.6-sol", pricing=(5.0, 0.50, 30.0)),
    "gpt-5.6-terra": ModelSpec("gpt-5.6-terra", "gpt-5.6-terra", "gpt-5.6-terra", pricing=(2.0, 0.20, 12.0)),
    "gpt-5.6-luna": ModelSpec("gpt-5.6-luna", "gpt-5.6-luna", "gpt-5.6-luna", pricing=(0.20, 0.02, 1.20)),
    # picker 에서는 감춘다. 엔트리는 남긴다 — 과거 run 의 토큰 사용량을
    # 정산할 때 pricing 을 이 표에서 찾는다.
    "gpt-5.4-mini": ModelSpec("gpt-5.4-mini", "gpt-5.4-mini", "gpt-5.4-mini", pricing=(0.75, 0.075, 4.50), selectable=False),
    "codex-auto-review": ModelSpec("codex-auto-review", "codex-auto-review", "codex-auto-review", 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("codex/")
    return ServedModelAttestation(
        raw_model, f"codex/{model_id}", "exact", "provider-output"
    )


class CodexExecutionNormalizer:
    """Capture Codex model availability once, then normalize the catalog."""

    def snapshot(self, models, roles):
        available = model_discovery.codex_availability()
        return snapshot_execution_normalizations(
            models,
            roles,
            lambda execution, _role: model_discovery.normalize_codex_execution(
                execution=execution,
                available=available,
            ),
        )


class CodexExecution:
    """codex CLI invocation.

    No sandbox bounds this worker, which is what every other provider CLI
    already does: grok and kimi expose no sandbox flag at all, antigravity's
    `--sandbox` was measured not to block writes, and the claude CLI applies
    none. codex was the only worker running behind an enforced boundary, and a
    boundary carried by one provider out of five decided whether the same task
    passed by which CLI drew it, not by the code under test.

    The approval gate still has to be open. A non-interactive codex run under
    the default `on-request` policy blocks on the first approval it wants, and
    with no TTY to answer it ends the turn with exit 0 and no output — a success
    that produced nothing.

    `workspace-write` also closed every socket. Measured 2026-08-26 (macOS):
    under it `socket.bind(("127.0.0.1", 0))`, an outbound `connect`, and an
    `AF_UNIX` connect all raise `PermissionError [Errno 1]`; outside it all
    three succeed. A verifier that cannot open a port cannot run the suite, the
    HTTP contract test, or the DB test its own profile requires, so it returned
    FAIL for a reason that was never about the code. Dropping the sandbox
    removes that failure mode at its source, so no per-role network grant is
    needed any more.

    Working directory is the project root even when a stage worktree exists:
    this CLI attaches the worktree with `--add-dir` and runs from the root,
    unlike grok/kimi which run inside the worktree.
    """

    def build_command(self, request: WorkerExecRequest) -> ExecCommand:
        # 출력은 파이프로 수집하므로 auto 는 색상을 끈다. 화면 표시와 기록의
        # 색상 제거 여부는 공통 세션 기록기가 목적지에 맞춰 결정한다.
        argv = ["codex", "exec", "--color", "always", "-C", str(request.project_root)]
        for directory in request.policy.write_scope:
            if directory != request.project_root:
                argv += ["--add-dir", str(directory)]
        argv += ["--model", request.model, "--sandbox", "danger-full-access"]
        if request.policy.auto_approve:
            argv += ["-c", "approval_policy=never"]
        argv.append("-")
        return ExecCommand(
            argv=tuple(argv),
            stdin_text=request.prompt_text,
            cwd=request.project_root,
            presentation=SplitText(),
        )

    def policy_support(self) -> PolicySupport:
        return PolicySupport(
            can_auto_approve=True,
            can_bound_write_scope=False,
            note="this CLI no longer runs behind a sandbox boundary",
        )


def create_provider() -> ProviderSpec:
    return ProviderSpec(
        provider="codex",
        display_label="Codex",
        models=CODEX,
        default_models={
            role: "gpt-5.6-sol"
            for role in ("lead", "analyser", "critic", "designer", "planner", "executor", "verifier", "report-writer", "translator")
        },
        wrapper="okstra-codex-exec.sh",
        supported_roles=ALL_ROLE_TOKENS,
        execution_capabilities=frozenset(
            {
                "lead-session",
                "worker-artifact-io",
                "source-readonly",
                "extended-artifact-authoring",
                "project-mutation",
            }
        ),
        lead_launch=LeadLaunchSpec(
            executable="codex",
            model_flag="-m",
            # A sandboxed lead cannot reach cmux or write its worker CLI config.
            sandbox_waiver=("-s", "danger-full-access"),
            sandbox_waiver_note=(
                "codex will start without its filesystem and network sandbox, "
                "which okstra needs so the lead can reach cmux and start worker CLIs."
            ),
        ),
        exec_strategy=CodexExecution(),
        execution_normalizer=CodexExecutionNormalizer(),
        served_model_normalizer=normalise_served_model,
    )
