"""MCP config persistence — read/write ~/.icarus/mcp.json.

Pure functions for managing the MCP server configuration file.
The config format is compatible with Claude Code's mcp.json:

    {
        "mcpServers": {
            "server-name": {
                "command": "python",
                "args": ["server.py"],
                "env": {"KEY": "val"}
            }
        },
        "_disabled": {
            "disabled-server": { ... }
        }
    }

Servers in ``mcpServers`` are enabled; servers in ``_disabled`` are
tracked but not connected.  The ``_disabled`` convention is specific
to Icarus — Claude Code does not use it.
"""

from __future__ import annotations

import contextlib
import json
import logging
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class ServerEntry:
    """A single MCP server from the config, with its enabled state."""

    name: str
    enabled: bool
    config: dict[str, Any]

    @property
    def transport_type(self) -> str:
        """Return ``"http"`` if the server has a ``url``, else ``"stdio"``."""
        return "http" if "url" in self.config else "stdio"


def read_mcp_config(path: Path) -> dict[str, Any]:
    """Read mcp.json, returning the full config dict.

    Returns the empty config structure if the file is missing or invalid.
    """
    if not path.is_file():
        return {"mcpServers": {}, "_disabled": {}}

    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError) as e:
        logger.warning("MCP config: failed to read %s: %s", path, e)
        return {"mcpServers": {}, "_disabled": {}}

    # Ensure both keys exist
    data.setdefault("mcpServers", {})
    data.setdefault("_disabled", {})
    return data


def write_mcp_config(path: Path, config: dict[str, Any]) -> None:
    """Write mcp.json atomically (write to .tmp, rename)."""
    path.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(config, f, indent=2)
            f.write("\n")
        Path(tmp_path).replace(path)
    except BaseException:
        with contextlib.suppress(OSError):
            Path(tmp_path).unlink()
        raise


def list_servers(config: dict[str, Any]) -> list[ServerEntry]:
    """List all servers from both ``mcpServers`` and ``_disabled`` sections."""
    entries = []
    for name, cfg in config.get("mcpServers", {}).items():
        entries.append(ServerEntry(name=name, enabled=True, config=cfg))
    for name, cfg in config.get("_disabled", {}).items():
        entries.append(ServerEntry(name=name, enabled=False, config=cfg))
    return entries


def add_server(
    config: dict[str, Any], name: str, server_def: dict[str, Any],
) -> dict[str, Any]:
    """Add a server to ``mcpServers``.  Raises if name already exists."""
    if name in config.get("mcpServers", {}) or name in config.get("_disabled", {}):
        raise ValueError(f"Server '{name}' already exists")
    config.setdefault("mcpServers", {})[name] = server_def
    return config


def update_server(
    config: dict[str, Any], name: str, server_def: dict[str, Any],
) -> dict[str, Any]:
    """Update a server in whichever section it lives."""
    if name in config.get("mcpServers", {}):
        config["mcpServers"][name] = server_def
    elif name in config.get("_disabled", {}):
        config["_disabled"][name] = server_def
    else:
        raise ValueError(f"Server '{name}' not found")
    return config


def remove_server(config: dict[str, Any], name: str) -> dict[str, Any]:
    """Remove a server from both sections."""
    removed = False
    if name in config.get("mcpServers", {}):
        del config["mcpServers"][name]
        removed = True
    if name in config.get("_disabled", {}):
        del config["_disabled"][name]
        removed = True
    if not removed:
        raise ValueError(f"Server '{name}' not found")
    return config


def toggle_server(config: dict[str, Any], name: str) -> dict[str, Any]:
    """Move a server between ``mcpServers`` and ``_disabled``."""
    if name in config.get("mcpServers", {}):
        server_def = config["mcpServers"].pop(name)
        config.setdefault("_disabled", {})[name] = server_def
    elif name in config.get("_disabled", {}):
        server_def = config["_disabled"].pop(name)
        config.setdefault("mcpServers", {})[name] = server_def
    else:
        raise ValueError(f"Server '{name}' not found")
    return config
