#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@time   : 2026/03/04
@file   : load_config.py
@author : nbyue
@desc   : 通用外部配置加载器（支持多种配置类型，获取第一个非空配置）
"""

import os
import sys
import json
from typing import Dict, Any, Optional, List
from pathlib import Path

try:
    import yaml

    HAS_YAML = True
except ImportError:
    HAS_YAML = False
    yaml = None

class ConfigLoader:
    """
    通用配置加载器
    支持从多个位置加载 YAML/JSON/CONF 格式的配置文件
    
    使用示例：
        # 方式 1: 直接指定文件路径
        loader = ConfigLoader.from_path("/path/to/config.yaml")
        
        # 方式 2: 自动检测（环境变量/工作目录/用户目录）
        loader = ConfigLoader.auto_detect()
        
        # 方式 3: 自定义搜索路径列表
        loader = ConfigLoader.from_paths([
            "/etc/myapp/config.yaml",
            "~/.myapp/config.json",
            "./config.yml"
        ])
        
        # 加载配置
        config = loader.load()
        if loader.has_config:
            db_config = loader.get_database_config(env="production")
    """

    # 支持的配置文件扩展名
    SUPPORTED_EXTENSIONS = {'.yaml', '.yml', '.json', '.conf'}

    def __init__(self, config_paths: Optional[List[str]] = None):
        """
        初始化配置加载器
        :param config_paths: 配置文件路径列表（按优先级排序）
        """
        self.config_paths = config_paths or []
        self._loaded_config: Optional[Dict[str, Any]] = None
        self._config_file: Optional[str] = None

    @classmethod
    def from_path(cls, config_path: str) -> 'ConfigLoader':
        """
        从单个文件路径创建加载器
        :param config_path: 配置文件路径
        :return: ConfigLoader 实例
        """
        return cls(config_paths=[config_path])

    @classmethod
    def from_paths(cls, config_paths: List[str]) -> 'ConfigLoader':
        """
        从多个文件路径创建加载器（按优先级）
        :param config_paths: 配置文件路径列表
        :return: ConfigLoader 实例
        """
        return cls(config_paths=config_paths)

    @classmethod
    def auto_detect(cls, config_name: str = "db") -> 'ConfigLoader':
        """
        自动检测配置文件位置
        检测顺序：
        1. 环境变量 DB_CONFIG
        2. 当前工作目录下的配置文件 {config_name}_config.yaml
        3. 用户主目录 ~/{config_name}_config.yaml
        4. 系统目录 /etc/{config_name}_config/config.yaml
        
        :param config_name: 配置名称（用于构建默认路径）
        :return: ConfigLoader 实例
        """
        search_paths = []

        # 1. 检查环境变量
        env_var_name = f"{config_name.upper()}_CONFIG"
        env_path = os.getenv(env_var_name)
        if env_path:
            search_paths.append(env_path)

        # 2. 检查工作目录
        cwd = Path.cwd()
        for ext in ['.yaml', '.yml', '.json', '.conf']:
            config_file = cwd / f"{config_name}_config{ext}"
            if config_file.exists():
                search_paths.append(str(config_file))

            # 也检查无应用前缀的通用文件名
            generic_file = cwd / f"config{ext}"
            if generic_file.exists():
                search_paths.append(str(generic_file))

        # 3. 检查用户主目录
        home_config = Path.home() / f"{config_name}_config.yaml"
        if home_config.exists():
            search_paths.append(str(home_config))

        # 4. 检查系统目录（Unix-like 系统）
        if sys.platform != 'win32':
            system_config = Path(f"/etc/{config_name}_config/config.yaml")
            if system_config.exists():
                search_paths.append(str(system_config))

        return cls(config_paths=search_paths)

    def load(self) -> Optional[Dict[str, Any]]:
        """
        加载配置文件（按优先级查找第一个存在的文件）
        :return: 配置字典，如果未找到则返回 None
        """
        if not self.config_paths:
            return None

        for config_path in self.config_paths:
            # print(f"正在加载配置文件：{config_path}")
            path = Path(config_path).expanduser()  # 展开 ~ 为用户目录

            if not path.exists():
                continue

            try:
                suffix = path.suffix.lower()

                with open(path, 'r', encoding='utf-8') as f:
                    content = f.read()

                if suffix in {'.yaml', '.yml', '.conf'}:
                    if not HAS_YAML:
                        raise ImportError(
                            "需要安装 PyYAML 来解析 YAML 配置文件。\n"
                            "请运行：pip install pyyaml"
                        )
                    self._loaded_config = yaml.safe_load(content)
                elif suffix == '.json':
                    self._loaded_config = json.loads(content)
                else:
                    continue  # 跳过不支持的格式

                # 如果文件内容为空，继续尝试下一个文件
                if self._loaded_config is None:
                    print(f"警告：配置文件 {config_path} 为空，继续尝试下一个文件")
                    continue

                self._config_file = str(path)
                return self._loaded_config

            except Exception as e:
                # 记录错误但继续尝试下一个文件
                print(f"警告：加载配置文件 {config_path} 失败：{e}")
                continue

        return None

    def load_required(self) -> Dict[str, Any]:
        """
        加载配置文件（必须存在）
        :return: 配置字典
        :raises FileNotFoundError: 如果未找到配置文件
        """
        config = self.load()
        if config is None:
            available_paths = "\n".join(f"  - {p}" for p in self.config_paths) if self.config_paths else "  - 无"
            raise FileNotFoundError(
                f"未检索到配置文件\n"
                f"已尝试以下路径:\n{available_paths}\n"
                f"提示：可以通过设置环境变量 {os.getenv('CONFIG_NAME', 'DB')}_CONFIG "
                f"指定配置文件路径，或将配置文件放置到以下位置:\n"
                f"  - 当前工作目录\n"
                f"  - 用户主目录 (~/db_config.yaml)\n"
                f"  - 系统目录 (/etc/db_config/config.yaml)"
            )
        return config

    def get_value(self, key_path: str, default: Any = None) -> Any:
        """
        获取配置值（支持嵌套键）
        :param key_path: 键路径，如 "database.ro.host"
        :param default: 默认值
        :return: 配置值
        """
        if not self._loaded_config:
            self.load()

        if not self._loaded_config:
            return default

        keys = key_path.split('.')
        value = self._loaded_config

        for key in keys:
            if isinstance(value, dict) and key in value:
                value = value[key]
            else:
                return default

        return value

    def get_database_config(
            self,
            env: Optional[str] = None,
            env_var_prefix: str = "DB",
            continue_if_missing: bool = False
    ) -> Dict[str, Any]:
        """
        获取数据库配置
        :param env: 环境标识 (如 ro/ops/rw)，默认从环境变量或配置文件获取
        :param env_var_prefix: 环境变量前缀，默认 "DB"
        :param continue_if_missing: 如果当前文件未找到配置，是否继续在其他文件中查找（默认False）
        :return: 数据库配置字典
        :raises ValueError: 如果配置不存在或格式错误
        """
        # 定义数据库配置的必填字段和类型转换器
        required_fields = ["host", "port", "user", "password", "database", "charset"]
        type_converters = {
            "port": int,
            "autocommit": bool,
            "cursor_type": str
        }
        
        result = self.get_typed_config(
            config_type='database',
            env=env,
            env_var_prefix=env_var_prefix,
            required_fields=required_fields,
            type_converters=type_converters,
            continue_if_missing=continue_if_missing
        )
        
        # 应用默认值（如果类型转换后仍需要）
        result.setdefault("port", 3306)
        result.setdefault("autocommit", False)
        result.setdefault("cursor_type", "tuple")
        
        return result

    def get_typed_config(
            self,
            config_type: str,
            env: Optional[str] = None,
            env_var_prefix: str = "APP",
            required_fields: Optional[List[str]] = None,
            type_converters: Optional[Dict[str, callable]] = None,
            continue_if_missing: bool = False
    ) -> Dict[str, Any]:
        """
        通用配置获取方法（适用于各种类型的配置）
        
        :param config_type: 配置类型名称（如 'database', 'redis', 'mq', 'api' 等）
        :param env: 环境标识 (如 ro/ops/rw/production)，默认从环境变量或配置文件获取
        :param env_var_prefix: 环境变量前缀，默认 "DB"
        :param required_fields: 必填字段列表
        :param type_converters: 类型转换器字典，如 {"port": int, "timeout": float, "enable": bool}
        :param continue_if_missing: 如果当前文件未找到配置，是否继续在其他文件中查找（默认False）
        :return: 配置字典
        :raises ValueError: 如果配置不存在或格式错误
        :raises FileNotFoundError: 如果未加载配置文件
        
        使用示例：
            # 获取 Redis 配置
            redis_config = loader.get_typed_config(
                config_type='redis',
                env_var_prefix='REDIS',
                required_fields=['host', 'port'],
                type_converters={'port': int, 'db': int, 'ssl': bool}
            )
            
            # 获取消息队列配置（开启继续查找）
            mq_config = loader.get_typed_config(
                config_type='mq',
                env='production',
                env_var_prefix='MQ',
                required_fields=['host', 'user', 'password'],
                type_converters={'port': int, 'vhost': str},
                continue_if_missing=True
            )
        """
        if not self._loaded_config:
            self.load_required()
        
        # 确定环境：参数 > 环境变量 > 配置文件
        env_var_name = f"{env_var_prefix}_CONFIG"
        target_env = (
                env or
                os.getenv(env_var_name) or
                self._loaded_config.get("env", "DB")
        )
        
        # 获取对应环境的配置
        typed_config = (
            self._loaded_config
            .get(config_type, {})
            .get(target_env)
        )
        
        if not typed_config:
            available_envs = list(self._loaded_config.get(config_type, {}).keys())
            
            # 如果开启继续查找且有多个配置路径
            if continue_if_missing and len(self.config_paths) > 1:
                # print(f"警告：在当前配置文件 {self._config_file} 中未找到 {config_type}.{target_env} 配置")
                # print(f"可用环境：{available_envs}")
                # print(f"将在其他配置路径中继续查找...")
                
                # 遍历所有配置路径查找
                for path_str in self.config_paths:
                    if path_str == self._config_file:
                        continue  # 跳过已加载的文件
                    
                    path = Path(path_str).expanduser()
                    if not path.exists():
                        continue
                    
                    try:
                        with open(path, 'r', encoding='utf-8') as f:
                            content = f.read()
                        
                        suffix = path.suffix.lower()
                        temp_config = None
                        
                        if suffix in {'.yaml', '.yml', '.conf'}:
                            if not HAS_YAML:
                                continue
                            temp_config = yaml.safe_load(content) or {}
                        elif suffix == '.json':
                            temp_config = json.loads(content) or {}
                        else:
                            continue
                        
                        # 检查该文件是否包含目标配置
                        found_config = (
                            temp_config
                            .get(config_type, {})
                            .get(target_env)
                        )
                        
                        if found_config:
                            print(f"✓ 在 {path} 中找到 {config_type}.{target_env} 配置")
                            # 更新加载的配置
                            self._loaded_config = temp_config
                            self._config_file = str(path)
                            
                            # 递归调用自身进行类型转换和校验
                            return self.get_typed_config(
                                config_type=config_type,
                                env=env,
                                env_var_prefix=env_var_prefix,
                                required_fields=required_fields,
                                type_converters=type_converters,
                                continue_if_missing=False  # 已找到，不再继续
                            )
                    
                    except Exception as e:
                        print(f"  读取 {path} 失败：{e}")
                        continue
                
                # 所有文件都未找到
                raise ValueError(
                    f"在所有配置文件中均未找到 '{target_env}' 环境的 {config_type} 配置。\n"
                    f"已尝试路径：{self.config_paths}\n"
                    f"提示：检查配置文件中是否存在 '{target_env}' 环境配置"
                )
            else:
                # 不继续查找，直接抛出异常
                raise ValueError(
                    f"未找到 '{target_env}' 环境的 {config_type} 配置。\n"
                    f"可用环境：{available_envs}\n"
                    f"提示：设置 {env_var_name} 环境变量切换环境"
                )
        
        # 类型转换和校验
        result = typed_config.copy()
        
        if type_converters:
            for field, converter in type_converters.items():
                if field in result:
                    try:
                        result[field] = converter(result[field])
                    except (ValueError, TypeError) as e:
                        raise ValueError(
                            f"字段 '{field}' 类型转换失败：期望 {converter.__name__}, "
                            f"实际值 '{result[field]}' - {str(e)}"
                        )
        
        # 校验必填字段
        if required_fields:
            missing = [f for f in required_fields if f not in result or not result[f]]
            if missing:
                raise ValueError(
                    f"'{target_env}' 环境配置缺失必填字段：{missing}"
                )
        
        return result

    @property
    def has_config(self) -> bool:
        """检查是否已加载配置"""
        return self._loaded_config is not None

    @property
    def config_file(self) -> Optional[str]:
        """获取当前使用的配置文件路径"""
        return self._config_file

    def __repr__(self) -> str:
        return (
            f"ConfigLoader("
            f"paths={len(self.config_paths)}, "
            f"loaded={self.has_config}, "
            f"file='{self._config_file}'"
            f")"
        )


# 便捷函数
def load_config_from_path(config_path: str) -> Dict[str, Any]:
    """从指定路径加载配置"""
    loader = ConfigLoader.from_path(config_path)
    return loader.load_required()


def load_config_auto(config_name: str = "") -> Dict[str, Any]:
    """自动检测并加载配置"""
    loader = ConfigLoader.auto_detect(config_name=config_name)
    return loader.load_required()


def get_db_config_from_env(env: Optional[str] = None) -> Dict[str, Any]:
    """快速获取数据库配置（自动检测配置文件）"""
    loader = ConfigLoader.auto_detect()
    return loader.get_database_config(env=env)
