"""
Prometheus MCP Tool Provider — MCP Phase 2b
=============================================
提供三個 Prometheus 查詢工具，供 AI 情報收集使用：

  prometheus_query           — 即時查詢（PromQL instant query）
  prometheus_query_range     — 範圍查詢（step 預設 60s）
  prometheus_get_alert_history — 告警觸發歷史（最近 N 筆）

設計原則:
- 唯讀（只呼叫 /api/v1/query 和 /api/v1/query_range）
- 所有 HTTP 錯誤回傳 MCPToolResult(success=False)，不拋出
- 逾時 10 秒，避免 AI 等待過長
- Lazy import settings，避免 circular import

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

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

import re
import uuid
from datetime import UTC, datetime, timedelta
from typing import Any

import httpx
import structlog

# P1 fix 2026-04-11: alertname 白名單防止 PromQL label injection
_RE_SAFE_ALERTNAME = re.compile(r"^[a-zA-Z0-9_-]{1,64}$")

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

logger = structlog.get_logger(__name__)

_HTTP_TIMEOUT = 10.0  # 秒


class PrometheusProvider(MCPToolProvider):
    """
    Prometheus MCP Tool Provider

    三個工具:
      prometheus_query          — 即時查詢
      prometheus_query_range    — 範圍查詢（過去 N 分鐘，step 60s）
      prometheus_get_alert_history — 告警觸發歷史
    """

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

    @property
    def enabled(self) -> bool:
        from src.core.config import settings
        return settings.PROMETHEUS_MCP_ENABLED

    def _base_url(self) -> str:
        from src.core.config import settings
        return settings.PROMETHEUS_URL.rstrip("/")

    async def list_tools(self) -> list[MCPTool]:
        return [
            MCPTool(
                name="prometheus_query",
                description=(
                    "Execute a PromQL instant query against Prometheus. "
                    "Returns current value(s) for the given expression."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "PromQL expression, e.g. 'up{job=\"awoooi-api\"}'",
                        },
                        "time": {
                            "type": "string",
                            "description": "Evaluation timestamp (RFC3339 or Unix). Default: now.",
                        },
                    },
                    "required": ["query"],
                },
                server_name=self.name,
            ),
            MCPTool(
                name="prometheus_query_range",
                description=(
                    "Execute a PromQL range query. "
                    "Returns time-series data over the last N minutes."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "PromQL expression",
                        },
                        "window_minutes": {
                            "type": "integer",
                            "description": "Look-back window in minutes (default: 15)",
                        },
                        "step_seconds": {
                            "type": "integer",
                            "description": "Step interval in seconds (default: 60)",
                        },
                    },
                    "required": ["query"],
                },
                server_name=self.name,
            ),
            MCPTool(
                name="prometheus_get_alert_history",
                description=(
                    "Get recent firing history for a specific alertname. "
                    "Returns timestamps and labels of the last N firings."
                ),
                input_schema={
                    "type": "object",
                    "properties": {
                        "alertname": {
                            "type": "string",
                            "description": "Alert name (e.g. HostHighCpuLoad)",
                        },
                        "window_hours": {
                            "type": "integer",
                            "description": "Look-back window in hours (default: 24)",
                        },
                        "limit": {
                            "type": "integer",
                            "description": "Max number of results (default: 20)",
                        },
                    },
                    "required": ["alertname"],
                },
                server_name=self.name,
            ),
        ]

    async def execute(
        self,
        tool_name: str,
        parameters: dict[str, Any],
    ) -> MCPToolResult:
        execution_id = str(uuid.uuid4())
        start = datetime.now(UTC)

        try:
            if tool_name == "prometheus_query":
                output = await self._instant_query(parameters)
            elif tool_name == "prometheus_query_range":
                output = await self._range_query(parameters)
            elif tool_name == "prometheus_get_alert_history":
                output = await self._alert_history(parameters)
            else:
                raise ValueError(f"Unknown tool: {tool_name}")

            duration = (datetime.now(UTC) - start).total_seconds()
            return MCPToolResult(
                success=True,
                execution_id=execution_id,
                output=output,
                duration=duration,
            )

        except Exception as e:
            duration = (datetime.now(UTC) - start).total_seconds()
            logger.warning(
                "prometheus_tool_failed",
                tool=tool_name,
                error=str(e),
            )
            return MCPToolResult(
                success=False,
                execution_id=execution_id,
                error=str(e),
                duration=duration,
            )

    async def health_check(self) -> bool:
        try:
            async with httpx.AsyncClient(timeout=5.0) as client:
                resp = await client.get(f"{self._base_url()}/-/ready")
                return resp.status_code == 200
        except Exception:
            return False

    # =========================================================================
    # Internal helpers
    # =========================================================================

    async def _instant_query(self, params: dict) -> dict:
        # Bug 根因：LLM 產生的 payload 有時用 promql 而非 query，直接存取 ["query"] 會 KeyError
        # 修復：優先取 query，fallback promql；兩者皆缺則返回結構化 error（2026-04-24 Claude Sonnet 4.6）
        query = params.get("query", "") or params.get("promql", "")
        if not query:
            return {"error": "missing_query_parameter", "message": "必須提供 query 或 promql 參數"}
        time_param = params.get("time", "")

        url = f"{self._base_url()}/api/v1/query"
        payload: dict[str, Any] = {"query": query}
        if time_param:
            payload["time"] = time_param

        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
            resp = await client.get(url, params=payload)
            resp.raise_for_status()
            data = resp.json()

        if data.get("status") != "success":
            raise RuntimeError(f"Prometheus error: {data.get('error', 'unknown')}")

        result = data.get("data", {}).get("result", [])
        return {
            "query": query,
            "result_type": data.get("data", {}).get("resultType", ""),
            "result": result,
            "result_count": len(result),
        }

    async def _range_query(self, params: dict) -> dict:
        # Bug 根因：同 _instant_query，LLM 可能用 promql 而非 query（2026-04-24 Claude Sonnet 4.6）
        query = params.get("query", "") or params.get("promql", "")
        if not query:
            return {"error": "missing_query_parameter", "message": "必須提供 query 或 promql 參數"}
        window_minutes = int(params.get("window_minutes", 15))
        step_seconds = int(params.get("step_seconds", 60))

        now = datetime.now(UTC)
        end_ts = now.timestamp()
        start_ts = (now - timedelta(minutes=window_minutes)).timestamp()

        url = f"{self._base_url()}/api/v1/query_range"
        payload = {
            "query": query,
            "start": str(start_ts),
            "end": str(end_ts),
            "step": f"{step_seconds}s",
        }

        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
            resp = await client.get(url, params=payload)
            resp.raise_for_status()
            data = resp.json()

        if data.get("status") != "success":
            raise RuntimeError(f"Prometheus error: {data.get('error', 'unknown')}")

        result = data.get("data", {}).get("result", [])
        return {
            "query": query,
            "window_minutes": window_minutes,
            "step_seconds": step_seconds,
            "result_type": data.get("data", {}).get("resultType", ""),
            "result": result,
            "series_count": len(result),
        }

    async def _alert_history(self, params: dict) -> dict:
        # Bug 根因：LLM 偶發 alertname 缺失，直接存取 ["alertname"] 會 KeyError（2026-04-24 Claude Sonnet 4.6）
        alertname = params.get("alertname", "")
        if not alertname:
            return {"error": "missing_alertname_parameter", "message": "必須提供 alertname 參數"}
        # P1 fix 2026-04-11: 白名單驗證防止 PromQL label injection
        if not _RE_SAFE_ALERTNAME.match(alertname):
            raise ValueError(f"Unsafe alertname: {alertname!r}")
        window_hours = int(params.get("window_hours", 24))
        limit = int(params.get("limit", 20))

        # 用 ALERTS_FOR_STATE metric 查詢告警觸發歷史
        # 若找不到，fallback 用 ALERTS metric
        query = f'ALERTS{{alertname="{alertname}",alertstate="firing"}}'
        window_minutes = window_hours * 60

        range_result = await self._range_query({
            "query": query,
            "window_minutes": window_minutes,
            "step_seconds": 300,  # 5 分鐘粒度
        })

        # 整理為易讀的觸發時間列表
        firings = []
        for series in range_result.get("result", [])[:limit]:
            labels = series.get("metric", {})
            values = series.get("values", [])
            for ts, val in values:
                if float(val) > 0:
                    firings.append({
                        "timestamp": datetime.fromtimestamp(float(ts), tz=UTC).isoformat(),
                        "labels": {k: v for k, v in labels.items() if k != "__name__"},
                        "value": float(val),
                    })

        # 最近優先，截斷到 limit
        firings.sort(key=lambda x: x["timestamp"], reverse=True)
        firings = firings[:limit]

        return {
            "alertname": alertname,
            "window_hours": window_hours,
            "firing_count": len(firings),
            "firings": firings,
        }
