"""Bundled Codex host strategy."""
from __future__ import annotations

import os
import shutil
from collections.abc import Callable
from pathlib import Path

from okstra_ctl.adapters.accounting import CliArtifactUsageAccountingPort
from okstra_ctl.adapters.hosts.capability_adapter import (
    INTERACTION_FUNCTIONS,
    PENDING_HOST_PORT,
    CapabilityHostAdapter,
    no_automatic_claim,
    relay_interaction_port,
)
from okstra_ctl.domain.host import HostDescriptor
from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
from okstra_ctl.registry.provider_registry import ProviderRegistry


DESCRIPTOR = HostDescriptor(
    id="codex",
    aliases=("codex",),
    native_provider_id="codex",
    required_executables=("codex",),
    launch_mode="lead",
    install_targets=frozenset({"agents"}),
    agent_id="codex",
    agent_label="Codex CLI",
    role="Codex lead",
    dispatch_mode="render-only",
    session_accounting="artifact-only",
    has_claude_session=False,
    relay_contract=str(Path(__file__).with_name("relay.md").resolve()),
    initial_prompt_delivery_mode="eager-include",
)


def _okstra_home_write_checks(context) -> tuple[dict[str, object], ...]:
    """Whether THIS codex session can host the lead, not whether the run can go.

    okstra's cross-project state lives in `~/.okstra`, and every run takes the
    worktree registry lock there before the wizard starts. A codex session the
    Codex app started under its `workspace-write` default cannot write that
    path, and macOS seatbelt is inherited by children, so no okstra process
    launched from inside that session can either.

    That is a fact about the session, not about the machine. okstra already
    knows how to run codex with no sandbox — `LeadLaunchSpec.sandbox_waiver`
    puts `-s danger-full-access` on every lead it spawns itself. The waiver has
    nowhere to go in `current-session` mode because there is no process being
    launched. So the answer is to launch one: this check reports
    `lead-must-spawn`, and the run proceeds in `spawn-process` mode.

    It is deliberately not a blocker. Stopping here asked the user to widen
    their whole Codex sandbox for a boundary okstra does not want anywhere else
    — every provider CLI now runs without one.
    """
    if context.entry_mode != "current-session":
        return ({"id": "okstra-home-write", "status": "not-applicable"},)

    home_dir = Path(os.environ.get("OKSTRA_PROBE_HOME_DIR", str(Path.home())))
    registry_lock = home_dir / ".okstra" / "worktrees" / "registry.lock"
    try:
        registry_lock.parent.mkdir(parents=True, exist_ok=True)
        if not registry_lock.exists():
            registry_lock.touch()
        with registry_lock.open("r+"):
            pass
    except PermissionError:
        return ({
            "id": "okstra-home-write",
            "status": "lead-must-spawn",
            "action": "spawn-unsandboxed-codex-lead",
        },)
    return ({"id": "okstra-home-write", "status": "accepted"},)


def create_adapter(
    *,
    executable_finder: Callable[[str], str | None] = shutil.which,
    interaction_port=None,
    lead_session_port=PENDING_HOST_PORT,
    worker_dispatch_port=PENDING_HOST_PORT,
    usage_accounting_port=CliArtifactUsageAccountingPort(),
    provider_registry: ProviderRegistry | None = None,
    host_model_port=None,
) -> CapabilityHostAdapter:
    return CapabilityHostAdapter(
        DESCRIPTOR,
        executable_finder=executable_finder,
        interaction_port=(
            interaction_port
            if interaction_port is not None
            else relay_interaction_port(DESCRIPTOR.relay_contract)
        ),
        lead_session_port=lead_session_port,
        worker_dispatch_port=worker_dispatch_port,
        usage_accounting_port=usage_accounting_port,
        supported_functions=INTERACTION_FUNCTIONS,
        detector=no_automatic_claim,
        provider_registry=provider_registry,
        readiness_probe=_okstra_home_write_checks,
        host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
            host_runtime=DESCRIPTOR.id,
            native_provider=DESCRIPTOR.native_provider_id,
        ),
    )
