"""
ArgoCD MCP Tool Provider — MCP Phase 3
========================================
提供三個 ArgoCD 管理工具，供 AI 查詢 GitOps 部署狀態：

  argocd_list_apps        — 列出所有 Application 及同步狀態
  argocd_get_app_status   — 取得特定 Application 詳細狀態
  argocd_get_sync_history — 取得 Application 最近 N 筆部署記錄

設計原則:
- 唯讀（只呼叫 GET endpoints）
- 透過 ArgoCD API Token 認證
- 逾時 10 秒
- 失敗回傳 MCPToolResult(success=False)，不拋出

建立時間: 2026-04-11 (台北時區)
建立者: Claude Sonnet 4.6 — MCP Phase 3

@see docs/superpowers/specs/2026-04-10-infra-rebuild-sprint-abc-design.md §MCP-3
"""

import re
from typing import Any

import httpx
import structlog

from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult

logger = structlog.get_logger(__name__)

_HTTP_TIMEOUT = 10.0
_RE_SAFE_APP_NAME = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$')


def _tls_verify() -> bool:
    """P2 fix 2026-04-11: 讀取 ARGOCD_VERIFY_TLS env，預設 False（因 self-signed cert）。"""
    import os
    return os.environ.get("ARGOCD_VERIFY_TLS", "false").lower() in ("true", "1", "yes")


def _validate_app_name(name: str) -> str:
    if not _RE_SAFE_APP_NAME.match(name):
        raise ValueError(f"Unsafe app name: {name!r}")
    return name


class ArgoCDProvider(MCPToolProvider):
    """
    ArgoCD MCP Tool Provider

    三個工具:
      argocd_list_apps        — 列出所有 Applications
      argocd_get_app_status   — 取得單一 Application 狀態
      argocd_get_sync_history — 取得部署歷史
    """

    @property
    def name(self) -> str:
        return "argocd"

    @property
    def enabled(self) -> bool:
        from src.core.config import settings
        return getattr(settings, "ARGOCD_MCP_ENABLED", False)

    def _base_url(self) -> str:
        from src.core.config import settings
        return getattr(settings, "ARGOCD_URL", "http://192.168.0.125:32080").rstrip("/")

    def _headers(self) -> dict:
        from src.core.config import settings
        token = getattr(settings, "ARGOCD_API_TOKEN", "")
        if token:
            return {"Authorization": f"Bearer {token}"}
        return {}

    async def list_tools(self) -> list[MCPTool]:
        return [
            MCPTool(
                name="argocd_list_apps",
                description=(
                    "List all ArgoCD Applications with their sync and health status. "
                    "Returns name, sync_status (Synced/OutOfSync), health (Healthy/Degraded), "
                    "and last sync time."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "namespace": {
                            "type": "string",
                            "description": "Filter by destination namespace (optional)",
                        }
                    },
                    "required": [],
                },
            ),
            MCPTool(
                name="argocd_get_app_status",
                description=(
                    "Get detailed status of a specific ArgoCD Application. "
                    "Returns sync status, health, resources list, and last sync result."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "app_name": {
                            "type": "string",
                            "description": "ArgoCD Application name, e.g. 'awoooi-prod'",
                        }
                    },
                    "required": ["app_name"],
                },
            ),
            MCPTool(
                name="argocd_get_sync_history",
                description=(
                    "Get the recent sync history of an ArgoCD Application. "
                    "Returns last N deploys with revision, status, and timestamp."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "app_name": {
                            "type": "string",
                            "description": "ArgoCD Application name",
                        },
                        "limit": {
                            "type": "integer",
                            "description": "Number of history entries to return (1-20, default 5)",
                            "default": 5,
                        },
                    },
                    "required": ["app_name"],
                },
            ),
        ]

    async def execute(self, tool_name: str, parameters: dict[str, Any]) -> MCPToolResult:
        handlers = {
            "argocd_list_apps": self._list_apps,
            "argocd_get_app_status": self._get_app_status,
            "argocd_get_sync_history": self._get_sync_history,
        }
        handler = handlers.get(tool_name)
        if not handler:
            return MCPToolResult(success=False, error=f"Unknown tool: {tool_name}")
        try:
            return await handler(parameters)
        except Exception as e:
            logger.error("argocd_mcp_error", tool=tool_name, error=str(e))
            return MCPToolResult(success=False, error=str(e))

    async def _list_apps(self, parameters: dict) -> MCPToolResult:
        ns_filter = parameters.get("namespace", "")
        url = f"{self._base_url()}/api/v1/applications"
        params = {}
        if ns_filter:
            params["appNamespace"] = ns_filter

        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT, verify=_tls_verify()) as client:
            resp = await client.get(url, headers=self._headers(), params=params)
            resp.raise_for_status()
            data = resp.json()

        apps = data.get("items") or []
        result = []
        for app in apps:
            metadata = app.get("metadata", {})
            status = app.get("status", {})
            sync = status.get("sync", {})
            health = status.get("health", {})
            op_state = status.get("operationState", {})
            result.append({
                "name": metadata.get("name"),
                "sync_status": sync.get("status"),
                "health_status": health.get("status"),
                "revision": sync.get("revision", "")[:8],
                "last_sync": op_state.get("finishedAt", ""),
                "destination_namespace": status.get("summary", {}).get("images"),
            })

        return MCPToolResult(
            success=True,
            data={"apps": result, "count": len(result)},
        )

    async def _get_app_status(self, parameters: dict) -> MCPToolResult:
        app_name = _validate_app_name(parameters["app_name"])
        url = f"{self._base_url()}/api/v1/applications/{app_name}"

        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT, verify=_tls_verify()) as client:
            resp = await client.get(url, headers=self._headers())
            resp.raise_for_status()
            app = resp.json()

        status = app.get("status", {})
        sync = status.get("sync", {})
        health = status.get("health", {})
        op_state = status.get("operationState", {})
        resources = status.get("resources", [])

        # 只取 degraded/outOfSync 資源
        problem_resources = [
            {
                "kind": r.get("kind"),
                "name": r.get("name"),
                "namespace": r.get("namespace"),
                "status": r.get("status"),
                "health": r.get("health", {}).get("status") if r.get("health") else None,
            }
            for r in resources
            if r.get("status") not in ("Synced", None) or
               (r.get("health") or {}).get("status") not in ("Healthy", None)
        ]

        return MCPToolResult(
            success=True,
            data={
                "name": app.get("metadata", {}).get("name"),
                "sync_status": sync.get("status"),
                "health_status": health.get("status"),
                "revision": sync.get("revision", "")[:8],
                "last_sync_at": op_state.get("finishedAt"),
                "last_sync_message": op_state.get("message"),
                "total_resources": len(resources),
                "problem_resources": problem_resources,
            },
        )

    async def _get_sync_history(self, parameters: dict) -> MCPToolResult:
        app_name = _validate_app_name(parameters["app_name"])
        limit = max(1, min(int(parameters.get("limit", 5)), 20))
        url = f"{self._base_url()}/api/v1/applications/{app_name}/revisions"

        # ArgoCD history endpoint
        history_url = f"{self._base_url()}/api/v1/applications/{app_name}"
        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT, verify=_tls_verify()) as client:
            resp = await client.get(history_url, headers=self._headers())
            resp.raise_for_status()
            app = resp.json()

        history = app.get("status", {}).get("history", [])
        history = sorted(history, key=lambda x: x.get("deployedAt", ""), reverse=True)[:limit]

        entries = [
            {
                "revision": h.get("revision", "")[:8],
                "deployed_at": h.get("deployedAt"),
                "deploy_started_at": h.get("deployStartedAt"),
                "id": h.get("id"),
            }
            for h in history
        ]

        return MCPToolResult(
            success=True,
            data={"app_name": app_name, "history": entries, "count": len(entries)},
        )

    async def health_check(self) -> bool:
        try:
            url = f"{self._base_url()}/api/v1/applicationsets"
            async with httpx.AsyncClient(timeout=5.0, verify=_tls_verify()) as client:
                resp = await client.get(url, headers=self._headers())
                return resp.status_code < 500
        except Exception:
            return False
