"""Bundled Claude Code host strategy."""
from __future__ import annotations

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

from okstra_ctl.adapters.accounting import ClaudeJsonlUsageAccountingPort
from okstra_ctl.adapters.hosts.capability_adapter import (
    INTERACTION_FUNCTIONS,
    PENDING_HOST_PORT,
    CapabilityHostAdapter,
    relay_interaction_port,
)
from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
from okstra_ctl.domain.provider import HostModelBinding
from okstra_ctl.ports.host_model import (
    HostModelBindingError,
    HostModelBindingRequest,
)
from okstra_ctl.registry.provider_registry import ProviderRegistry
from okstra_ctl.json_boundary import (
    JsonBoundaryError,
    external_claude_global_json_source,
    load_external_json,
)


DESCRIPTOR = HostDescriptor(
    id="claude-code",
    aliases=("claude",),
    native_provider_id="claude",
    required_executables=("claude",),
    launch_mode="lead",
    install_targets=frozenset({"agents", "claude"}),
    agent_id="claude",
    agent_label="Claude Code",
    role="Claude lead",
    dispatch_mode="team",
    session_accounting="claude-jsonl",
    has_claude_session=True,
    relay_contract=str(Path(__file__).with_name("relay.md").resolve()),
)


class ClaudeFamilyHostModelBindingPort:
    _FAMILIES = ("fable", "opus", "sonnet", "haiku")

    def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
        if request.runner == "cli-wrapper":
            return HostModelBinding(
                runner=request.runner,
                catalog_execution_value=request.model_execution_value,
                resolved_execution_value=request.execution_value,
                host_model_value=None,
                binding_fidelity="exact",
                worker_write_capability=request.worker_write_capability,
            )
        if (
            request.runner != "native-session"
            or request.host_runtime != DESCRIPTOR.id
            or request.provider != DESCRIPTOR.native_provider_id
        ):
            raise self._unsupported(request)
        segments = request.execution_value.lower().split("-")
        matches = [family for family in self._FAMILIES if family in segments]
        if len(matches) != 1:
            raise self._unsupported(request)
        if request.version_kind == "pinned":
            raise HostModelBindingError(
                "exact binding unavailable: "
                f"host={request.host_runtime!r}, model={request.model_ref!r}"
            )
        return HostModelBinding(
            runner=request.runner,
            catalog_execution_value=request.model_execution_value,
            resolved_execution_value=request.execution_value,
            host_model_value=matches[0],
            binding_fidelity="channel",
            worker_write_capability=request.worker_write_capability,
        )

    @staticmethod
    def _unsupported(request: HostModelBindingRequest) -> HostModelBindingError:
        return HostModelBindingError(
            "unsupported host model: "
            f"host={request.host_runtime!r}, provider={request.provider!r}, "
            f"model={request.execution_value!r}"
        )


def _detect_claude_skill_handoff(
    context: HostResolutionContext,
) -> HostClaim | None:
    if "claude-skill-handoff" not in context.signals:
        return None
    return HostClaim(
        DESCRIPTOR.id,
        100,
        "Claude Code skill handoff selected runtime 'claude-code'.",
    )


def _workspace_trust_checks(context) -> tuple[dict[str, object], ...]:
    project_root = Path(
        os.path.abspath(
            os.environ.get("OKSTRA_PROBE_PROJECT_ROOT", str(Path.cwd()))
        )
    )
    home_dir = Path(os.environ.get("OKSTRA_PROBE_HOME_DIR", str(Path.home())))
    if not (project_root / ".claude" / "settings.local.json").exists():
        return ({"id": "workspace-trust", "status": "not-applicable"},)
    try:
        # 외부 입력: Claude Code가 소유하는 사용자 workspace trust 상태다.
        state = load_external_json(
            external_claude_global_json_source(home_dir / ".claude.json", home_dir),
            artifact="Claude Code configuration",
        )
        projects = state.get("projects") if isinstance(state, dict) else None
        if not isinstance(projects, dict):
            return _workspace_trust_unavailable()
        project_key = str(project_root)
        if project_key not in projects:
            return _workspace_trust_required()
        project_state = projects[project_key]
        if not isinstance(project_state, dict):
            return _workspace_trust_unavailable()
        accepted = project_state.get("hasTrustDialogAccepted")
        if accepted is True:
            return ({"id": "workspace-trust", "status": "accepted"},)
        if accepted is False or accepted is None:
            return _workspace_trust_required()
        return _workspace_trust_unavailable()
    except JsonBoundaryError:
        return _workspace_trust_unavailable()


def _workspace_trust_required() -> tuple[dict[str, object], ...]:
    return ({
        "id": "workspace-trust",
        "status": "required",
        "action": "restart-and-trust",
    },)


def _workspace_trust_unavailable() -> tuple[dict[str, object], ...]:
    return ({
        "id": "workspace-trust",
        "status": "unavailable",
        "action": "open-project-and-trust",
    },)


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=ClaudeJsonlUsageAccountingPort(),
    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=_detect_claude_skill_handoff,
        provider_registry=provider_registry,
        readiness_probe=_workspace_trust_checks,
        host_model_port=host_model_port or ClaudeFamilyHostModelBindingPort(),
    )
