"""
Model Registry - Phase 12 P1 修復
=================================
集中管理 AI 模型配置，消除 hardcode 模型名稱

功能:
- 從 models.json 讀取配置
- 提供 get_model(provider, purpose) 方法
- Singleton 模式
- 支援依賴注入測試

版本: v1.0
建立: 2026-03-26 23:00 (台北時區)
建立者: Claude Code
最後修改: 2026-04-09 10:00 (台北時區) — ogt: fallback config 更新為 deepseek-r1:14b + gemma3:4b
修改者: Claude Code
"""

import json
from pathlib import Path
from typing import Protocol

import structlog

logger = structlog.get_logger(__name__)


# =============================================================================
# Interface (支援 DI 測試)
# =============================================================================


class IModelRegistry(Protocol):
    """Model Registry Interface for DI"""

    def get_model(self, provider: str, purpose: str = "default") -> str:
        """取得模型名稱"""
        ...

    def get_fallback_order(self) -> list[str]:
        """取得備援順序"""
        ...

    def get_model_by_complexity(self, complexity: int) -> str:
        """依複雜度取得推薦模型"""
        ...

    def get_provider_config(self, provider: str) -> dict:
        """取得 provider 完整配置"""
        ...


# =============================================================================
# Implementation
# =============================================================================


class ModelRegistry:
    """
    Model Registry 實作

    從 models.json 讀取配置，提供統一的模型查詢介面

    Usage:
        registry = get_model_registry()
        model = registry.get_model("ollama", "rca")  # -> "qwen2.5:7b-instruct"
    """

    def __init__(self, config_path: Path | str | None = None):
        """
        初始化 ModelRegistry

        Args:
            config_path: models.json 路徑，None 使用預設路徑
        """
        if config_path is None:
            # 預設路徑: apps/api/models.json
            config_path = Path(__file__).parent.parent.parent / "models.json"
        elif isinstance(config_path, str):
            config_path = Path(config_path)

        self._config_path = config_path
        self._config: dict = {}
        self._load_config()

        # 複雜度對應模型 (從 config 或使用預設)
        self._complexity_map = self._build_complexity_map()

    def _load_config(self) -> None:
        """載入 models.json"""
        try:
            with open(self._config_path) as f:
                self._config = json.load(f)
            logger.info(
                "model_registry_loaded",
                path=str(self._config_path),
                providers=list(self._config.get("providers", {}).keys()),
            )
        except FileNotFoundError:
            logger.warning(
                "models_json_not_found",
                path=str(self._config_path),
                using="fallback_defaults",
            )
            self._config = self._get_default_config()
        except json.JSONDecodeError as e:
            logger.error(
                "models_json_parse_error",
                path=str(self._config_path),
                error=str(e),
            )
            self._config = self._get_default_config()

    def _get_default_config(self) -> dict:
        """預設配置 (fallback)"""
        return {
            "default_provider": "ollama",
            "fallback_order": ["ollama", "gemini", "claude"],
            # 2026-03-29 ogt: P2-3 加入 Tool Calling Fallback (ADR-036)
            "tool_calling_fallback_order": ["nvidia", "gemini", "claude"],
            "providers": {
                # 2026-04-08 ogt: 切換到 M1 Pro (192.168.0.111)，deepseek-r1:14b + gemma3:4b
                "ollama": {
                    "models": {
                        "default": "deepseek-r1:14b",
                        "rca": "deepseek-r1:14b",
                        "summary": "gemma3:4b",
                    }
                },
                # 2026-03-28 ogt: 更新到 gemini-2.0-flash (1.5 已停用)
                "gemini": {
                    "models": {
                        "default": "gemini-2.0-flash",
                        "rca": "gemini-2.0-flash",
                        "summary": "gemini-2.0-flash",
                    }
                },
                "claude": {
                    "models": {
                        "default": "claude-haiku-4-5-20251001",
                        "rca": "claude-haiku-4-5-20251001",
                        "summary": "claude-haiku-4-5-20251001",
                    }
                },
                # 2026-03-29 ogt: P2-3 加入 NVIDIA (ADR-036)
                "nvidia": {
                    "models": {
                        "default": "nvidia/nemotron-mini-4b-instruct",
                        "tool_calling": "nvidia/nemotron-mini-4b-instruct",
                    }
                },
            },
        }

    def _build_complexity_map(self) -> dict[int, str]:
        """建立複雜度對應模型映射"""
        # 從 config 或使用預設
        ollama_models = self._config.get("providers", {}).get("ollama", {}).get("models", {})
        default_model = ollama_models.get("default", "qwen2.5:7b-instruct")
        summary_model = ollama_models.get("summary", "llama3.2:3b")

        return {
            1: summary_model,  # 簡單任務，快速回應
            2: default_model,  # 中等任務
            3: default_model,  # 複雜任務
            4: "gemini",  # 需要雲端能力
            5: "claude",  # 極複雜，需要最強模型
        }

    def get_model(self, provider: str, purpose: str = "default") -> str:
        """
        取得模型名稱

        Args:
            provider: 提供者 (ollama, gemini, claude)
            purpose: 用途 (default, rca, summary)

        Returns:
            模型名稱
        """
        providers = self._config.get("providers", {})
        provider_config = providers.get(provider, {})
        models = provider_config.get("models", {})

        # 優先取用途，fallback 到 default
        model = models.get(purpose) or models.get("default")

        if not model:
            # 最終 fallback
            # 2026-03-28 ogt: 更新 gemini-2.0-flash
            fallback_map = {
                "ollama": "qwen2.5:7b-instruct",
                "gemini": "gemini-2.0-flash",
                "claude": "claude-haiku-4-5-20251001",
            }
            model = fallback_map.get(provider, provider)
            logger.warning(
                "model_not_found_using_fallback",
                provider=provider,
                purpose=purpose,
                fallback=model,
            )

        return model

    def get_fallback_order(self) -> list[str]:
        """取得備援順序"""
        return self._config.get("fallback_order", ["ollama", "gemini", "claude"])

    def get_model_by_complexity(self, complexity: int) -> str:
        """
        依複雜度取得推薦模型

        Args:
            complexity: 複雜度分數 (1-5)

        Returns:
            推薦模型名稱
        """
        # 確保在範圍內
        complexity = max(1, min(5, complexity))
        return self._complexity_map.get(complexity, self.get_model("ollama", "default"))

    def get_provider_config(self, provider: str) -> dict:
        """取得 provider 完整配置"""
        return self._config.get("providers", {}).get(provider, {})

    def get_default_provider(self) -> str:
        """取得預設 provider"""
        return self._config.get("default_provider", "ollama")

    def get_provider_options(self, provider: str) -> dict:
        """取得 provider 的 options"""
        provider_config = self.get_provider_config(provider)
        return provider_config.get("options", {})

    def get_provider_timeout(self, provider: str) -> int:
        """取得 provider 的 timeout (秒)"""
        provider_config = self.get_provider_config(provider)
        return provider_config.get("timeout_seconds", 30)


# =============================================================================
# Singleton
# =============================================================================

_registry: ModelRegistry | None = None


def get_model_registry() -> ModelRegistry:
    """取得 ModelRegistry 單例"""
    global _registry
    if _registry is None:
        _registry = ModelRegistry()
    return _registry


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


# =============================================================================
# Convenience Functions
# =============================================================================


def get_model(provider: str, purpose: str = "default") -> str:
    """便捷函數: 取得模型名稱"""
    return get_model_registry().get_model(provider, purpose)


def get_model_by_complexity(complexity: int) -> str:
    """便捷函數: 依複雜度取得模型"""
    return get_model_registry().get_model_by_complexity(complexity)
