"""
Grafana MCP Tool Provider - Phase 13.2 #83
==========================================

提供 Grafana Dashboard 查詢工具：
- list_dashboards: 列出可用 Dashboard
- get_dashboard: 取得 Dashboard 詳細資訊
- get_panel_data: 取得 Panel 數據
- generate_dashboard_url: 產生 Dashboard URL

安全機制:
1. API Key 認證 (Bearer Token)
2. 唯讀操作 (Read-Only)
3. Dashboard UID 驗證

透過 DI 注入配置，不直接 import services。

@see docs/adr/ADR-015-mcp-modular-architecture.md
@author Claude Code
@version 1.1
@created 2026-03-26 (台北時區)
@updated 2026-03-29 (台北時區) - P1 修復: URL/Key 快取優化
@issue #83
"""

import re
import uuid
from typing import Any
from urllib.parse import urljoin

import httpx
import structlog

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

logger = structlog.get_logger(__name__)

# =============================================================================
# Configuration Constants
# =============================================================================

# Default Grafana URL (can be overridden via settings)
DEFAULT_GRAFANA_URL = "http://192.168.0.188:3000"

# Dashboard UID validation pattern (alphanumeric, dash, underscore)
DASHBOARD_UID_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$")

# Request timeout in seconds
REQUEST_TIMEOUT = 30.0


class GrafanaError(Exception):
    """Grafana API 錯誤"""
    pass


class GrafanaProvider(MCPToolProvider):
    """
    Grafana MCP Tool Provider

    提供 Grafana Dashboard 查詢功能：
    1. 列出所有 Dashboard
    2. 取得 Dashboard 詳細資訊
    3. 取得 Panel 數據
    4. 產生 Dashboard URL

    Security Notes:
    - 所有操作都是唯讀
    - Dashboard UID 會被驗證格式
    - API Key 透過 Bearer Token 認證
    """

    def __init__(
        self,
        grafana_url: str | None = None,
        api_key: str | None = None,
    ) -> None:
        """
        初始化 Provider

        Args:
            grafana_url: Grafana URL (default: from settings)
            api_key: Grafana API Key (default: from settings)

        P1 修復 (2026-03-29): 快取 URL/Key 避免重複 settings 查詢
        """
        self._grafana_url_input = grafana_url
        self._api_key_input = api_key
        self._grafana_url: str | None = None  # 快取解析後的 URL
        self._api_key: str | None = None  # 快取解析後的 Key
        self._client: httpx.AsyncClient | None = None

        logger.info(
            "grafana_provider_initialized",
            grafana_url=self._get_grafana_url(),
        )

    def _get_grafana_url(self) -> str:
        """
        取得 Grafana URL (快取 + lazy load from settings)

        P1 修復: 首次查詢後快取結果
        """
        if self._grafana_url is not None:
            return self._grafana_url

        if self._grafana_url_input:
            self._grafana_url = self._grafana_url_input
            return self._grafana_url

        try:
            from src.core.config import get_settings
            settings = get_settings()
            self._grafana_url = getattr(settings, "GRAFANA_URL", DEFAULT_GRAFANA_URL)
        except Exception:
            self._grafana_url = DEFAULT_GRAFANA_URL

        return self._grafana_url

    def _get_api_key(self) -> str | None:
        """
        取得 API Key (快取 + lazy load from settings)

        P1 修復: 首次查詢後快取結果
        """
        if self._api_key is not None:
            return self._api_key

        if self._api_key_input:
            self._api_key = self._api_key_input
            return self._api_key

        try:
            from src.core.config import get_settings
            settings = get_settings()
            self._api_key = getattr(settings, "GRAFANA_API_KEY", None)
        except Exception:
            self._api_key = None

        return self._api_key

    def _get_headers(self) -> dict[str, str]:
        """取得 HTTP Headers (含認證)"""
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

        api_key = self._get_api_key()
        if api_key:
            headers["Authorization"] = f"Bearer {api_key}"

        return headers

    async def _get_client(self) -> httpx.AsyncClient:
        """取得 HTTP Client (lazy initialization)"""
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self._get_grafana_url(),
                headers=self._get_headers(),
                timeout=REQUEST_TIMEOUT,
            )
        return self._client

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

    async def list_tools(self) -> list[MCPTool]:
        return [
            MCPTool(
                name="list_dashboards",
                description="List available Grafana dashboards. Returns dashboard titles, UIDs, and folder info.",
                input_schema={
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "Search query to filter dashboards by title",
                        },
                        "folder_id": {
                            "type": "integer",
                            "description": "Filter by folder ID (0 = General folder)",
                        },
                        "tag": {
                            "type": "string",
                            "description": "Filter by tag (e.g., 'kubernetes', 'prometheus')",
                        },
                        "limit": {
                            "type": "integer",
                            "description": "Max results (default: 100)",
                            "default": 100,
                        },
                    },
                },
                server_name=self.name,
            ),
            MCPTool(
                name="get_dashboard",
                description="Get dashboard details by UID. Returns panels, variables, and annotations.",
                input_schema={
                    "type": "object",
                    "properties": {
                        "uid": {
                            "type": "string",
                            "description": "Dashboard UID (e.g., 'abc123')",
                        },
                    },
                    "required": ["uid"],
                },
                server_name=self.name,
            ),
            MCPTool(
                name="get_panel_data",
                description="Get data from a specific panel in a dashboard. Useful for fetching metrics.",
                input_schema={
                    "type": "object",
                    "properties": {
                        "dashboard_uid": {
                            "type": "string",
                            "description": "Dashboard UID",
                        },
                        "panel_id": {
                            "type": "integer",
                            "description": "Panel ID within the dashboard",
                        },
                        "from_time": {
                            "type": "string",
                            "description": "Start time (e.g., 'now-1h', '2026-03-26T00:00:00Z')",
                            "default": "now-1h",
                        },
                        "to_time": {
                            "type": "string",
                            "description": "End time (e.g., 'now', '2026-03-26T01:00:00Z')",
                            "default": "now",
                        },
                    },
                    "required": ["dashboard_uid", "panel_id"],
                },
                server_name=self.name,
            ),
            MCPTool(
                name="generate_dashboard_url",
                description="Generate a URL to view a dashboard with optional time range and variables.",
                input_schema={
                    "type": "object",
                    "properties": {
                        "uid": {
                            "type": "string",
                            "description": "Dashboard UID",
                        },
                        "from_time": {
                            "type": "string",
                            "description": "Start time (e.g., 'now-1h', 'now-24h')",
                            "default": "now-1h",
                        },
                        "to_time": {
                            "type": "string",
                            "description": "End time (e.g., 'now')",
                            "default": "now",
                        },
                        "variables": {
                            "type": "object",
                            "description": "Template variables as key-value pairs (e.g., {'namespace': 'awoooi-prod'})",
                        },
                        "panel_id": {
                            "type": "integer",
                            "description": "Focus on specific panel (opens panel view)",
                        },
                    },
                    "required": ["uid"],
                },
                server_name=self.name,
            ),
        ]

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

        try:
            if tool_name == "list_dashboards":
                output = await self._list_dashboards(parameters)
            elif tool_name == "get_dashboard":
                output = await self._get_dashboard(parameters)
            elif tool_name == "get_panel_data":
                output = await self._get_panel_data(parameters)
            elif tool_name == "generate_dashboard_url":
                output = await self._generate_dashboard_url(parameters)
            else:
                return MCPToolResult(
                    success=False,
                    execution_id=execution_id,
                    error=f"Unknown tool: {tool_name}",
                )

            logger.info(
                "grafana_tool_executed",
                tool=tool_name,
                execution_id=execution_id,
            )

            return MCPToolResult(
                success=True,
                execution_id=execution_id,
                output=output,
            )

        except GrafanaError as e:
            logger.warning(
                "grafana_api_error",
                tool=tool_name,
                execution_id=execution_id,
                error=str(e),
            )
            return MCPToolResult(
                success=False,
                execution_id=execution_id,
                error=f"Grafana API error: {e}",
            )
        except httpx.HTTPStatusError as e:
            logger.warning(
                "grafana_http_error",
                tool=tool_name,
                execution_id=execution_id,
                status_code=e.response.status_code,
            )
            return MCPToolResult(
                success=False,
                execution_id=execution_id,
                error=f"HTTP {e.response.status_code}: {e.response.text[:200]}",
            )
        except httpx.RequestError as e:
            logger.warning(
                "grafana_request_error",
                tool=tool_name,
                execution_id=execution_id,
                error=str(e),
            )
            return MCPToolResult(
                success=False,
                execution_id=execution_id,
                error=f"Request failed: {e}",
            )
        except Exception as e:
            logger.exception(
                "grafana_provider_error",
                tool=tool_name,
                execution_id=execution_id,
                error=str(e),
            )
            return MCPToolResult(
                success=False,
                execution_id=execution_id,
                error=str(e),
            )

    # =========================================================================
    # Validation Methods
    # =========================================================================

    def _validate_dashboard_uid(self, uid: str) -> str:
        """
        驗證 Dashboard UID 格式

        Args:
            uid: Dashboard UID

        Returns:
            str: 驗證後的 UID

        Raises:
            GrafanaError: UID 格式不正確
        """
        if not uid:
            raise GrafanaError("Dashboard UID is required")

        uid = uid.strip()

        if len(uid) > 40:
            raise GrafanaError(f"Dashboard UID too long: {len(uid)} chars (max: 40)")

        if not DASHBOARD_UID_PATTERN.match(uid):
            raise GrafanaError(
                f"Invalid dashboard UID format: {uid}. "
                "Only alphanumeric, dash, and underscore allowed."
            )

        return uid

    # =========================================================================
    # Tool Implementations
    # =========================================================================

    async def _list_dashboards(self, parameters: dict) -> dict:
        """
        列出 Dashboard

        Grafana API: GET /api/search
        """
        query = parameters.get("query")
        folder_id = parameters.get("folder_id")
        tag = parameters.get("tag")
        limit = parameters.get("limit", 100)

        # 構建查詢參數
        params: dict[str, Any] = {
            "type": "dash-db",  # 只搜尋 dashboard
            "limit": min(limit, 500),  # 最多 500
        }

        if query:
            params["query"] = query
        if folder_id is not None:
            params["folderIds"] = folder_id
        if tag:
            params["tag"] = tag

        # 呼叫 Grafana API
        client = await self._get_client()
        response = await client.get("/api/search", params=params)
        response.raise_for_status()

        dashboards = response.json()

        # 轉換回應格式
        results = []
        for db in dashboards:
            results.append({
                "uid": db.get("uid"),
                "title": db.get("title"),
                "folder_title": db.get("folderTitle", "General"),
                "folder_id": db.get("folderId", 0),
                "url": db.get("url"),
                "tags": db.get("tags", []),
                "type": db.get("type"),
            })

        return {
            "dashboards": results,
            "count": len(results),
            "filters": {
                "query": query,
                "folder_id": folder_id,
                "tag": tag,
            },
        }

    async def _get_dashboard(self, parameters: dict) -> dict:
        """
        取得 Dashboard 詳細資訊

        Grafana API: GET /api/dashboards/uid/:uid
        """
        uid = parameters.get("uid", "")
        uid = self._validate_dashboard_uid(uid)

        # 呼叫 Grafana API
        client = await self._get_client()
        response = await client.get(f"/api/dashboards/uid/{uid}")
        response.raise_for_status()

        data = response.json()
        dashboard = data.get("dashboard", {})
        meta = data.get("meta", {})

        # 提取 Panel 資訊
        panels = []
        for panel in dashboard.get("panels", []):
            panels.append({
                "id": panel.get("id"),
                "title": panel.get("title"),
                "type": panel.get("type"),
                "description": panel.get("description"),
                "datasource": panel.get("datasource"),
            })

        # 提取變數資訊
        variables = []
        for var in dashboard.get("templating", {}).get("list", []):
            variables.append({
                "name": var.get("name"),
                "label": var.get("label"),
                "type": var.get("type"),
                "current_value": var.get("current", {}).get("value"),
            })

        return {
            "uid": uid,
            "title": dashboard.get("title"),
            "description": dashboard.get("description"),
            "tags": dashboard.get("tags", []),
            "folder": meta.get("folderTitle", "General"),
            "created_by": meta.get("createdBy"),
            "updated_by": meta.get("updatedBy"),
            "version": dashboard.get("version"),
            "panels": panels,
            "panel_count": len(panels),
            "variables": variables,
            "url": meta.get("url"),
        }

    async def _get_panel_data(self, parameters: dict) -> dict:
        """
        取得 Panel 數據

        使用 Grafana Query API 取得 Panel 數據
        Grafana API: POST /api/ds/query
        """
        dashboard_uid = parameters.get("dashboard_uid", "")
        dashboard_uid = self._validate_dashboard_uid(dashboard_uid)

        panel_id = parameters.get("panel_id")
        if panel_id is None:
            raise GrafanaError("Panel ID is required")

        from_time = parameters.get("from_time", "now-1h")
        to_time = parameters.get("to_time", "now")

        # 先取得 Dashboard 取得 Panel 定義
        client = await self._get_client()
        dash_response = await client.get(f"/api/dashboards/uid/{dashboard_uid}")
        dash_response.raise_for_status()

        dashboard = dash_response.json().get("dashboard", {})
        panel = None

        # 尋找 Panel
        for p in dashboard.get("panels", []):
            if p.get("id") == panel_id:
                panel = p
                break

        if not panel:
            raise GrafanaError(f"Panel {panel_id} not found in dashboard {dashboard_uid}")

        # 取得 Panel 基本資訊
        panel_info = {
            "dashboard_uid": dashboard_uid,
            "panel_id": panel_id,
            "panel_title": panel.get("title"),
            "panel_type": panel.get("type"),
            "from_time": from_time,
            "to_time": to_time,
        }

        # 取得 Panel 的查詢定義
        targets = panel.get("targets", [])
        if not targets:
            return {
                **panel_info,
                "data": None,
                "message": "Panel has no query targets",
            }

        # 使用 Grafana 的 Query API
        # 注意: 這需要 datasource 支援且有權限
        datasource = panel.get("datasource")
        if not datasource:
            return {
                **panel_info,
                "data": None,
                "message": "Panel has no datasource configured",
                "targets": [t.get("expr", t.get("query", "N/A")) for t in targets],
            }

        # 嘗試執行查詢
        try:
            query_payload = {
                "queries": [
                    {
                        **target,
                        "datasource": datasource,
                        "refId": target.get("refId", "A"),
                    }
                    for target in targets
                ],
                "from": from_time,
                "to": to_time,
            }

            query_response = await client.post("/api/ds/query", json=query_payload)
            query_response.raise_for_status()
            query_data = query_response.json()

            # 解析回應
            results = query_data.get("results", {})
            frames = []

            for ref_id, result in results.items():
                for frame in result.get("frames", []):
                    schema = frame.get("schema", {})
                    data = frame.get("data", {})
                    frames.append({
                        "ref_id": ref_id,
                        "name": schema.get("name"),
                        "fields": [
                            {
                                "name": f.get("name"),
                                "type": f.get("type"),
                            }
                            for f in schema.get("fields", [])
                        ],
                        "row_count": len(data.get("values", [[]])[0]) if data.get("values") else 0,
                    })

            return {
                **panel_info,
                "datasource": datasource,
                "frames": frames,
                "frame_count": len(frames),
            }

        except httpx.HTTPStatusError as e:
            # 查詢失敗，回傳 Panel 資訊但無數據
            return {
                **panel_info,
                "data": None,
                "message": f"Query failed: HTTP {e.response.status_code}",
                "targets": [t.get("expr", t.get("query", "N/A")) for t in targets],
            }

    async def _generate_dashboard_url(self, parameters: dict) -> dict:
        """
        產生 Dashboard URL

        支援時間範圍、變數、Panel 聚焦
        """
        uid = parameters.get("uid", "")
        uid = self._validate_dashboard_uid(uid)

        from_time = parameters.get("from_time", "now-1h")
        to_time = parameters.get("to_time", "now")
        variables = parameters.get("variables", {})
        panel_id = parameters.get("panel_id")

        # 構建 URL
        base_url = self._get_grafana_url()
        dashboard_path = f"/d/{uid}"

        # 構建查詢參數
        query_params = [
            f"from={from_time}",
            f"to={to_time}",
        ]

        # 添加變數
        for key, value in variables.items():
            # Grafana 變數格式: var-<name>=<value>
            query_params.append(f"var-{key}={value}")

        # 添加 Panel 聚焦
        if panel_id is not None:
            query_params.append(f"viewPanel={panel_id}")

        # 組合 URL
        query_string = "&".join(query_params)
        full_url = urljoin(base_url, f"{dashboard_path}?{query_string}")

        return {
            "uid": uid,
            "url": full_url,
            "from_time": from_time,
            "to_time": to_time,
            "variables": variables,
            "panel_id": panel_id,
        }

    async def health_check(self) -> bool:
        """
        健康檢查

        驗證 Grafana API 可連線
        """
        try:
            client = await self._get_client()
            response = await client.get("/api/health")
            return response.status_code == 200
        except Exception as e:
            logger.warning("grafana_health_check_failed", error=str(e))
            return False

    async def close(self) -> None:
        """關閉 HTTP Client"""
        if self._client:
            await self._client.aclose()
            self._client = None
