"""
统一日志配置模块

这个模块提供了整个项目的统一日志配置和管理功能，确保所有组件使用一致的日志格式和输出方式。

主要功能:
1. 统一的日志格式配置
2. 支持控制台和文件双重输出
3. 日志文件轮转管理
4. MCP模式下的特殊处理（禁用控制台输出）
5. 便捷的日志器获取接口
6. 丰富的日志工具函数

设计特点:
- 单例模式确保配置一致性
- 支持动态配置调整
- 异常安全的编码处理
- 详细的调试信息记录

作者: lzwcai
版本: 1.0.0
"""

import logging
import logging.handlers
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional


class LoggerConfig:
    """
    日志配置管理器

    这个类采用单例模式管理整个项目的日志配置。
    它提供了统一的日志格式、文件轮转、编码处理等功能。

    主要特性:
    - 单例模式：确保全局日志配置一致
    - 双重输出：同时支持控制台和文件输出
    - 文件轮转：自动管理日志文件大小和数量
    - 编码安全：正确处理中文字符
    - MCP兼容：支持MCP模式下的特殊需求

    配置参数:
        DEFAULT_LOG_LEVEL: 默认日志级别（INFO）
        DEFAULT_LOG_FORMAT: 日志格式模板
        DEFAULT_DATE_FORMAT: 时间格式
        LOG_FILE_NAME: 日志文件名
        MAX_LOG_SIZE: 单个日志文件最大大小（10MB）
        BACKUP_COUNT: 保留的备份文件数量（5个）
    """

    # ==================== 默认配置常量 ====================

    # 默认日志级别：INFO级别平衡了信息量和性能
    DEFAULT_LOG_LEVEL = logging.INFO

    # 默认日志格式：包含时间、模块名、级别、文件位置、消息内容
    DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"

    # 默认时间格式：标准的年-月-日 时:分:秒格式
    DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

    # ==================== 日志文件配置 ====================

    # 日志文件名：使用项目名称作为前缀
    LOG_FILE_NAME = "lzwcai_mcp_api_converter.log"

    # 单个日志文件最大大小：10MB
    MAX_LOG_SIZE = 10 * 1024 * 1024  # 10MB

    # 保留的备份文件数量：5个（总共约50MB的日志存储）
    BACKUP_COUNT = 5

    # ==================== 单例模式状态 ====================

    # 初始化标志：确保只初始化一次
    _initialized = False

    # 日志文件路径：记录当前使用的日志文件路径
    _log_file_path = None
    
    @classmethod
    def setup_logging(
        cls,
        log_level: int = DEFAULT_LOG_LEVEL,
        log_file: Optional[str] = None,
        console_output: bool = True,
        file_output: bool = True
    ) -> str:
        """
        设置项目统一日志配置

        这是日志系统的核心初始化方法，负责配置整个项目的日志输出。
        采用单例模式，确保在整个应用生命周期中只初始化一次。

        配置流程:
        1. 检查是否已经初始化（单例模式）
        2. 确定日志文件路径（自动或手动指定）
        3. 创建必要的目录结构
        4. 配置根日志器和处理器
        5. 设置日志格式化器
        6. 添加控制台和文件处理器
        7. 记录初始化信息

        特殊处理:
        - MCP模式下通常禁用控制台输出，避免干扰stdio通信
        - Windows系统下的UTF-8编码处理
        - 日志文件的自动轮转管理

        参数:
            log_level: 日志级别（DEBUG, INFO, WARNING, ERROR, CRITICAL）
            log_file: 日志文件路径，None时使用默认路径
            console_output: 是否输出到控制台（MCP模式下通常为False）
            file_output: 是否输出到文件（通常为True）

        返回:
            str: 实际使用的日志文件路径

        注意事项:
        - 这个方法是线程安全的
        - 重复调用会直接返回已配置的路径
        - 日志文件会自动创建必要的目录
        """
        # 单例模式检查：如果已经初始化，直接返回
        if cls._initialized:
            return cls._log_file_path

        # ==================== 日志文件路径配置 ====================

        if log_file is None:
            # 自动确定日志文件路径：项目根目录 + 默认文件名
            project_root = cls._get_project_root()
            log_file = project_root / cls.LOG_FILE_NAME
        else:
            # 使用指定的日志文件路径
            log_file = Path(log_file)

        # 确保日志目录存在（递归创建）
        log_file.parent.mkdir(parents=True, exist_ok=True)
        cls._log_file_path = str(log_file)

        # ==================== 包日志器配置 ====================

        # 获取包的顶层日志器，而不是根日志器
        package_logger = logging.getLogger('lzwcai_mcp_api_converter')
        package_logger.setLevel(log_level)

        # 作为库，不应该清除宿主应用的任何处理器
        # 也不应该让日志消息向上传播到根日志器，以免重复打印
        package_logger.propagate = False

        # 清除此日志器上现有的处理器，避免重复配置
        for handler in package_logger.handlers[:]:
            package_logger.removeHandler(handler)

        # ==================== 日志格式化器 ====================

        # 创建统一的日志格式化器
        formatter = logging.Formatter(
            fmt=cls.DEFAULT_LOG_FORMAT,      # 日志格式模板
            datefmt=cls.DEFAULT_DATE_FORMAT  # 时间格式
        )
        
        # ==================== 控制台处理器配置 ====================

        if console_output:
            # 控制台输出处理器，支持彩色输出和UTF-8编码
            import io

            # 处理Windows系统的编码问题
            if hasattr(sys.stdout, 'buffer'):
                # 在Windows上强制使用UTF-8编码，避免中文乱码
                # errors='replace'确保即使有编码问题也不会崩溃
                console_stream = io.TextIOWrapper(
                    sys.stdout.buffer,
                    encoding='utf-8',
                    errors='replace'
                )
            else:
                # Unix/Linux系统通常默认支持UTF-8
                console_stream = sys.stdout

            # 创建控制台处理器
            console_handler = logging.StreamHandler(console_stream)
            console_handler.setLevel(log_level)
            console_handler.setFormatter(formatter)
            package_logger.addHandler(console_handler)

        # ==================== 文件处理器配置 ====================

        if file_output:
            # 文件输出处理器，支持自动轮转
            file_handler = logging.handlers.RotatingFileHandler(
                filename=cls._log_file_path,        # 日志文件路径
                maxBytes=cls.MAX_LOG_SIZE,          # 单文件最大大小
                backupCount=cls.BACKUP_COUNT,       # 备份文件数量
                encoding='utf-8'                    # 文件编码
            )
            file_handler.setLevel(log_level)
            file_handler.setFormatter(formatter)
            package_logger.addHandler(file_handler)

        # ==================== 初始化完成标记 ====================

        # 标记为已初始化，防止重复配置
        cls._initialized = True

        # ==================== 记录初始化信息 ====================

        # 获取当前模块的日志器并记录初始化信息
        logger = logging.getLogger(__name__)
        logger.info("=" * 80)
        logger.info(f"日志系统初始化完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        logger.info(f"日志级别: {logging.getLevelName(log_level)}")
        logger.info(f"日志文件: {cls._log_file_path}")
        logger.info(f"控制台输出: {console_output}")
        logger.info(f"文件输出: {file_output}")
        logger.info(f"文件轮转: 最大{cls.MAX_LOG_SIZE // (1024*1024)}MB, 保留{cls.BACKUP_COUNT}个备份")
        logger.info("=" * 80)

        return cls._log_file_path
    
    @classmethod
    def _get_project_root(cls) -> Path:
        """
        获取项目根目录

        这个方法通过向上遍历目录树来查找项目根目录。
        它会寻找常见的项目标识文件来确定根目录位置。

        查找策略:
        1. 从当前文件所在目录开始向上查找
        2. 寻找项目标识文件：pyproject.toml, setup.py, main.py
        3. 找到任一标识文件的目录即为项目根目录
        4. 如果都找不到，使用当前文件的上级目录作为备选

        返回:
            Path: 项目根目录的路径对象

        注意事项:
        - 这个方法假设项目结构相对标准
        - 在特殊的部署环境中可能需要调整
        - 备选方案确保总是返回有效路径
        """
        # 从当前文件向上查找项目根目录
        current_path = Path(__file__).parent

        # 向上遍历目录树
        while current_path.parent != current_path:  # 避免到达文件系统根目录
            # 检查常见的项目标识文件
            if (current_path / "pyproject.toml").exists() or \
               (current_path / "setup.py").exists() or \
               (current_path / "main.py").exists():
                return current_path
            current_path = current_path.parent

        # 备选方案：如果找不到标识文件，使用预设的相对路径
        # 这个路径基于当前的项目结构：util -> src -> 项目根
        return Path(__file__).parent.parent.parent

    @classmethod
    def get_logger(cls, name: str) -> logging.Logger:
        """
        获取配置好的日志器

        这是获取日志器的标准方法，确保返回的日志器使用统一的配置。
        如果日志系统尚未初始化，会自动进行初始化。

        参数:
            name: 日志器名称，通常使用模块的 __name__ 变量

        返回:
            logging.Logger: 配置好的日志器实例

        使用示例:
            logger = LoggerConfig.get_logger(__name__)
            logger.info("这是一条信息日志")

        特性:
        - 自动初始化：首次调用时自动配置日志系统（MCP模式下禁用控制台输出）
        - 层次化命名：支持Python日志器的层次化命名
        - 统一配置：所有日志器使用相同的格式和输出配置
        """
        # 检查是否已初始化，未初始化则使用默认配置初始化
        # 重要：在MCP模式下禁用控制台输出，避免干扰stdio通信
        if not cls._initialized:
            cls.setup_logging(console_output=False, file_output=True)

        # 返回指定名称的日志器
        return logging.getLogger(name)
    
    # ==================== 日志工具方法 ====================

    @classmethod
    def log_function_entry(cls, logger: logging.Logger, func_name: str, **kwargs):
        """
        记录函数入口日志

        用于调试和性能分析，记录函数被调用时的参数信息。
        通常在DEBUG级别输出，不会影响生产环境的性能。

        参数:
            logger: 日志器实例
            func_name: 函数名称
            **kwargs: 函数参数（键值对形式）

        使用示例:
            LoggerConfig.log_function_entry(logger, "process_data", user_id=123, action="login")
        """
        args_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
        logger.debug(f"进入函数 {func_name}({args_str})")

    @classmethod
    def log_function_exit(cls, logger: logging.Logger, func_name: str, result=None):
        """
        记录函数出口日志

        与log_function_entry配对使用，记录函数执行完成和返回值。
        有助于跟踪函数执行流程和调试返回值问题。

        参数:
            logger: 日志器实例
            func_name: 函数名称
            result: 函数返回值（可选）

        使用示例:
            LoggerConfig.log_function_exit(logger, "process_data", result={"status": "success"})
        """
        if result is not None:
            logger.debug(f"退出函数 {func_name}，返回值: {result}")
        else:
            logger.debug(f"退出函数 {func_name}")

    @classmethod
    def log_api_request(cls, logger: logging.Logger, method: str, url: str, **kwargs):
        """
        记录API请求日志

        标准化API请求的日志记录，包含HTTP方法、URL和请求参数。
        有助于API调用的监控和调试。

        参数:
            logger: 日志器实例
            method: HTTP方法（GET, POST, PUT, DELETE等）
            url: 请求URL
            **kwargs: 请求参数（可选）

        使用示例:
            LoggerConfig.log_api_request(logger, "POST", "https://api.example.com/users",
                                       headers={"Authorization": "Bearer xxx"})
        """
        logger.info(f"API请求 - {method} {url}")
        if kwargs:
            logger.debug(f"请求参数: {kwargs}")

    @classmethod
    def log_api_response(cls, logger: logging.Logger, status_code: int, response_time: float = None):
        """
        记录API响应日志

        记录API响应的状态码和响应时间，用于性能监控和问题诊断。

        参数:
            logger: 日志器实例
            status_code: HTTP状态码
            response_time: 响应时间（秒，可选）

        使用示例:
            LoggerConfig.log_api_response(logger, 200, 0.156)
        """
        if response_time:
            logger.info(f"API响应 - 状态码: {status_code}, 响应时间: {response_time:.3f}s")
        else:
            logger.info(f"API响应 - 状态码: {status_code}")

    @classmethod
    def log_error_with_context(cls, logger: logging.Logger, error: Exception, context: str = ""):
        """
        记录带上下文的错误日志

        提供丰富的错误信息记录，包含异常类型、错误消息、上下文信息和详细堆栈。
        这是错误处理的标准方法。

        参数:
            logger: 日志器实例
            error: 异常对象
            context: 错误发生的上下文描述（可选）

        使用示例:
            try:
                risky_operation()
            except Exception as e:
                LoggerConfig.log_error_with_context(logger, e, "处理用户请求时")
        """
        if context:
            logger.error(f"错误发生在 {context}: {type(error).__name__}: {str(error)}")
        else:
            logger.error(f"错误: {type(error).__name__}: {str(error)}")
        # 记录详细的异常堆栈信息（仅在DEBUG级别显示）
        logger.debug("错误详情:", exc_info=True)


# ==================== 便捷函数 ====================

def get_logger(name: str) -> logging.Logger:
    """
    获取日志器的便捷函数

    这是LoggerConfig.get_logger的简化版本，提供更简洁的调用方式。
    推荐在模块级别使用这个函数获取日志器。

    参数:
        name: 日志器名称，通常使用 __name__

    返回:
        logging.Logger: 配置好的日志器实例

    使用示例:
        logger = get_logger(__name__)
    """
    return LoggerConfig.get_logger(name)


def setup_logging(**kwargs) -> str:
    """
    设置日志的便捷函数

    这是LoggerConfig.setup_logging的简化版本，支持所有相同的参数。

    参数:
        **kwargs: 传递给LoggerConfig.setup_logging的所有参数

    返回:
        str: 日志文件路径

    使用示例:
        log_file = setup_logging(log_level=logging.DEBUG, console_output=False)
    """
    return LoggerConfig.setup_logging(**kwargs)


# ==================== 装饰器 ====================

def log_function_calls(logger: Optional[logging.Logger] = None):
    """
    函数调用日志装饰器

    这个装饰器自动记录函数的调用和返回，包括参数和返回值。
    主要用于调试和性能分析，在生产环境中通常设置为DEBUG级别。

    特性:
    - 自动记录函数入口和出口
    - 记录函数参数（kwargs）
    - 记录返回值
    - 自动处理异常并记录错误上下文
    - 支持自定义日志器或自动获取

    参数:
        logger: 可选的日志器实例，None时自动获取函数所在模块的日志器

    返回:
        装饰器函数

    使用示例:
        @log_function_calls()
        def process_user_data(user_id, action="login"):
            # 函数实现
            return {"status": "success"}

        # 或者指定日志器
        @log_function_calls(logger=my_logger)
        def another_function():
            pass

    注意事项:
    - 会记录所有kwargs参数，注意不要记录敏感信息
    - 返回值也会被记录，大对象可能影响性能
    - 异常会被重新抛出，不会被吞掉
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            nonlocal logger
            # 如果没有提供日志器，自动获取函数所在模块的日志器
            if logger is None:
                logger = get_logger(func.__module__)

            func_name = func.__name__

            # 记录函数入口（只记录kwargs，避免记录过多信息）
            LoggerConfig.log_function_entry(logger, func_name, **kwargs)

            try:
                # 执行原函数
                result = func(*args, **kwargs)

                # 记录函数出口和返回值
                LoggerConfig.log_function_exit(logger, func_name, result)
                return result

            except Exception as e:
                # 记录异常信息并重新抛出
                LoggerConfig.log_error_with_context(logger, e, f"函数 {func_name}")
                raise

        return wrapper
    return decorator


# ==================== 测试代码 ====================

if __name__ == "__main__":
    """
    日志配置测试代码

    这个测试代码演示了日志系统的基本功能，包括：
    1. 日志系统初始化
    2. 不同级别的日志输出
    3. 日志文件路径获取
    4. 装饰器功能测试

    运行方式:
        python -m lzwcai_mcp_api_converter.src.util.logger_config
    """
    # 初始化日志系统（DEBUG级别，同时输出到控制台和文件）
    log_file = setup_logging(log_level=logging.DEBUG)
    test_logger = get_logger(__name__)
    
    test_logger.info("开始测试日志配置...")

    # 测试不同级别的日志输出
    test_logger.debug("这是一个调试消息 - 用于开发调试")
    test_logger.info("这是一个信息消息 - 记录重要信息")
    test_logger.warning("这是一个警告消息 - 提醒注意事项")
    test_logger.error("这是一个错误消息 - 记录错误情况")

    # 测试工具方法
    LoggerConfig.log_api_request(test_logger, "GET", "https://api.example.com/test")
    LoggerConfig.log_api_response(test_logger, 200, 0.123)

    # 测试装饰器
    @log_function_calls()
    def test_function(param1, param2="default"):
        """测试函数"""
        return {"result": "success", "param1": param1}

    # 调用测试函数
    result = test_function("test_value", param2="custom")

    # 输出日志文件位置
    test_logger.info(f"日志文件位置: {log_file}")
    test_logger.info("日志配置测试完成！")
