"""
AI Router - Phase 13.3 #87
==========================
智能 AI 路由器，根據意圖和複雜度動態選擇 AI Provider

目標: 根據請求特性自動選擇最適模型
策略: Intent Classifier + Complexity Scorer → Routing Decision
延遲目標: < 50ms (規則引擎優先)

路由決策矩陣 (ADR-023):
┌─────────────────┬───────────────┬──────────────────────────────┐
│ 複雜度 + 風險    │ Provider      │ 備註                          │
├─────────────────┼───────────────┼──────────────────────────────┤
│ 1-2 + LOW       │ Ollama        │ 快速本地處理                  │
│ 3 + MEDIUM      │ Ollama        │ fallback → Gemini            │
│ 4-5 + HIGH      │ Gemini        │ fallback → Claude            │
│ DELETE/CRITICAL │ Claude        │ 強制使用最強模型              │
└─────────────────┴───────────────┴──────────────────────────────┘

版本: v3.0
建立: 2026-03-26 (台北時區)
建立者: Claude Code
最後修改: 2026-03-26 (台北時區)
修改者: Claude Code

變更紀錄:
| 版本 | 日期 | 執行者 | 變更內容 |
|------|------|--------|----------|
| v1.0 | 2026-03-26 | Claude Code | 初始實作 |
| v2.0 | 2026-03-26 | Claude Code | 支援 IntentResult + 新意圖類型 |
| v3.0 | 2026-03-26 | Claude Code | Phase 13.3 #87 完整路由決策矩陣 |
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Protocol

import structlog

if TYPE_CHECKING:
    from src.services.intent_classifier import IntentResult

from src.services.complexity_scorer import (
    ComplexityScore,
    get_complexity_scorer,
)
from src.services.intent_classifier import (
    IntentResult,
    IntentType,
    RiskLevel,
    get_intent_classifier,
    normalize_intent,
)
from src.services.model_registry import get_model_registry

logger = structlog.get_logger(__name__)


# =============================================================================
# Provider 定義
# =============================================================================


class AIProvider(Enum):
    """AI 提供者"""

    OLLAMA = "ollama"
    GEMINI = "gemini"
    CLAUDE = "claude"
    # 2026-03-29 ogt: ADR-036 Nemotron Tool Calling (83.3% 精準度)
    NVIDIA = "nvidia"


# Provider 對應延遲預算 (ms)
PROVIDER_LATENCY_BUDGET: dict[AIProvider, int] = {
    AIProvider.OLLAMA: 60000,  # 本地，允許較長處理時間
    AIProvider.GEMINI: 30000,  # 雲端，較低延遲
    AIProvider.CLAUDE: 30000,  # 雲端，較低延遲
    # 2026-03-29 ogt: ADR-036 Nemotron Tool Calling (延遲 11-45s)
    AIProvider.NVIDIA: 60000,  # Tool Calling 專用，允許較長時間
}


# =============================================================================
# Interface 定義 (P1 修復 - 2026-04-01 首席架構師審查)
# =============================================================================


class IAIRouter(Protocol):
    """
    AI Router Protocol - 支援 DI 測試替換

    2026-04-01 ogt: 首席架構師審查 P1 修復
    - 新增 Protocol 定義支援依賴注入
    - 參考: IModelRegistry, IComplexityScorer
    """

    async def route(
        self,
        text: str,
        context: dict | None = None,
    ) -> "RoutingDecision":
        """路由請求到最適 AI Provider"""
        ...

    def route_sync(
        self,
        text: str,
        context: dict | None = None,
    ) -> "RoutingDecision":
        """同步版本路由"""
        ...

    def route_tool_calling(
        self,
    ) -> tuple[AIProvider, str, list[tuple[AIProvider, str]]]:
        """Tool Calling 專用路由"""
        ...


@dataclass
class RoutingDecision:
    """
    路由決策結果 (Phase 13.3 #87)

    包含完整的路由資訊，供 OpenClaw 主流程使用
    """

    # 核心決策
    selected_provider: AIProvider  # 選擇的 AI Provider
    selected_model: str  # 選擇的模型名稱
    fallback_chain: list[tuple[AIProvider, str]]  # 備援鏈 [(provider, model), ...]
    routing_reason: str  # 路由決策原因
    latency_budget_ms: int  # 延遲預算 (毫秒)

    # 分類結果
    intent: IntentType  # 意圖分類 (正規化後)
    intent_result: IntentResult  # 完整 Intent 分類結果
    complexity: ComplexityScore  # 複雜度評分
    risk_level: RiskLevel = field(default=RiskLevel.MEDIUM)  # 風險等級

    # 路由 metadata
    routing_latency_ms: float = 0.0  # 路由決策耗時 (ms)

    # 向後相容 (deprecated)
    model: str = ""  # -> selected_model
    reason: str = ""  # -> routing_reason
    fallback_models: list[str] = field(default_factory=list)  # -> fallback_chain

    def __post_init__(self):
        """初始化後設定衍生欄位"""
        self.risk_level = self.intent_result.risk_level
        # 向後相容
        self.model = self.selected_model
        self.reason = self.routing_reason
        self.fallback_models = [model for _, model in self.fallback_chain]

    def to_dict(self) -> dict:
        """轉換為字典 (API 回應用)"""
        return {
            "selected_provider": self.selected_provider.value,
            "selected_model": self.selected_model,
            "fallback_chain": [
                {"provider": p.value, "model": m} for p, m in self.fallback_chain
            ],
            "routing_reason": self.routing_reason,
            "latency_budget_ms": self.latency_budget_ms,
            "intent": self.intent.value,
            "risk_level": self.risk_level.value,
            "complexity_score": self.complexity.score,
            "routing_latency_ms": round(self.routing_latency_ms, 2),
        }


class AIRouter:
    """
    AI 路由器 (Phase 13.3 #87)

    整合 IntentClassifier 和 ComplexityScorer，
    動態選擇最適合的 AI Provider 和模型。

    路由決策矩陣:
    ┌─────────────────┬───────────────┬──────────────────────────────┐
    │ 複雜度 + 風險    │ Provider      │ 備註                          │
    ├─────────────────┼───────────────┼──────────────────────────────┤
    │ 1-2 + LOW       │ Ollama        │ 快速本地處理                  │
    │ 3 + MEDIUM      │ Ollama        │ fallback → Gemini            │
    │ 4-5 + HIGH      │ Gemini        │ fallback → Claude            │
    │ DELETE/CRITICAL │ Claude        │ 強制使用最強模型              │
    └─────────────────┴───────────────┴──────────────────────────────┘

    路由策略 (按優先級):
    1. CRITICAL 風險強制使用 Claude
    2. DELETE 意圖強制使用 Claude
    3. HIGH 風險或複雜度 4-5 → Gemini
    4. 其他情況 → Ollama (成本優先)
    """

    def __init__(self):
        self._intent_classifier = get_intent_classifier()
        self._complexity_scorer = get_complexity_scorer()
        self._model_registry = get_model_registry()

        # 從 ModelRegistry 取得模型配置
        self._ollama_default = self._model_registry.get_model("ollama", "default")
        self._ollama_summary = self._model_registry.get_model("ollama", "summary")
        self._gemini_default = self._model_registry.get_model("gemini", "default")
        self._claude_default = self._model_registry.get_model("claude", "default")
        # 2026-03-29 ogt: ADR-036 Nemotron Tool Calling
        self._nvidia_default = self._model_registry.get_model("nvidia", "default")

        # Provider 對應模型映射
        self._provider_models: dict[AIProvider, str] = {
            AIProvider.OLLAMA: self._ollama_default,
            AIProvider.GEMINI: self._gemini_default,
            AIProvider.CLAUDE: self._claude_default,
            AIProvider.NVIDIA: self._nvidia_default,  # ADR-036
        }

        # 完整 Fallback 鏈 (Provider, Model)
        # 2026-03-30 ogt: NVIDIA 成為首選仲裁，加入 Fallback 鏈首位
        self._full_fallback_chain: list[tuple[AIProvider, str]] = [
            (AIProvider.NVIDIA, self._nvidia_default),
            (AIProvider.GEMINI, self._gemini_default),
            (AIProvider.CLAUDE, self._claude_default),
            (AIProvider.OLLAMA, self._ollama_default),
        ]

        # Tool Calling 專用 Fallback 鏈 (ADR-036)
        self._tool_calling_fallback_chain: list[tuple[AIProvider, str]] = [
            (AIProvider.NVIDIA, self._nvidia_default),
            (AIProvider.GEMINI, self._gemini_default),
            (AIProvider.CLAUDE, self._claude_default),
        ]

        # 意圖對應 Provider 強制覆寫 (None = 依複雜度決定)
        self._intent_provider_overrides: dict[IntentType, AIProvider | None] = {
            # 四大核心意圖
            IntentType.RESTART: None,  # 依複雜度
            IntentType.SCALE: None,  # 依複雜度
            IntentType.CONFIG: None,  # 依複雜度 (但 HIGH 會升級)
            IntentType.DIAGNOSE: AIProvider.OLLAMA,  # 診斷優先本地 (隱私)
            # 輔助意圖
            IntentType.DELETE: AIProvider.CLAUDE,  # CRITICAL → 強制 Claude
            IntentType.ROLLBACK: None,  # 依複雜度
            IntentType.UNKNOWN: None,
            # 舊版兼容
            IntentType.CODE_REVIEW: None,
            IntentType.DEPLOYMENT: None,
            IntentType.ALERT_TRIAGE: AIProvider.OLLAMA,
            IntentType.QUERY: AIProvider.OLLAMA,
            IntentType.MAINTENANCE: None,
        }

        # 向後相容
        self._default_model = self._ollama_default
        self._summary_model = self._ollama_summary
        self._fallback_order = [
            self._ollama_default,
            self._ollama_summary,
            "gemini",
            "claude",
        ]

    async def route(
        self,
        text: str,
        context: dict | None = None,
    ) -> RoutingDecision:
        """
        路由請求到最適 AI Provider 和模型

        延遲目標: < 50ms (規則引擎優先，LLM 分類時可能稍長)

        Args:
            text: 用戶輸入或告警內容
            context: 額外上下文 (服務、指標等)

        Returns:
            RoutingDecision: 完整路由決策
        """
        start_time = time.perf_counter()
        context = context or {}

        # Step 1: 意圖分類 (返回 IntentResult, 規則引擎 < 10ms)
        intent_result = await self._intent_classifier.classify(text)
        intent = normalize_intent(intent_result.intent)

        # Step 2: 複雜度評分 (< 10ms)
        complexity = self._complexity_scorer.score(context)

        # Step 3: Provider + Model 選擇 (< 1ms)
        provider, model, reason = self._select_provider_and_model(
            intent, intent_result, complexity
        )

        # Step 4: 建立 Fallback 鏈
        fallback_chain = self._build_fallback_chain(provider)

        # Step 5: 計算延遲預算
        latency_budget = PROVIDER_LATENCY_BUDGET.get(provider, 30000)

        # 計算路由決策耗時
        routing_latency = (time.perf_counter() - start_time) * 1000

        decision = RoutingDecision(
            selected_provider=provider,
            selected_model=model,
            fallback_chain=fallback_chain,
            routing_reason=reason,
            latency_budget_ms=latency_budget,
            intent=intent,
            intent_result=intent_result,
            complexity=complexity,
            routing_latency_ms=routing_latency,
        )

        logger.info(
            "ai_routing_decision",
            provider=provider.value,
            model=model,
            intent=intent.value,
            intent_confidence=intent_result.confidence,
            risk_level=intent_result.risk_level.value,
            complexity_score=complexity.score,
            reason=reason,
            latency_budget_ms=latency_budget,
            routing_latency_ms=round(routing_latency, 2),
            fallback_count=len(fallback_chain),
        )

        return decision

    def _select_provider_and_model(
        self,
        intent: IntentType,
        intent_result: IntentResult,
        complexity: ComplexityScore,
    ) -> tuple[AIProvider, str, str]:
        """
        選擇 Provider 和模型 (Phase 13.3 #87 核心邏輯)

        路由決策矩陣:
        ┌─────────────────┬───────────────┬──────────────────────────────┐
        │ 複雜度 + 風險    │ Provider      │ 備註                          │
        ├─────────────────┼───────────────┼──────────────────────────────┤
        │ 1-2 + LOW       │ Ollama        │ 快速本地處理                  │
        │ 3 + MEDIUM      │ Ollama        │ fallback → Gemini            │
        │ 4-5 + HIGH      │ Gemini        │ fallback → Claude            │
        │ DELETE/CRITICAL │ Claude        │ 強制使用最強模型              │
        └─────────────────┴───────────────┴──────────────────────────────┘

        Args:
            intent: 正規化後的意圖
            intent_result: 完整分類結果
            complexity: 複雜度評分

        Returns:
            (provider, model, reason)
        """
        risk = intent_result.risk_level
        score = complexity.score

        # =======================================================================
        # 規則 1: CRITICAL 風險強制 Claude (最高優先級)
        # =======================================================================
        if risk == RiskLevel.CRITICAL:
            provider = AIProvider.CLAUDE
            model = self._claude_default
            reason = f"CRITICAL 風險 ({intent.value}) 強制使用 Claude"
            return provider, model, reason

        # =======================================================================
        # 規則 2: DELETE 意圖強制 Claude (不可逆操作)
        # =======================================================================
        if intent == IntentType.DELETE:
            provider = AIProvider.CLAUDE
            model = self._claude_default
            reason = "DELETE 意圖 (不可逆) 強制使用 Claude"
            return provider, model, reason

        # =======================================================================
        # 規則 3: 檢查意圖強制覆寫
        # =======================================================================
        provider_override = self._intent_provider_overrides.get(intent)
        if provider_override is not None:
            provider = provider_override
            model = self._provider_models[provider]
            reason = f"意圖 {intent.value} 指定使用 {provider.value}"
            return provider, model, reason

        # =======================================================================
        # 規則 4: 複雜度 4-5 或 HIGH 風險 → Nvidia Nemotron
        # =======================================================================
        if score >= 4 or risk == RiskLevel.HIGH:
            provider = AIProvider.NVIDIA
            model = self._nvidia_default
            reason = f"複雜度={score}/5, 風險={risk.value} → Nvidia (fallback Gemini)"
            return provider, model, reason

        # =======================================================================
        # 規則 5: 複雜度 3 + MEDIUM → Ollama (fallback Gemini)
        # =======================================================================
        if score == 3:
            provider = AIProvider.OLLAMA
            model = self._ollama_default
            reason = f"複雜度={score}/5, 風險={risk.value} → Ollama (fallback Gemini)"
            return provider, model, reason

        # =======================================================================
        # 規則 6: 複雜度 1-2 + LOW/MEDIUM → Ollama (快速本地處理)
        # =======================================================================
        provider = AIProvider.OLLAMA
        # 低複雜度使用輕量模型 (更快回應)
        model = self._ollama_summary if score <= 1 else self._ollama_default
        reason = f"複雜度={score}/5, 風險={risk.value} → Ollama (成本優先)"
        return provider, model, reason

    def _select_model(
        self,
        intent: IntentType,
        intent_result: IntentResult,
        complexity: ComplexityScore,
    ) -> tuple[str, str]:
        """
        選擇模型 (向後相容方法)

        Deprecated: 請使用 _select_provider_and_model

        Args:
            intent: 正規化後的意圖
            intent_result: 完整分類結果
            complexity: 複雜度評分

        Returns:
            (model_name, reason)
        """
        _, model, reason = self._select_provider_and_model(
            intent, intent_result, complexity
        )
        return model, reason

    def _build_fallback_chain(
        self, selected_provider: AIProvider
    ) -> list[tuple[AIProvider, str]]:
        """
        建立 Fallback 鏈 (排除已選 Provider)

        Fallback 順序: Ollama → Gemini → Claude

        Args:
            selected_provider: 已選擇的 Provider

        Returns:
            Fallback 鏈 [(provider, model), ...]
        """
        fallback_chain: list[tuple[AIProvider, str]] = []

        for provider, model in self._full_fallback_chain:
            if provider != selected_provider:
                fallback_chain.append((provider, model))

        return fallback_chain

    def _build_fallback_list(self, selected_model: str) -> list[str]:
        """建立 Fallback 列表 (向後相容)"""
        fallbacks = [m for m in self._fallback_order if m != selected_model]
        return fallbacks

    def route_sync(
        self,
        text: str,
        context: dict | None = None,
    ) -> RoutingDecision:
        """
        同步版本路由 (僅關鍵字匹配，保證 < 50ms)

        適用場景: 需要快速決策，不需要 LLM 分類的情況

        Args:
            text: 用戶輸入或告警內容
            context: 額外上下文

        Returns:
            RoutingDecision: 路由決策
        """
        start_time = time.perf_counter()
        context = context or {}

        # 同步分類 (僅規則引擎, < 10ms)
        intent_result = self._intent_classifier.classify_sync(text)
        intent = normalize_intent(intent_result.intent)

        # 複雜度評分 (< 10ms)
        complexity = self._complexity_scorer.score(context)

        # Provider + Model 選擇
        provider, model, reason = self._select_provider_and_model(
            intent, intent_result, complexity
        )

        # 建立 Fallback 鏈
        fallback_chain = self._build_fallback_chain(provider)

        # 延遲預算
        latency_budget = PROVIDER_LATENCY_BUDGET.get(provider, 30000)

        # 計算路由決策耗時
        routing_latency = (time.perf_counter() - start_time) * 1000

        return RoutingDecision(
            selected_provider=provider,
            selected_model=model,
            fallback_chain=fallback_chain,
            routing_reason=reason,
            latency_budget_ms=latency_budget,
            intent=intent,
            intent_result=intent_result,
            complexity=complexity,
            routing_latency_ms=routing_latency,
        )

    # =========================================================================
    # Tool Calling 路由 (ADR-036)
    # =========================================================================

    def route_tool_calling(self) -> tuple[AIProvider, str, list[tuple[AIProvider, str]]]:
        """
        Tool Calling 專用路由 (ADR-036)

        Tool Calling 任務優先使用 Nemotron (83.3% 精準度)，
        Fallback 到 Gemini/Claude。

        Returns:
            (provider, model, fallback_chain)
        """
        provider = AIProvider.NVIDIA
        model = self._nvidia_default
        fallback_chain = [
            (p, m) for p, m in self._tool_calling_fallback_chain if p != provider
        ]

        logger.info(
            "tool_calling_routing",
            provider=provider.value,
            model=model,
            fallback_count=len(fallback_chain),
        )

        return provider, model, fallback_chain

    def get_tool_calling_fallback_chain(self) -> list[tuple[AIProvider, str]]:
        """取得 Tool Calling Fallback 鏈"""
        return self._tool_calling_fallback_chain.copy()

    # =========================================================================
    # 便捷方法
    # =========================================================================

    def get_provider_for_intent(self, intent: IntentType) -> AIProvider:
        """取得意圖對應的 Provider (不考慮複雜度)"""
        override = self._intent_provider_overrides.get(intent)
        return override if override else AIProvider.OLLAMA

    def get_model_for_provider(self, provider: AIProvider) -> str:
        """取得 Provider 對應的模型"""
        return self._provider_models.get(provider, self._ollama_default)

    def get_routing_matrix(self) -> list[dict]:
        """
        取得路由決策矩陣 (用於 API 文檔或除錯)

        Returns:
            路由規則清單
        """
        return [
            {
                "rule": 1,
                "condition": "CRITICAL risk",
                "provider": "claude",
                "reason": "不可逆/高風險操作強制最強模型",
            },
            {
                "rule": 2,
                "condition": "DELETE intent",
                "provider": "claude",
                "reason": "刪除操作強制最強模型",
            },
            {
                "rule": 3,
                "condition": "Intent override",
                "provider": "depends",
                "reason": "特定意圖有預設 Provider",
            },
            {
                "rule": 4,
                "condition": "complexity >= 4 OR HIGH risk",
                "provider": "nvidia",
                "reason": "高複雜度需要 Nvidia Nemotron 強大推理能力",
            },
            {
                "rule": 5,
                "condition": "complexity == 3",
                "provider": "ollama",
                "reason": "中等複雜度本地處理",
            },
            {
                "rule": 6,
                "condition": "complexity 1-2",
                "provider": "ollama",
                "reason": "低複雜度快速處理",
            },
        ]


# 單例
_router: AIRouter | None = None


def get_ai_router() -> AIRouter:
    """取得 AIRouter 單例"""
    global _router
    if _router is None:
        _router = AIRouter()
    return _router


def reset_ai_router() -> None:
    """重置單例 (用於測試)"""
    global _router
    _router = None
