"""
llm_smart_router.cache
----------------------

模型清單與替換映射快取模組

提供 TTL (Time To Live) 快取機制,用於儲存從供應商 API 查詢到的模型清單,
降低重複 API 呼叫的頻率,提升效能並避免觸及 rate limit。

同時提供持久化快取,用於儲存「失效模型 -> 替代模型」的映射關係,
解決應用程式重啟與多進程環境下的重複錯誤嘗試問題。
"""

import json
import time
from pathlib import Path
from typing import Optional, List, Dict, Any
from cachetools import TTLCache
import threading


class ModelCache:
    """
    模型清單快取管理器
    
    使用 TTLCache 實作具有過期時間的快取機制。
    每個供應商的模型清單獨立快取,預設 TTL 為 3600 秒 (1 小時)。
    
    Attributes:
        _cache: TTLCache 實例,key 為供應商名稱,value 為模型清單
        _lock: 執行緒鎖,確保快取操作的執行緒安全性
        default_ttl: 預設快取過期時間 (秒)
    """
    
    def __init__(self, max_size: int = 100, default_ttl: int = 3600):
        """
        初始化快取管理器
        
        Args:
            max_size: 快取最大容量 (預設 100 筆)
            default_ttl: 預設 TTL 時間,單位為秒 (預設 3600 秒 = 1 小時)
        """
        self._cache: TTLCache = TTLCache(maxsize=max_size, ttl=default_ttl)
        self._lock = threading.Lock()
        self.default_ttl = default_ttl
    
    def get(self, provider: str) -> Optional[List[str]]:
        """
        取得指定供應商的模型清單
        
        Args:
            provider: 供應商名稱 (如 "claude", "gemini")
        
        Returns:
            模型清單,若快取未命中或已過期則返回 None
        """
        with self._lock:
            return self._cache.get(provider)
    
    def set(self, provider: str, models: List[str]) -> None:
        """
        儲存供應商的模型清單至快取
        
        Args:
            provider: 供應商名稱
            models: 模型清單
        """
        with self._lock:
            self._cache[provider] = models
    
    def clear(self, provider: Optional[str] = None) -> None:
        """
        清除快取
        
        Args:
            provider: 若指定則僅清除該供應商的快取,否則清除所有快取
        """
        with self._lock:
            if provider:
                self._cache.pop(provider, None)
            else:
                self._cache.clear()
    
    def has(self, provider: str) -> bool:
        """
        檢查指定供應商是否有快取
        
        Args:
            provider: 供應商名稱
        
        Returns:
            True 表示快取存在且未過期,False 表示無快取或已過期
        """
        with self._lock:
            return provider in self._cache


class ReplacementCache:
    """
    模型替換映射持久化快取
    
    將「失效模型 -> 替代模型」的映射關係持久化到 JSON 檔案,
    解決應用程式重啟與多進程環境下的重複錯誤嘗試問題。
    
    Attributes:
        cache_path: 快取檔案路徑
        default_ttl: 預設快取過期時間 (秒)
        _memory_cache: 記憶體快取,避免頻繁讀取檔案
        _lock: 執行緒鎖
    """
    
    DEFAULT_CACHE_DIR = Path.home() / ".llm_smart_router"
    DEFAULT_CACHE_FILE = "replacement_cache.json"
    
    def __init__(
        self, 
        cache_path: Optional[str] = None, 
        default_ttl: int = 3600
    ):
        """
        初始化替換映射快取
        
        Args:
            cache_path: 快取檔案路徑,預設為 ~/.llm_smart_router/replacement_cache.json
            default_ttl: 預設 TTL 時間,單位為秒 (預設 3600 秒 = 1 小時)
        """
        if cache_path:
            self.cache_path = Path(cache_path)
        else:
            self.cache_path = self.DEFAULT_CACHE_DIR / self.DEFAULT_CACHE_FILE
        
        self.default_ttl = default_ttl
        self._memory_cache: Dict[str, Dict[str, Any]] = {}
        self._lock = threading.Lock()
        
        # 確保快取目錄存在
        self.cache_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 載入既有快取
        self._load_from_disk()
    
    def _load_from_disk(self) -> None:
        """從磁碟載入快取"""
        if self.cache_path.exists():
            try:
                with open(self.cache_path, "r", encoding="utf-8") as f:
                    self._memory_cache = json.load(f)
            except (json.JSONDecodeError, IOError):
                # 快取檔案損壞,重新建立
                self._memory_cache = {}
    
    def _save_to_disk(self) -> None:
        """儲存快取到磁碟"""
        try:
            with open(self.cache_path, "w", encoding="utf-8") as f:
                json.dump(self._memory_cache, f, indent=4, ensure_ascii=False)
        except IOError:
            # 無法寫入,僅記錄警告
            pass
    
    def _make_key(self, provider: str, failed_model: str) -> str:
        """生成快取鍵"""
        return f"{provider}:{failed_model}"
    
    def get(
        self, 
        provider: str, 
        failed_model: str, 
        ttl: Optional[int] = None
    ) -> Optional[str]:
        """
        取得替代模型
        
        Args:
            provider: 供應商名稱
            failed_model: 失效的模型名稱
            ttl: 自訂 TTL,若未指定則使用預設值
        
        Returns:
            替代模型名稱,若快取未命中或已過期則返回 None
        """
        effective_ttl = ttl if ttl is not None else self.default_ttl
        key = self._make_key(provider, failed_model)
        
        with self._lock:
            entry = self._memory_cache.get(key)
            if entry is None:
                return None
            
            # 檢查是否過期
            created_at = entry.get("created_at", 0)
            if time.time() - created_at > effective_ttl:
                # 已過期,移除並返回 None
                del self._memory_cache[key]
                self._save_to_disk()
                return None
            
            return entry.get("replacement")
    
    def set(
        self, 
        provider: str, 
        failed_model: str, 
        replacement: str
    ) -> None:
        """
        儲存替代模型映射
        
        Args:
            provider: 供應商名稱
            failed_model: 失效的模型名稱
            replacement: 替代模型名稱
        """
        key = self._make_key(provider, failed_model)
        
        with self._lock:
            self._memory_cache[key] = {
                "replacement": replacement,
                "created_at": time.time(),
                "provider": provider,
                "failed_model": failed_model
            }
            self._save_to_disk()
    
    def clear(self, provider: Optional[str] = None) -> None:
        """
        清除快取
        
        Args:
            provider: 若指定則僅清除該供應商的快取,否則清除所有快取
        """
        with self._lock:
            if provider:
                keys_to_remove = [
                    k for k in self._memory_cache 
                    if k.startswith(f"{provider}:")
                ]
                for k in keys_to_remove:
                    del self._memory_cache[k]
            else:
                self._memory_cache.clear()
            self._save_to_disk()
    
    def list_all(self) -> Dict[str, Dict[str, Any]]:
        """
        列出所有快取的替換映射
        
        Returns:
            所有快取項目的字典
        """
        with self._lock:
            return dict(self._memory_cache)


# 全域快取實例
_global_model_cache = ModelCache()
_global_replacement_cache = ReplacementCache()


def get_cache() -> ModelCache:
    """
    取得全域模型清單快取實例
    
    Returns:
        ModelCache 實例
    """
    return _global_model_cache


def get_replacement_cache() -> ReplacementCache:
    """
    取得全域替換映射快取實例
    
    Returns:
        ReplacementCache 實例
    """
    return _global_replacement_cache


def configure_cache(
    replacement_cache_path: Optional[str] = None,
    model_cache_ttl: int = 3600,
    replacement_cache_ttl: int = 3600
) -> None:
    """
    配置全域快取設定
    
    Args:
        replacement_cache_path: 替換快取檔案路徑
        model_cache_ttl: 模型清單快取 TTL (秒)
        replacement_cache_ttl: 替換映射快取 TTL (秒)
    """
    global _global_model_cache, _global_replacement_cache
    
    _global_model_cache = ModelCache(default_ttl=model_cache_ttl)
    _global_replacement_cache = ReplacementCache(
        cache_path=replacement_cache_path,
        default_ttl=replacement_cache_ttl
    )

