"""Per-run MCP config provisioning (bridge first class, 2026-06-11).

Directives carry engine-neutral MCP server defs from the backend connector
registry: {name, transport, command?, args?, env?, url?, headers?}. This module
translates them into each engine's config format as EPHEMERAL per-run files —
written 0600 (they carry secrets), removed in the runner's finally so secrets
never persist on disk between runs.

Per the Phase 0 matrix (test_logs/2026-06-11-bridge-first-class/):
  - claude_code: a JSON file passed via --mcp-config + --strict-mcp-config.
  - codex: [mcp_servers.*] tables in <CODEX_HOME>/config.toml, with CODEX_HOME
    relocated to <workspace>/.atashi/codex (which also closes the codex half of
    the OBS-10 isolation gap; host auth.json symlinked in, mirroring the claude
    credentials handling).
  - antigravity: NO per-run mechanism (global host config only) — never
    provisioned here.
"""

from __future__ import annotations

import json
import logging
import os
import uuid
from pathlib import Path

logger = logging.getLogger(__name__)


def _to_claude_server(server: dict) -> dict:
    if server.get("transport") == "http":
        out: dict = {"type": "http", "url": server.get("url", "")}
        if server.get("headers"):
            out["headers"] = server["headers"]
        return out
    out = {"command": server.get("command", "")}
    if server.get("args"):
        out["args"] = server["args"]
    if server.get("env"):
        out["env"] = server["env"]
    return out


def provision_claude_mcp(config_home: str, mcp_servers: list[dict]) -> str:
    """Write an ephemeral mcp-run-<uuid>.json into the run's config home and
    return its path. Caller removes it in finally."""
    path = Path(config_home) / f"mcp-run-{uuid.uuid4().hex}.json"
    payload = {
        "mcpServers": {
            s["name"]: _to_claude_server(s) for s in mcp_servers if s.get("name")
        }
    }
    path.write_text(json.dumps(payload, indent=2))
    path.chmod(0o600)
    return str(path)


def ensure_codex_home(workspace_dir: str) -> str:
    """Provision <workspace>/.atashi/codex as a relocated CODEX_HOME.

    Mirrors isolation.ensure_claude_config_home: idempotent, and the host's
    auth.json is symlinked in so login state (when the host has any) survives
    the relocation — a fresh empty CODEX_HOME is logged out.
    """
    home = Path(workspace_dir) / ".atashi" / "codex"
    home.mkdir(parents=True, exist_ok=True)

    auth = home / "auth.json"
    host_auth = Path(os.environ.get("CODEX_HOME", "").strip() or (Path.home() / ".codex")) / "auth.json"
    if not auth.exists() and not auth.is_symlink() and host_auth.exists():
        try:
            auth.symlink_to(host_auth)
        except OSError as exc:
            logger.warning("codex isolation: could not link auth.json: %s", exc)
    return str(home)


def _toml_str(value: str) -> str:
    # json string escaping is valid TOML basic-string escaping.
    return json.dumps(str(value))


def _to_codex_table(server: dict) -> str:
    lines = [f"[mcp_servers.{server['name']}]"]
    if server.get("transport") == "http":
        lines.append(f"url = {_toml_str(server.get('url', ''))}")
        headers = server.get("headers") or {}
        if headers:
            inline = ", ".join(f"{_toml_str(k)} = {_toml_str(v)}" for k, v in headers.items())
            lines.append(f"http_headers = {{ {inline} }}")
    else:
        lines.append(f"command = {_toml_str(server.get('command', ''))}")
        args = server.get("args") or []
        if args:
            lines.append("args = [" + ", ".join(_toml_str(a) for a in args) + "]")
        env = server.get("env") or {}
        if env:
            inline = ", ".join(f"{_toml_str(k)} = {_toml_str(v)}" for k, v in env.items())
            lines.append(f"env = {{ {inline} }}")
    return "\n".join(lines)


def provision_codex_mcp(codex_home: str, mcp_servers: list[dict]) -> str:
    """Write <codex_home>/config.toml containing only the run's MCP servers
    and return its path. The .atashi codex home is bridge-owned, so the whole
    file is managed: caller removes it in finally (secrets must not persist)."""
    path = Path(codex_home) / "config.toml"
    tables = [_to_codex_table(s) for s in mcp_servers if s.get("name")]
    path.write_text("\n\n".join(tables) + "\n")
    path.chmod(0o600)
    return str(path)


def cleanup_paths(paths: list[str]) -> None:
    """Best-effort removal of ephemeral secret-bearing files."""
    for p in paths:
        try:
            os.remove(p)
        except FileNotFoundError:
            pass
        except OSError as exc:
            logger.warning("mcp cleanup: could not remove %s: %s", p, exc)
