"""
llm_smart_router.providers.base
-------------------------------

Provider 抽象基礎類別

定義所有供應商適配器必須實作的介面與共用邏輯,
包含模型查詢、關鍵字提取、版本排序等核心功能。
"""

from abc import ABC, abstractmethod
from typing import List, Optional, Dict
import difflib
import re
import httpx


class BaseProvider(ABC):
    """
    供應商適配器抽象基礎類別
    
    所有供應商適配器 (如 AnthropicProvider, GoogleProvider) 都必須繼承此類別,
    並實作抽象方法: list_models(), _extract_family(), _select_latest()
    
    Attributes:
        client: httpx.AsyncClient 實例,用於查詢供應商 API
        TIER_KEYWORDS: 模型階層關鍵字字典,用於家族識別
    """
    
    # 模型階層關鍵字字典 (可由子類別覆寫)
    TIER_KEYWORDS = {
        "fast": ["haiku", "flash", "lite", "mini", "nano", "turbo", "small"],
        "balanced": ["sonnet", "pro", "plus", "medium"],
        "powerful": ["opus", "max", "ultra", "large"],
        "code": ["code", "coder", "codestral", "devstral"]
    }
    
    def __init__(self):
        """初始化 Provider"""
        self.client = httpx.AsyncClient(timeout=30.0)
    
    @abstractmethod
    async def list_models(self) -> List[str]:
        """
        查詢供應商的可用模型清單
        
        此方法必須由子類別實作,根據各供應商的 API 規範查詢模型清單。
        
        Returns:
            模型名稱列表 (如 ["claude-4-5-haiku", "claude-4-5-sonnet"])
        
        Raises:
            ProviderAPIError: 當 API 查詢失敗時拋出
        """
        pass
    
    async def find_replacement(self, failed_model: str) -> Optional[str]:
        """
        為失效模型尋找替代方案
        
        執行流程:
        1. 提取失效模型的家族特徵 (如 "haiku", "claude")
        2. 查詢供應商的可用模型清單
        3. 策略 1: 關鍵字匹配 - 過濾包含相同關鍵字的模型
        4. 策略 2: 編輯距離備選 - 若策略 1 無結果,使用 difflib 找相似模型
        5. 版本排序 - 從候選模型中選擇最新穩定版本
        
        Args:
            failed_model: 失效的模型名稱
        
        Returns:
            替代模型名稱,若無法找到則返回 None
        """
        # 1. 提取家族特徵
        family = self._extract_family(failed_model)
        
        # 2. 查詢可用模型
        available_models = await self.list_models()
        
        # 3. 策略 1: 關鍵字匹配
        keywords = self._extract_keywords(failed_model, family)
        candidates = [
            m for m in available_models 
            if all(kw in m.lower() for kw in keywords)
        ]
        
        # 4. 策略 2: 編輯距離備選 (若策略 1 無結果)
        if not candidates:
            candidates = difflib.get_close_matches(
                failed_model, 
                available_models, 
                n=10,  # 取前 10 個最相似的
                cutoff=0.5  # 相似度閾值 50%
            )
        
        # 5. 版本排序並選擇最新版本
        if candidates:
            return self._select_latest(candidates)
        
        return None
    
    def _extract_keywords(self, model_name: str, family: Dict) -> List[str]:
        """
        提取模型的關鍵字用於匹配
        
        結合基礎名稱 (如 "claude") 與階層關鍵字 (如 "haiku")
        
        Args:
            model_name: 模型名稱
            family: _extract_family() 返回的家族資訊字典
        
        Returns:
            關鍵字列表 (如 ["claude", "haiku"])
        """
        keywords = []
        
        # 加入基礎名稱
        if family.get("base"):
            keywords.append(family["base"])
        
        # 加入階層關鍵字
        if family.get("tier") and family["tier"] != "unknown":
            keywords.append(family["tier"])
        
        return keywords
    
    @abstractmethod
    def _extract_family(self, model_name: str) -> Dict[str, str]:
        """
        提取模型的家族特徵
        
        此方法必須由子類別實作,根據各供應商的命名規範提取特徵。
        
        Args:
            model_name: 模型名稱
        
        Returns:
            家族特徵字典,至少包含:
            - "base": 基礎名稱 (如 "claude", "gemini")
            - "tier": 階層名稱 (如 "haiku", "flash", "mini")
            
        Example:
            >>> provider._extract_family("claude-3-5-haiku")
            {"base": "claude", "tier": "haiku"}
        """
        pass
    
    @abstractmethod
    def _select_latest(self, candidates: List[str]) -> str:
        """
        從候選模型中選擇最新版本
        
        此方法必須由子類別實作,根據各供應商的版本規範進行排序。
        通常考慮因素:
        1. 版本號 (如 4-5 > 3-5)
        2. 穩定性標籤 (stable > latest > preview)
        3. 日期戳記 (20250101 > 20241201)
        
        Args:
            candidates: 候選模型列表
        
        Returns:
            最新版本的模型名稱
        """
        pass
    
    def _extract_version_tuple(self, model_name: str) -> tuple:
        """
        提取版本號為可比較的 tuple
        
        輔助方法,用於版本排序。支援多種格式:
        - "claude-4-5-haiku" -> (4, 5)
        - "gpt-5.2-pro" -> (5, 2)
        - "gemini-2.5-flash" -> (2, 5)
        
        Args:
            model_name: 模型名稱
        
        Returns:
            版本號 tuple,無法解析時返回 (0, 0)
        """
        # 嘗試匹配 "數字-數字" 或 "數字.數字" 格式
        patterns = [
            r'(\d+)[-.](\d+)',  # 如 "4-5" 或 "5.2"
            r'(\d+)',           # 僅一個數字
        ]
        
        for pattern in patterns:
            match = re.search(pattern, model_name)
            if match:
                groups = match.groups()
                if len(groups) == 2:
                    return (int(groups[0]), int(groups[1]))
                elif len(groups) == 1:
                    return (int(groups[0]), 0)
        
        return (0, 0)
    
    async def close(self):
        """關閉 HTTP client"""
        await self.client.aclose()
