from __future__ import annotations

import contextlib
import os
import tempfile
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from unittest import mock

ROOT = Path(__file__).resolve().parents[1]
FAKE_CODEX = ROOT / "tests" / "helpers" / "fake_codex.py"
FAKE_SWITCHYARD = ROOT / "tests" / "helpers" / "fake_switchyard.py"


def root_thread_binding(thread_id: str | None) -> dict[str, object]:
    """Return one internally consistent root-thread lineage for state-fixture tests."""

    if thread_id is None:
        return {
            "root_thread_id": None,
            "root_thread_generation": 0,
            "root_thread_lineage": [],
            "root_thread_transition": None,
        }
    return {
        "root_thread_id": thread_id,
        "root_thread_generation": 1,
        "root_thread_lineage": [
            {
                "generation": 1,
                "thread_id": thread_id,
                "codex_session_id": thread_id,
                "adopted_at": "2000-01-01T00:00:00+00:00",
                "reason": "test_fixture",
            }
        ],
        "root_thread_transition": None,
    }


def create_access_lab_session(
    *, cwd: Path, profile: str | Path = "access-efficient-escalation-lab"
) -> dict[str, Any]:
    """Create the local-first fixture without depending on a host llama.cpp process."""

    import mmo_runtime

    real_availability = mmo_runtime.route_availability

    def available_local_route(snapshot: Mapping[str, Any]) -> dict[str, dict[str, Any]]:
        availability = real_availability(snapshot)
        availability["llama_cpp_local_openai_chat"] = {
            "available": True,
            "selected_credential_env": None,
            "reason": None,
        }
        return availability

    with mock.patch.object(
        mmo_runtime,
        "route_availability",
        side_effect=available_local_route,
    ):
        return mmo_runtime.create_session(profile=profile, cwd=cwd)


class RuntimeSandbox:
    def __init__(self) -> None:
        self.temp = tempfile.TemporaryDirectory(prefix="codex-mmo-test-")
        self.root = Path(self.temp.name)
        self.config = self.root / "config with spaces"
        self.state = self.root / "state with spaces"
        self.runtime = self.root / "runtime with spaces"
        self.workspace = self.root / "workspace with spaces"
        self.base_codex_home = self.root / "base codex home"
        self.old_env: dict[str, str | None] = {}

    def __enter__(self) -> RuntimeSandbox:
        for path in (
            self.config,
            self.state,
            self.runtime,
            self.workspace,
            self.base_codex_home,
        ):
            path.mkdir(parents=True, exist_ok=True)
        self.old_env = {
            key: os.environ.get(key)
            for key in (
                "MMO_INSTALL_ROOT",
                "MMO_CONFIG_ROOT",
                "MMO_STATE_ROOT",
                "XDG_RUNTIME_DIR",
                "MMO_CODEX_BIN",
                "ZAI_CODING_API_KEY",
                "OPENCODE_API_KEY",
                "OPENROUTER_API_KEY",
                "OPENAI_API_KEY",
                "FIRECRAWL_API_KEY",
                "IDA_MCP_TOKEN",
                "CODEX_HOME",
            )
        }
        os.environ.update(
            {
                "MMO_INSTALL_ROOT": str(ROOT),
                "MMO_CONFIG_ROOT": str(self.config),
                "MMO_STATE_ROOT": str(self.state),
                "XDG_RUNTIME_DIR": str(self.runtime),
                "MMO_CODEX_BIN": str(FAKE_CODEX),
                "ZAI_CODING_API_KEY": "fake-zai-coding",
                "OPENCODE_API_KEY": "fake-opencode",
                "OPENROUTER_API_KEY": "fake-openrouter",
                "OPENAI_API_KEY": "fake-openai",
            }
        )
        (self.config / "profiles.d").mkdir()
        (self.config / "catalog.d").mkdir()
        (self.config / "tool-mcp.d").mkdir()
        settings = (ROOT / "config" / "settings.toml").read_text(encoding="utf-8")
        settings = settings.replace(
            'base_codex_home = "~/.codex"', f"base_codex_home = {self.base_codex_home.as_posix()!r}"
        )
        # TOML accepts JSON-style double quoted paths, not Python repr single quotes.
        settings = settings.replace(
            f"base_codex_home = '{self.base_codex_home.as_posix()}'",
            f'base_codex_home = "{self.base_codex_home.as_posix()}"',
        )
        settings = settings.replace(
            'switchyard_bin = "switchyard-server"',
            f'switchyard_bin = "{FAKE_SWITCHYARD.as_posix()}"',
        )
        settings = settings.replace(
            "gateway_start_timeout_seconds = 15", "gateway_start_timeout_seconds = 5"
        )
        settings = settings.replace(
            "gateway_idle_timeout_seconds = 3600", "gateway_idle_timeout_seconds = 1"
        )
        (self.config / "settings.toml").write_text(settings, encoding="utf-8")
        (self.config / "credentials.env").write_text(
            "ZAI_CODING_API_KEY=fake-zai-coding\nOPENCODE_API_KEY=fake-opencode\n"
            "OPENROUTER_API_KEY=fake-openrouter\nOPENAI_API_KEY=fake-openai\n",
            encoding="utf-8",
        )
        (self.base_codex_home / "auth.json").write_text('{"fake":true}\n', encoding="utf-8")
        return self

    def __exit__(self, *_exc: object) -> None:
        with contextlib.suppress(Exception):
            from mmo_runtime import cancel_session, iter_sessions

            for session in iter_sessions(strict=False):
                with contextlib.suppress(Exception):
                    cancel_session(str(session["session_id"]))
        with contextlib.suppress(Exception):
            from mmo_gateway import list_gateways, stop_gateway

            for gateway in list_gateways():
                stop_gateway(str(gateway["snapshot_hash"]))
        for key, value in self.old_env.items():
            if value is None:
                os.environ.pop(key, None)
            else:
                os.environ[key] = value
        self.temp.cleanup()

    def init_git(self) -> None:
        import subprocess

        subprocess.run(["git", "init", "-q", str(self.workspace)], check=True)
        subprocess.run(
            ["git", "-C", str(self.workspace), "config", "user.email", "test@example.invalid"],
            check=True,
        )
        subprocess.run(
            ["git", "-C", str(self.workspace), "config", "user.name", "MMO Test"], check=True
        )
        (self.workspace / "README.md").write_text("fixture\n", encoding="utf-8")
        subprocess.run(["git", "-C", str(self.workspace), "add", "."], check=True)
        subprocess.run(["git", "-C", str(self.workspace), "commit", "-qm", "fixture"], check=True)
