"""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 = {
    # ChatGPT-account hosts serve this model without per-token billing.
    "gpt-5.6-sol": ModelSpec("gpt-5.6-sol", "gpt-5.6-sol", "gpt-5.6-sol"),
    "gpt-5.6": ModelSpec("gpt-5.6", "gpt-5.6", "gpt-5.6", pricing=(5.00, 0.50, 30.0)),
    "gpt-5.5": ModelSpec("gpt-5.5", "gpt-5.5", "gpt-5.5", pricing=(5.00, 0.50, 30.0)),
    "gpt-5.4": ModelSpec("gpt-5.4", "gpt-5.4", "gpt-5.4", aliases=("gpt-5.3-codex",), pricing=(2.50, 0.25, 15.00)),
    "gpt-5.4-mini": ModelSpec("gpt-5.4-mini", "gpt-5.4-mini", "gpt-5.4-mini", pricing=(0.75, 0.075, 4.50)),
    "gpt-5.2": ModelSpec("gpt-5.2", "gpt-5.2", "gpt-5.2", pricing=(1.75, 0.175, 14.0)),
    "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,
            ),
        )


def normalize_execution(execution: str, role: str) -> str:
    """Compatibility entry point for direct adapter callers."""
    return model_discovery.normalize_execution_for_dispatch(
        provider="codex", execution=execution, role=role
    )


class CodexExecution:
    """codex CLI invocation.

    The sandbox stays `workspace-write` even when the approval gate is opened:
    removing the gate is not the same as removing the boundary. 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.

    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:
        argv = ["codex", "exec", "-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", "workspace-write"]
        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=True)


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,
    )
