"""
配置设置模块
使用 Pydantic 进行配置验证和管理
支持配置分组、验证和热重载
"""
import os
from pathlib import Path
from typing import Optional, Literal, Any, Dict
from datetime import datetime
from threading import Lock

from pydantic import BaseModel, Field, field_validator, model_validator, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
from loguru import logger


# ============================================================
# 配置分组模型
# ============================================================

class PathConfig(BaseModel):
    """路径配置"""
    base_dir: Path = Field(default_factory=lambda: Path(__file__).parent.parent.parent)
    storage_root: Optional[Path] = Field(default=None)
    vector_storage_dir: Optional[Path] = Field(default=None)
    bm25_storage_dir: Optional[Path] = Field(default=None)
    knowledge_dir: Optional[Path] = Field(default=None)
    log_dir: Optional[Path] = Field(default=None)
    
    @model_validator(mode='after')
    def set_default_paths(self):
        """设置默认路径"""
        if self.storage_root is None:
            self.storage_root = self.base_dir / "storage"
        if self.vector_storage_dir is None:
            self.vector_storage_dir = self.storage_root / "vector" / "simple"
        if self.bm25_storage_dir is None:
            self.bm25_storage_dir = self.storage_root / "bm25"
        if self.knowledge_dir is None:
            self.knowledge_dir = self.base_dir / "my_knowledge_base"
        if self.log_dir is None:
            self.log_dir = self.base_dir / "logs"
        return self
    
    @property
    def log_file(self) -> Path:
        """日志文件路径"""
        # log_dir 在 set_default_paths 后不会是 None
        if self.log_dir is None:
            self.set_default_paths()
        return self.log_dir / "rag_system.log"  # type: ignore
    
    def ensure_directories(self):
        """确保必要的目录存在"""
        # 确保 set_default_paths 已运行
        if self.vector_storage_dir is None or self.bm25_storage_dir is None or self.log_dir is None:
            # 触发验证器
            self = self.model_validate(self.model_dump())
        
        self.vector_storage_dir.mkdir(parents=True, exist_ok=True)  # type: ignore
        self.bm25_storage_dir.mkdir(parents=True, exist_ok=True)  # type: ignore
        self.log_dir.mkdir(parents=True, exist_ok=True)  # type: ignore


class LLMConfig(BaseModel):
    """LLM 配置"""
    api_key: SecretStr = Field(default=SecretStr(""))
    base_url: str = Field(default="https://api.deepseek.com/v1")
    model: str = Field(default="deepseek-chat")
    temperature: float = Field(default=0.1, ge=0.0, le=2.0)
    max_tokens: int = Field(default=4096, ge=1)
    context_window: int = Field(default=32768, ge=1)
    
    @field_validator('temperature')
    @classmethod
    def validate_temperature(cls, v):
        if not 0.0 <= v <= 2.0:
            raise ValueError('温度参数必须在 0.0 到 2.0 之间')
        return v
    
    def get_api_key(self) -> str:
        """获取 API Key 的实际值"""
        return self.api_key.get_secret_value()


class EmbeddingConfig(BaseModel):
    """Embedding 配置"""
    api_key: SecretStr = Field(default=SecretStr(""))
    base_url: str = Field(default="https://api.siliconflow.cn/v1")
    model: str = Field(default="Qwen/Qwen3-Embedding-8B")
    batch_size: int = Field(default=10, ge=1, le=100)
    
    def get_api_key(self) -> str:
        """获取 API Key 的实际值"""
        return self.api_key.get_secret_value()


class RetrievalConfig(BaseModel):
    """检索配置（增强版）"""
    # 基础配置
    mode: Literal["vector", "bm25", "hybrid"] = Field(default="hybrid")
    top_k: int = Field(default=5, ge=1, le=100)
    bm25_top_k: int = Field(default=5, ge=1, le=100)
    use_bm25: bool = Field(default=True)
    use_hybrid: bool = Field(default=True)
    bm25_language: Literal["chinese", "english"] = Field(default="chinese")
    chunk_size: int = Field(default=1024, ge=100, le=10000)
    chunk_overlap: int = Field(default=200, ge=0, le=1000)
    
    # Postprocessor配置 - 相似度过滤
    use_similarity_filter: bool = Field(default=False)
    similarity_cutoff: float = Field(default=0.3, ge=0.0, le=1.0)
    
    # Postprocessor配置 - 多样性优化
    use_optimizer: bool = Field(default=False)
    optimizer_percentile: float = Field(default=0.5, ge=0.0, le=1.0)
    optimizer_threshold: float = Field(default=0.7, ge=0.0, le=1.0)
    
    # Reranker配置
    use_reranker: bool = Field(default=False)
    reranker_type: Literal["cohere", "sentence-transformer", "flag-embedding"] = Field(default="sentence-transformer")
    reranker_model: str = Field(default="cross-encoder/ms-marco-MiniLM-L-2-v2")
    reranker_top_n: int = Field(default=5, ge=1, le=100)
    cohere_api_key: SecretStr = Field(default=SecretStr(""))
    
    # 元数据过滤
    enable_metadata_filters: bool = Field(default=True)
    
    @model_validator(mode='after')
    def validate_chunk_overlap(self):
        """验证分块重叠不超过分块大小"""
        if self.chunk_overlap >= self.chunk_size:
            raise ValueError('chunk_overlap 必须小于 chunk_size')
        return self
    
    def get_cohere_api_key(self) -> str:
        """获取 Cohere API Key 的实际值"""
        return self.cohere_api_key.get_secret_value()


class VectorStoreConfig(BaseModel):
    """向量存储配置"""
    type: Literal["simple", "qdrant"] = Field(default="qdrant")
    
    # Qdrant 云服务配置
    qdrant_url: str = Field(default="")
    qdrant_api_key: SecretStr = Field(default=SecretStr(""))
    qdrant_collection_name: str = Field(default="knowledge_base")
    
    # Qdrant 本地配置
    qdrant_host: str = Field(default="localhost")
    qdrant_port: int = Field(default=6333, ge=1, le=65535)
    
    @property
    def use_qdrant(self) -> bool:
        """是否使用 Qdrant"""
        return self.type == "qdrant"
    
    def get_api_key(self) -> str:
        """获取 API Key 的实际值"""
        return self.qdrant_api_key.get_secret_value()


class LogConfig(BaseModel):
    """日志配置"""
    level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(default="INFO")
    format: str = Field(default="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
    rotation: str = Field(default="10 MB")
    retention: str = Field(default="30 days")


class FileWatcherConfig(BaseModel):
    """文件监控配置"""
    enabled: bool = Field(default=True, description="是否启用文件监控")
    debounce_seconds: float = Field(default=2.0, ge=0.1, le=60.0, description="防抖时间（秒）")
    batch_size: int = Field(default=5, ge=1, le=100, description="批量处理文件数量")
    batch_timeout: float = Field(default=10.0, ge=1.0, le=300.0, description="批量处理超时时间（秒）")
    supported_extensions: list[str] = Field(
        default_factory=lambda: [".txt", ".md", ".pdf", ".docx"],
        description="支持的文件扩展名"
    )
    
    @property
    def supported_extensions_set(self) -> set[str]:
        """获取支持的文件扩展名集合"""
        return set(self.supported_extensions)


class SystemConfig(BaseModel):
    """系统配置"""
    system_prompt: str = Field(default="""你是一个AI助手，专门回答基于提供的知识库内容的问题。
请遵循以下规则：
1. 仅基于提供的上下文回答问题
2. 如果上下文中没有相关信息，明确告知用户
3. 回答要准确、简洁、有条理
4. 如果可能，引用具体的文档片段
""")


# ============================================================
# 主配置类
# ============================================================

class Settings(BaseSettings):
    """
    主配置类
    支持环境变量、.env 文件和运行时配置
    """
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        env_nested_delimiter="__",
        extra="ignore",
    )
    
    # ========== LLM 配置 ==========
    deepseek_api_key: str = Field(default="", alias="DEEPSEEK_API_KEY")
    deepseek_base_url: str = Field(default="https://api.deepseek.com/v1", alias="DEEPSEEK_BASE_URL")
    deepseek_model: str = Field(default="deepseek-chat", alias="DEEPSEEK_MODEL")
    llm_temperature: float = Field(default=0.1, alias="LLM_TEMPERATURE")
    
    # ========== Embedding 配置 ==========
    siliconflow_api_key: str = Field(default="", alias="SILICONFLOW_API_KEY")
    siliconflow_base_url: str = Field(default="https://api.siliconflow.cn/v1", alias="SILICONFLOW_BASE_URL")
    embedding_model: str = Field(default="Qwen/Qwen3-Embedding-8B", alias="EMBEDDING_MODEL")
    
    # ========== 检索配置 ==========
    retrieval_mode: Literal["vector", "bm25", "hybrid"] = Field(default="hybrid", alias="RETRIEVAL_MODE")
    default_top_k: int = Field(default=5, alias="DEFAULT_TOP_K")
    bm25_top_k: int = Field(default=5, alias="BM25_TOP_K")
    use_bm25: bool = Field(default=True, alias="USE_BM25")
    use_hybrid_retrieval: bool = Field(default=True, alias="USE_HYBRID_RETRIEVAL")
    bm25_language: Literal["chinese", "english"] = Field(default="chinese", alias="BM25_LANGUAGE")
    chunk_size: int = Field(default=1024, alias="CHUNK_SIZE")
    chunk_overlap: int = Field(default=200, alias="CHUNK_OVERLAP")
    
    # ========== Postprocessor 配置 ==========
    use_similarity_filter: bool = Field(default=False, alias="USE_SIMILARITY_FILTER")
    similarity_cutoff: float = Field(default=0.3, alias="SIMILARITY_CUTOFF")
    use_optimizer: bool = Field(default=False, alias="USE_OPTIMIZER")
    optimizer_percentile: float = Field(default=0.5, alias="OPTIMIZER_PERCENTILE")
    optimizer_threshold: float = Field(default=0.7, alias="OPTIMIZER_THRESHOLD")
    
    # ========== Reranker 配置 ==========
    use_reranker: bool = Field(default=False, alias="USE_RERANKER")
    reranker_type: Literal["cohere", "sentence-transformer", "flag-embedding"] = Field(default="sentence-transformer", alias="RERANKER_TYPE")
    reranker_model: str = Field(default="cross-encoder/ms-marco-MiniLM-L-2-v2", alias="RERANKER_MODEL")
    reranker_top_n: int = Field(default=5, alias="RERANKER_TOP_N")
    cohere_api_key: str = Field(default="", alias="COHERE_API_KEY")
    
    # ========== 元数据过滤配置 ==========
    enable_metadata_filters: bool = Field(default=True, alias="ENABLE_METADATA_FILTERS")
    
    # ========== 向量存储配置 ==========
    use_qdrant: bool = Field(default=True, alias="USE_QDRANT")
    qdrant_url: str = Field(default="", alias="QDRANT_URL")
    qdrant_api_key: str = Field(default="", alias="QDRANT_API_KEY")
    qdrant_collection_name: str = Field(default="knowledge_base", alias="QDRANT_COLLECTION_NAME")
    qdrant_host: str = Field(default="localhost", alias="QDRANT_HOST")
    qdrant_port: int = Field(default=6333, alias="QDRANT_PORT")
    
    # ========== 路径配置 ==========
    knowledge_base_dir: str = Field(default="", alias="KNOWLEDGE_BASE_DIR")
    storage_root_dir: str = Field(default="", alias="STORAGE_ROOT_DIR")
    log_dir: str = Field(default="", alias="LOG_DIR")

    # ========== API 服务配置 ==========
    api_host: str = Field(default="0.0.0.0", alias="API_HOST")
    api_port: int = Field(default=8000, alias="API_PORT")
    debug: bool = Field(default=False, alias="DEBUG")

    # ========== 文件监控配置 ==========
    file_watcher_enabled: bool = Field(default=True, alias="FILE_WATCHER_ENABLED")
    file_watcher_debounce_seconds: float = Field(default=2.0, alias="FILE_WATCHER_DEBOUNCE_SECONDS")
    file_watcher_batch_size: int = Field(default=5, alias="FILE_WATCHER_BATCH_SIZE")
    file_watcher_batch_timeout: float = Field(default=10.0, alias="FILE_WATCHER_BATCH_TIMEOUT")
    
    # ========== 日志配置 ==========
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(default="INFO", alias="LOG_LEVEL")
    
    def to_grouped_config(self) -> Dict[str, Any]:
        """将配置转换为分组结构"""
        base_dir = Path(__file__).parent.parent.parent
        
        # 从环境变量获取路径，如果为空则使用 None（让 PathConfig 使用默认值）
        knowledge_dir = Path(self.knowledge_base_dir) if self.knowledge_base_dir else None
        storage_root = Path(self.storage_root_dir) if self.storage_root_dir else None
        log_dir = Path(self.log_dir) if self.log_dir else None
        
        return {
            "paths": PathConfig(
                base_dir=base_dir,
                knowledge_dir=knowledge_dir,
                storage_root=storage_root,
                log_dir=log_dir
            ),
            "llm": LLMConfig(
                api_key=SecretStr(self.deepseek_api_key),
                base_url=self.deepseek_base_url,
                model=self.deepseek_model,
                temperature=self.llm_temperature,
            ),
            "embedding": EmbeddingConfig(
                api_key=SecretStr(self.siliconflow_api_key),
                base_url=self.siliconflow_base_url,
                model=self.embedding_model,
            ),
            "retrieval": RetrievalConfig(
                mode=self.retrieval_mode,
                top_k=self.default_top_k,
                bm25_top_k=self.bm25_top_k,
                use_bm25=self.use_bm25,
                use_hybrid=self.use_hybrid_retrieval,
                bm25_language=self.bm25_language,
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap,
                # Postprocessor配置
                use_similarity_filter=self.use_similarity_filter,
                similarity_cutoff=self.similarity_cutoff,
                use_optimizer=self.use_optimizer,
                optimizer_percentile=self.optimizer_percentile,
                optimizer_threshold=self.optimizer_threshold,
                # Reranker配置
                use_reranker=self.use_reranker,
                reranker_type=self.reranker_type,
                reranker_model=self.reranker_model,
                reranker_top_n=self.reranker_top_n,
                cohere_api_key=SecretStr(self.cohere_api_key),
                # 元数据过滤
                enable_metadata_filters=self.enable_metadata_filters,
            ),
            "vector_store": VectorStoreConfig(
                type="qdrant" if self.use_qdrant else "simple",
                qdrant_url=self.qdrant_url,
                qdrant_api_key=SecretStr(self.qdrant_api_key),
                qdrant_collection_name=self.qdrant_collection_name,
                qdrant_host=self.qdrant_host,
                qdrant_port=self.qdrant_port,
            ),
            "file_watcher": FileWatcherConfig(
                enabled=self.file_watcher_enabled,
                debounce_seconds=self.file_watcher_debounce_seconds,
                batch_size=self.file_watcher_batch_size,
                batch_timeout=self.file_watcher_batch_timeout,
            ),
            "log": LogConfig(level=self.log_level),
            "system": SystemConfig(),
        }


# ============================================================
# 配置管理器（支持热重载）
# ============================================================

class ConfigManager:
    """
    配置管理器
    支持配置热重载、验证和分组访问
    """
    
    _instance: Optional['ConfigManager'] = None
    _lock = Lock()
    
    def __new__(cls):
        """单例模式"""
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance
    
    def __init__(self):
        if self._initialized:
            return
        
        self._settings: Optional[Settings] = None
        self._grouped_config: Optional[Dict[str, Any]] = None
        self._last_load_time: Optional[datetime] = None
        self._config_lock = Lock()
        self._initialized = True
        
        # 初始加载配置
        self._load_config()
    
    def _load_config(self):
        """加载配置"""
        with self._config_lock:
            self._settings = Settings()
            self._grouped_config = self._settings.to_grouped_config()
            self._last_load_time = datetime.now()
            
            # 确保目录存在
            self._grouped_config["paths"].ensure_directories()
            
            logger.debug(f"配置加载完成: {self._last_load_time}")
    
    def reload(self):
        """重新加载配置（热重载）"""
        logger.info("开始热重载配置...")
        self._load_config()
        logger.info("配置热重载完成")
    
    @property
    def settings(self) -> Settings:
        """获取原始设置"""
        if self._settings is None:
            self._load_config()
        return self._settings  # type: ignore
    
    @property
    def paths(self) -> PathConfig:
        """获取路径配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["paths"]  # type: ignore
    
    @property
    def llm(self) -> LLMConfig:
        """获取 LLM 配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["llm"]  # type: ignore
    
    @property
    def embedding(self) -> EmbeddingConfig:
        """获取 Embedding 配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["embedding"]  # type: ignore
    
    @property
    def retrieval(self) -> RetrievalConfig:
        """获取检索配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["retrieval"]  # type: ignore
    
    @property
    def vector_store(self) -> VectorStoreConfig:
        """获取向量存储配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["vector_store"]  # type: ignore
    
    @property
    def log(self) -> LogConfig:
        """获取日志配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["log"]  # type: ignore
    
    @property
    def file_watcher(self) -> FileWatcherConfig:
        """获取文件监控配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["file_watcher"]  # type: ignore
    
    @property
    def system(self) -> SystemConfig:
        """获取系统配置"""
        if self._grouped_config is None:
            self._load_config()
        return self._grouped_config["system"]  # type: ignore
    
    # ============================================================
    # 方法
    # ============================================================
    
    def validate(self) -> list:
        """
        验证配置
        返回错误列表，空列表表示验证通过
        """
        errors = []
        
        # 确保设置已加载
        settings = self.settings
        
        # 验证 API Keys
        if not settings.deepseek_api_key:
            errors.append("DEEPSEEK_API_KEY 未设置")
        
        if not settings.siliconflow_api_key:
            errors.append("SILICONFLOW_API_KEY 未设置")
        
        # 验证 Qdrant 配置
        if settings.use_qdrant:
            # 检查是否有有效的 Qdrant 配置
            has_cloud_config = bool(settings.qdrant_url)
            has_local_config = bool(settings.qdrant_host and settings.qdrant_port)
            
            if not has_cloud_config and not has_local_config:
                errors.append("启用了 Qdrant 但未配置 QDRANT_URL 或 QDRANT_HOST/QDRANT_PORT")
        
        # 验证检索模式
        valid_modes = ["vector", "bm25", "hybrid"]
        if settings.retrieval_mode not in valid_modes:
            errors.append(f"无效的检索模式: {settings.retrieval_mode}，有效值: {valid_modes}")
        
        return errors
    
    def ensure_directories(self):
        """确保必要的目录存在"""
        self.paths.ensure_directories()
    
    def display(self, show_secrets: bool = False) -> Dict[str, Any]:
        """
        显示配置信息
        
        Args:
            show_secrets: 是否显示敏感信息
        """
        def mask_secret(value: str, show: bool = False) -> str:
            if show or not value:
                return value
            if len(value) <= 8:
                return "***"
            return value[:4] + "..." + value[-4:]
        
        return {
            "索引存储路径": str(self.paths.vector_storage_dir),
            "默认知识库目录": str(self.paths.knowledge_dir),
            "LLM 模型": self.llm.model,
            "LLM API": self.llm.base_url,
            "Embedding 模型": self.embedding.model,
            "Embedding API": self.embedding.base_url,
            "LLM 温度": self.llm.temperature,
            "检索模式": self.retrieval.mode,
            "检索文档数": self.retrieval.top_k,
            "BM25检索数": self.retrieval.bm25_top_k,
            "文本分块大小": self.retrieval.chunk_size,
            "分块重叠": self.retrieval.chunk_overlap,
            "向量存储类型": self.vector_store.type,
            "BM25语言": self.retrieval.bm25_language,
            "文件监控启用": self.file_watcher.enabled,
            "文件防抖时间": f"{self.file_watcher.debounce_seconds}秒",
            "文件批量大小": self.file_watcher.batch_size,
            "文件批量超时": f"{self.file_watcher.batch_timeout}秒",
            "日志级别": self.log.level,
            "Qdrant URL": mask_secret(self.vector_store.qdrant_url, show_secrets),
            "Qdrant 集合名": self.vector_store.qdrant_collection_name,
        }


# ============================================================
# 全局配置实例
# ============================================================

_config_manager: Optional[ConfigManager] = None
_config_lock = Lock()


def get_config_manager() -> ConfigManager:
    """获取配置管理器实例"""
    global _config_manager
    
    if _config_manager is None:
        with _config_lock:
            if _config_manager is None:
                _config_manager = ConfigManager()
    
    return _config_manager


def reload_config():
    """热重载配置"""
    get_config_manager().reload()


# ============================================================
# 便捷别名
# ============================================================

def get_config() -> ConfigManager:
    """获取配置实例"""
    return get_config_manager()
