"""Curated MCP connector registry.

Provides a catalog of known MCP servers that users can enable per-task.
The control plane resolves selected connector IDs into ``MCPServerConfig``
objects and passes them to the agent at startup.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from loguru import logger

try:
    import yaml
except ImportError:
    yaml = None  # type: ignore[assignment]


_REGISTRY_FILE = Path(__file__).parent / "registry.yaml"


@dataclass(frozen=True)
class ConnectorEntry:
    """A single entry in the connector registry."""
    id: str
    name: str
    description: str
    category: str
    transport: str  # "stdio" | "sse" | "streamableHttp"
    command: str = ""
    args: list[str] = field(default_factory=list)
    url: str = ""
    env_keys: list[str] = field(default_factory=list)
    tools_preview: list[str] = field(default_factory=list)
    docs_url: str = ""

    def to_mcp_config_dict(self, env: dict[str, str] | None = None) -> dict[str, Any]:
        """Convert to a dict compatible with ``MCPServerConfig``.

        Args:
            env: Environment variables to inject (secrets resolved by
                 the control plane before passing to the agent).
        """
        cfg: dict[str, Any] = {"type": self.transport}
        if self.transport == "stdio":
            cfg["command"] = self.command
            cfg["args"] = list(self.args)
            if env:
                cfg["env"] = {k: env[k] for k in self.env_keys if k in env}
        else:
            cfg["url"] = self.url
        return cfg


class ConnectorRegistry:
    """Loads and queries the curated connector catalog."""

    def __init__(self, registry_path: Path | None = None):
        self._path = registry_path or _REGISTRY_FILE
        self._entries: dict[str, ConnectorEntry] = {}
        self._loaded = False

    def _ensure_loaded(self) -> None:
        if self._loaded:
            return
        self._load()

    def _load(self) -> None:
        if yaml is None:
            logger.warning("PyYAML not installed — connector registry disabled")
            self._loaded = True
            return

        if not self._path.exists():
            logger.warning("Connector registry not found at {}", self._path)
            self._loaded = True
            return

        try:
            data = yaml.safe_load(self._path.read_text(encoding="utf-8"))
        except Exception as exc:
            logger.error("Failed to load connector registry: {}", exc)
            self._loaded = True
            return

        if not isinstance(data, dict):
            self._loaded = True
            return

        for cid, raw in data.items():
            if not isinstance(raw, dict):
                continue
            self._entries[cid] = ConnectorEntry(
                id=cid,
                name=raw.get("name", cid),
                description=raw.get("description", ""),
                category=raw.get("category", "custom"),
                transport=raw.get("transport", "stdio"),
                command=raw.get("command", ""),
                args=raw.get("args", []),
                url=raw.get("url", ""),
                env_keys=raw.get("env_keys", []),
                tools_preview=raw.get("tools_preview", []),
                docs_url=raw.get("docs_url", ""),
            )

        self._loaded = True
        logger.info("Connector registry loaded: {} connectors", len(self._entries))

    def get(self, connector_id: str) -> ConnectorEntry | None:
        """Look up a connector by ID."""
        self._ensure_loaded()
        return self._entries.get(connector_id)

    def list_all(self) -> list[ConnectorEntry]:
        """Return all registered connectors."""
        self._ensure_loaded()
        return list(self._entries.values())

    def list_by_category(self, category: str) -> list[ConnectorEntry]:
        """Return connectors in a given category."""
        self._ensure_loaded()
        return [e for e in self._entries.values() if e.category == category]

    def resolve(
        self,
        connector_ids: list[str],
        env: dict[str, str] | None = None,
    ) -> dict[str, dict[str, Any]]:
        """Resolve a list of connector IDs into MCP server configs.

        Args:
            connector_ids: List of connector IDs to resolve.
            env: Environment variables for secret injection.

        Returns:
            Dict mapping connector ID to MCP config dict, ready for
            ``MCPServerConfig.model_validate()``.
        """
        self._ensure_loaded()
        result: dict[str, dict[str, Any]] = {}
        for cid in connector_ids:
            entry = self._entries.get(cid)
            if entry is None:
                logger.warning("Unknown connector '{}', skipping", cid)
                continue
            # Validate required env keys
            missing = [k for k in entry.env_keys if not (env or {}).get(k)]
            if missing:
                logger.warning(
                    "Connector '{}' missing env keys: {} — skipping",
                    cid, ", ".join(missing),
                )
                continue
            result[cid] = entry.to_mcp_config_dict(env)
        return result

    def get_categories(self) -> list[str]:
        """Return sorted list of unique categories."""
        self._ensure_loaded()
        return sorted(set(e.category for e in self._entries.values()))

    def validate_connector_ids(self, connector_ids: list[str]) -> list[str]:
        """Return list of unknown connector IDs (for validation at task creation)."""
        self._ensure_loaded()
        return [cid for cid in connector_ids if cid not in self._entries]


# Module-level singleton
_registry: ConnectorRegistry | None = None


def get_connector_registry() -> ConnectorRegistry:
    """Get or create the global connector registry singleton."""
    global _registry
    if _registry is None:
        _registry = ConnectorRegistry()
    return _registry
