"""Agent dependency models — MCP servers and Claude Code plugins."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path


class McpType(StrEnum):
    """MCP server transport/install type."""

    BUILTIN = "builtin"
    NPM = "npm"
    PIP = "pip"
    HTTP = "http"


@dataclass
class EnvVarSpec:
    """Declaration of an environment variable needed by an MCP server."""

    name: str
    description: str = ""
    required: bool = True


@dataclass
class McpDependency:
    """A single MCP server dependency."""

    name: str
    type: McpType = McpType.BUILTIN
    package: str | None = None
    args: list[str] = field(default_factory=list)
    url: str | None = None
    headers: dict[str, str] = field(default_factory=dict)
    env: list[EnvVarSpec] = field(default_factory=list)

    @classmethod
    def from_dict(cls, data: dict) -> McpDependency:
        """Parse from agent.yaml dict."""
        env_raw = data.get("env", {})
        env_specs: list[EnvVarSpec] = []
        if isinstance(env_raw, dict):
            for var_name, var_info in env_raw.items():
                if isinstance(var_info, dict):
                    env_specs.append(
                        EnvVarSpec(
                            name=var_name,
                            description=var_info.get("description", ""),
                            required=var_info.get("required", True),
                        )
                    )
                else:
                    env_specs.append(EnvVarSpec(name=var_name))

        # Support legacy "builtin: true" format
        mcp_type = data.get("type", "builtin")
        if data.get("builtin"):
            mcp_type = "builtin"

        return cls(
            name=data["name"],
            type=McpType(mcp_type),
            package=data.get("package"),
            args=data.get("args", []),
            url=data.get("url"),
            headers=data.get("headers", {}),
            env=env_specs,
        )

    def to_dict(self) -> dict:
        """Serialize to agent.yaml dict."""
        d: dict = {"name": self.name, "type": self.type.value}
        if self.package:
            d["package"] = self.package
        if self.args:
            d["args"] = self.args
        if self.url:
            d["url"] = self.url
        if self.headers:
            d["headers"] = self.headers
        if self.env:
            d["env"] = {
                e.name: {"description": e.description, "required": e.required} for e in self.env
            }
        return d


@dataclass
class PluginDependency:
    """A Claude Code plugin dependency."""

    name: str
    marketplace: str = "claude-plugins-official"
    description: str = ""
    required: bool = True

    @classmethod
    def from_dict(cls, data: str | dict) -> PluginDependency:
        """Parse from agent.yaml. Handles both string and dict formats."""
        if isinstance(data, str):
            return cls(name=data)
        return cls(
            name=data["name"],
            marketplace=data.get("marketplace", "claude-plugins-official"),
            description=data.get("description", ""),
            required=data.get("required", True),
        )

    def to_dict(self) -> dict:
        d: dict = {"name": self.name, "marketplace": self.marketplace}
        if self.description:
            d["description"] = self.description
        if not self.required:
            d["required"] = False
        return d


@dataclass
class DepCheckResult:
    """Result of checking a single dependency."""

    name: str
    dep_type: str  # "mcp" or "plugin"
    satisfied: bool
    message: str
    fixable: bool = False
    fix_command: str = ""


def check_mcp_deps(root: Path, mcp_deps: list[McpDependency]) -> list[DepCheckResult]:
    """Check if all MCP deps are in .mcp.json and env vars are set."""
    results: list[DepCheckResult] = []

    mcp_path = root / ".mcp.json"
    servers: dict = {}
    if mcp_path.exists():
        config = json.loads(mcp_path.read_text())
        servers = config.get("mcpServers", {})

    for dep in mcp_deps:
        if dep.type == McpType.BUILTIN:
            results.append(
                DepCheckResult(
                    name=dep.name,
                    dep_type="mcp",
                    satisfied=True,
                    message="builtin",
                )
            )
            continue

        if dep.name not in servers:
            results.append(
                DepCheckResult(
                    name=dep.name,
                    dep_type="mcp",
                    satisfied=False,
                    message="not in .mcp.json",
                    fixable=True,
                    fix_command="ievo deps install",
                )
            )
            continue

        missing_env = [e for e in dep.env if e.required and not os.environ.get(e.name)]
        if missing_env:
            names = ", ".join(e.name for e in missing_env)
            results.append(
                DepCheckResult(
                    name=dep.name,
                    dep_type="mcp",
                    satisfied=False,
                    message=f"missing env: {names}",
                )
            )
        else:
            results.append(
                DepCheckResult(
                    name=dep.name,
                    dep_type="mcp",
                    satisfied=True,
                    message="configured",
                )
            )

    return results


def check_plugin_deps(plugins: list[PluginDependency]) -> list[DepCheckResult]:
    """Best-effort check for plugin installation.

    We can't reliably verify plugin install state from outside Claude Code,
    so we return advisory results.
    """
    results: list[DepCheckResult] = []
    for plugin in plugins:
        results.append(
            DepCheckResult(
                name=plugin.name,
                dep_type="plugin",
                satisfied=False,
                message="verify via /plugin list in Claude Code",
                fix_command=f"/plugin install {plugin.name}",
            )
        )
    return results


def ensure_mcp_configured(root: Path, mcp_deps: list[McpDependency]) -> list[str]:
    """Write missing MCP servers to .mcp.json.

    Returns list of warning messages (e.g., missing env vars).
    """
    mcp_path = root / ".mcp.json"
    config = json.loads(mcp_path.read_text()) if mcp_path.exists() else {"mcpServers": {}}

    servers = config.setdefault("mcpServers", {})
    warnings: list[str] = []
    changed = False

    for dep in mcp_deps:
        if dep.type == McpType.BUILTIN:
            continue
        if dep.name in servers:
            continue

        if dep.type == McpType.NPM:
            servers[dep.name] = {
                "type": "stdio",
                "command": "npx",
                "args": ["-y", dep.package, *dep.args],
            }
        elif dep.type == McpType.PIP:
            servers[dep.name] = {
                "type": "stdio",
                "command": "uvx",
                "args": [dep.package, *dep.args],
            }
        elif dep.type == McpType.HTTP:
            servers[dep.name] = {
                "type": "http",
                "url": dep.url,
            }
            if dep.headers:
                servers[dep.name]["headers"] = dep.headers

        # Add env vars as ${VAR} references
        if dep.env and dep.type != McpType.HTTP:
            env_dict = {}
            for env_spec in dep.env:
                env_dict[env_spec.name] = f"${{{env_spec.name}}}"
            servers[dep.name]["env"] = env_dict

        changed = True

        # Warn about missing env vars
        for env_spec in dep.env:
            if env_spec.required and not os.environ.get(env_spec.name):
                desc = f" — {env_spec.description}" if env_spec.description else ""
                warnings.append(f"{dep.name}: set {env_spec.name}{desc}")

    if changed:
        mcp_path.write_text(json.dumps(config, indent=2) + "\n")

    return warnings
