"""
YAML configuration parser for the local bridge daemon.

Expected config format (~/.nexus/bridge.yml):

    servers:
      browser:
        transport: stdio
        command: npx
        args: ["@anthropic/browser-mcp"]
      filesystem:
        transport: stdio
        command: npx
        args: ["@anthropic/filesystem-mcp", "/home/user/documents"]
      custom-api:
        transport: http
        url: http://localhost:3001/mcp
"""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

import yaml


DEFAULT_CONFIG_PATH = os.path.expanduser("~/.nexus/bridge.yml")

VALID_TRANSPORTS = ("stdio", "http")

# The scaffold written by `nexus-bridge init`
EXAMPLE_CONFIG_YAML = """\
# Nexus Local Bridge — MCP Server Configuration
# Each entry under 'servers' is an MCP server running on your machine.
# The bridge exposes them to your agents in the cloud.
#
# Supported transports:
#   stdio  — runs a local command (e.g. npx, uvx, python)
#   http   — connects to a server already listening on localhost
#
# Uncomment the servers you want, or add your own.

servers:
  # ── Browser automation (Playwright MCP) ─────────────────────
  # browser:
  #   transport: stdio
  #   command: npx
  #   args: ["-y", "@playwright/mcp@latest"]
  #   description: "Control a local Chromium browser"

  # ── Local filesystem access ─────────────────────────────────
  # filesystem:
  #   transport: stdio
  #   command: npx
  #   args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/directory"]
  #   description: "Read/write files on this machine"

  # ── GitHub integration ──────────────────────────────────────
  # github:
  #   transport: stdio
  #   command: npx
  #   args: ["@anthropic/github-mcp"]
  #   env:
  #     GITHUB_TOKEN: "ghp_your_token_here"
  #   description: "Interact with GitHub repos"

  # ── Custom HTTP MCP server ──────────────────────────────────
  # my-api:
  #   transport: http
  #   url: http://localhost:3001/mcp
  #   description: "My custom MCP server"
"""


@dataclass
class LocalServerConfig:
    """Configuration for a single local MCP server."""
    name: str
    transport: str  # "stdio" or "http"
    command: Optional[str] = None
    args: list[str] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    url: Optional[str] = None
    headers: dict[str, str] = field(default_factory=dict)
    description: str = ""


@dataclass
class BridgeConfig:
    """Top-level bridge daemon configuration."""
    servers: list[LocalServerConfig] = field(default_factory=list)


def init_config(path: Optional[str] = None) -> Path:
    """
    Create a scaffold config file with commented-out examples.

    Returns the path to the created file.
    Raises ``FileExistsError`` if the file already exists.
    """
    config_path = Path(path or DEFAULT_CONFIG_PATH)
    if config_path.exists():
        raise FileExistsError(f"Config already exists: {config_path}")

    config_path.parent.mkdir(parents=True, exist_ok=True)
    config_path.write_text(EXAMPLE_CONFIG_YAML, encoding="utf-8")
    return config_path


def load_config(path: Optional[str] = None) -> BridgeConfig:
    """Load and validate bridge configuration from a YAML file."""
    config_path = Path(path or DEFAULT_CONFIG_PATH)

    if not config_path.exists():
        raise FileNotFoundError(
            f"Config file not found: {config_path}\n"
            f"Run 'nexus-bridge init' to create one with examples."
        )

    with open(config_path, "r") as f:
        raw = yaml.safe_load(f)

    if not isinstance(raw, dict):
        raise ValueError(f"Config file must be a YAML mapping, got {type(raw).__name__}.")

    raw_servers = raw.get("servers", {})
    if not isinstance(raw_servers, dict):
        raise ValueError("Config 'servers' must be a mapping of name -> server config.")

    servers: list[LocalServerConfig] = []
    for name, server_raw in raw_servers.items():
        if not isinstance(name, str) or not name.strip():
            raise ValueError("Server names must be non-empty strings.")
        if not isinstance(server_raw, dict):
            raise ValueError(f"Server '{name}' config must be a mapping.")

        transport = server_raw.get("transport", "stdio")
        if transport not in VALID_TRANSPORTS:
            raise ValueError(
                f"Server '{name}' transport must be one of {VALID_TRANSPORTS}, got '{transport}'."
            )

        if transport == "stdio":
            command = server_raw.get("command")
            if not isinstance(command, str) or not command.strip():
                raise ValueError(f"Server '{name}' with stdio transport requires 'command'.")
            args = server_raw.get("args", [])
            if not isinstance(args, list) or not all(isinstance(a, str) for a in args):
                raise ValueError(f"Server '{name}' args must be a list of strings.")
            env = server_raw.get("env", {})
            if not isinstance(env, dict):
                raise ValueError(f"Server '{name}' env must be a string mapping.")

            servers.append(LocalServerConfig(
                name=name.strip(),
                transport="stdio",
                command=command.strip(),
                args=args,
                env={str(k): str(v) for k, v in env.items()},
                description=server_raw.get("description", ""),
            ))

        elif transport == "http":
            url = server_raw.get("url")
            if not isinstance(url, str) or not url.strip():
                raise ValueError(f"Server '{name}' with http transport requires 'url'.")
            headers = server_raw.get("headers", {})
            if not isinstance(headers, dict):
                raise ValueError(f"Server '{name}' headers must be a string mapping.")

            servers.append(LocalServerConfig(
                name=name.strip(),
                transport="http",
                url=url.strip(),
                headers={str(k): str(v) for k, v in headers.items()},
                description=server_raw.get("description", ""),
            ))

    return BridgeConfig(servers=servers)
