"""
Cookbook — Loading an MCP server catalog from config
=====================================================
On-prem operators want to declare which internal MCP servers an
agent talks to *outside* the application code, so different
environments (dev / staging / prod, or per-customer in a hosted
deployment) can swap them without a rebuild. The SDK does not ship
an opinionated config-loader API — but the recipe is small enough
to live in your own application bootstrap, and this snippet shows
the shape we recommend.

Run::

    python examples/cookbook/mcp_server_catalog.py
"""

import asyncio
import json
import tempfile
from pathlib import Path
from typing import Any

import yaml

from kneo_agent import MCPServerConfig
from kneo_agent.utils import ToolRegistry


def load_mcp_catalog(path: str | Path) -> list[MCPServerConfig]:
    """Parse a YAML or JSON file into a list of ``MCPServerConfig``.

    The schema is deliberately tiny so it can be hand-edited or
    generated by your config-management tooling. Fields map 1:1 to
    ``MCPServerConfig.stdio()`` / ``.http()`` / ``.sse()``
    parameters; unknown fields are forwarded verbatim so future
    additions to the SDK don't require a loader change.

    Schema (YAML)::

        servers:
          - name: filesystem
            transport: stdio
            command: npx
            args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"]

          - name: internal-search
            transport: http
            url: https://mcp.internal.corp/v1
            headers:
              X-Internal-Auth: ${INTERNAL_AUTH}      # caller resolves this
            ca_bundle: /etc/ssl/corp/ca.pem
            client_cert: /etc/ssl/corp/client.crt
            client_key:  /etc/ssl/corp/client.key

          - name: events
            transport: sse
            sse_url: https://events.internal.corp/sse
            verify: true

    The function intentionally does *not* resolve env-var
    substitution on its own — that's an application concern, and
    different teams handle it differently (jinja2, envsubst, your
    own ``${...}`` resolver, or just raw strings if your config
    tool already substitutes upstream).
    """
    path = Path(path)
    raw = path.read_text(encoding="utf-8")
    if path.suffix in (".yaml", ".yml"):
        data = yaml.safe_load(raw)
    elif path.suffix == ".json":
        data = json.loads(raw)
    else:
        raise ValueError(f"Unsupported catalog format: {path.suffix!r}")

    out: list[MCPServerConfig] = []
    for entry in data.get("servers", []):
        out.append(_entry_to_config(entry))
    return out


def _entry_to_config(entry: dict[str, Any]) -> MCPServerConfig:
    transport = entry["transport"]
    if transport == "stdio":
        return MCPServerConfig.stdio(
            name=entry["name"],
            command=entry["command"],
            args=entry.get("args"),
            env=entry.get("env"),
            cwd=entry.get("cwd"),
        )
    if transport == "http":
        return MCPServerConfig.http(
            name=entry["name"],
            url=entry["url"],
            headers=entry.get("headers"),
            timeout=entry.get("timeout", 30.0),
            sse_url=entry.get("sse_url"),
            verify=entry.get("verify", True),
            ca_bundle=entry.get("ca_bundle"),
            client_cert=entry.get("client_cert"),
            client_key=entry.get("client_key"),
        )
    if transport == "sse":
        return MCPServerConfig.sse(
            name=entry["name"],
            sse_url=entry["sse_url"],
            message_url=entry.get("message_url"),
            headers=entry.get("headers"),
            timeout=entry.get("timeout", 30.0),
            verify=entry.get("verify", True),
            ca_bundle=entry.get("ca_bundle"),
            client_cert=entry.get("client_cert"),
            client_key=entry.get("client_key"),
        )
    raise ValueError(f"Unknown MCP transport {transport!r}")


# ── Demo: write a sample catalog and load it ────────────────────────


SAMPLE_CATALOG = """
servers:
  - name: filesystem-stdio
    transport: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

  - name: internal-http
    transport: http
    url: https://mcp.internal.corp/v1
    timeout: 10
    headers:
      X-Internal-Auth: dev-token

  - name: events-sse
    transport: sse
    sse_url: https://events.internal.corp/sse
"""


async def main() -> None:
    # In production: load from a known path (config map, mounted file,
    # env-driven location). For the example we write a temp file.
    with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as fh:
        fh.write(SAMPLE_CATALOG)
        catalog_path = fh.name

    catalog = load_mcp_catalog(catalog_path)

    print(f"Loaded {len(catalog)} MCP server(s) from {catalog_path}:\n")
    for cfg in catalog:
        print(f"  {cfg.name:20} transport={cfg.transport}")

    # Register them with a ToolRegistry. In a real app this would happen
    # at startup, then the registry is shared across agents.
    # (Skipping the actual register_mcp_server() call here because it
    # would try to start the stdio server / open the HTTP connection.)
    registry = ToolRegistry()
    print(f"\nReady to register against {registry!r} via "
          f"registry.register_mcp_server(cfg) at startup.")


if __name__ == "__main__":
    asyncio.run(main())
