"""
日志管理工具模块

提供统一的日志管理功能，支持：
- 控制台日志输出
- 文件日志输出（按天轮转）
- 多级别日志控制
- 线程安全的单例模式
- 配置文件加载

Example:
    >>> logger = Xlog.get_logger("config.ini")
    >>> logger.info("Application started")
    >>> logger.error("An error occurred", exc_info=True)
"""
import os
import logging
import threading
from typing import Optional, Dict, Any, Mapping, Union

from xsdk.config_reader import ConfigReader
from logging.handlers import TimedRotatingFileHandler

# 全局线程锁，用于单例模式
_lock = threading.Lock()

DEFAULT_XLOG_NAME = "Xlog"
DEFAULT_PYTHON_LOGGER_NAME = "DefaultLogger"
TRUE_VALUES = {"1", "y", "yes", "true", "on"}

# 默认日志格式
DEFAULT_LOG_FORMAT = (
    "[%(asctime)s.%(msecs)03d %(levelname)s] "
    "[%(thread)d] (%(filename)s:%(lineno)d) %(message)s"
)
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"


class XlogConfig:
    """
    日志配置类

    封装日志相关的配置参数。

    Attributes:
        log_level: 全局日志级别
        console_log_flag: 是否输出到控制台
        file_log_flag: 是否输出到文件
        file_log_path: 日志文件目录
        file_log_name: 日志文件名

    Example:
        >>> config = XlogConfig(
        ...     log_level=logging.DEBUG,
        ...     console_log_flag=True,
        ...     file_log_flag=True,
        ...     file_log_path="./logs"
        ... )
    """

    def __init__(
        self,
        log_level: int = logging.DEBUG,
        console_log_flag: bool = True,
        file_log_flag: bool = False,
        file_log_path: str = "../log",
        file_log_name: str = "xlog.log"
    ):
        """
        初始化日志配置

        Args:
            log_level: 全局日志级别
            console_log_flag: 是否输出到控制台
            file_log_flag: 是否输出到文件
            file_log_path: 日志文件目录
            file_log_name: 日志文件名
        """
        self.log_level: int = log_level
        self.console_log_flag: bool = console_log_flag
        self.file_log_flag: bool = file_log_flag
        self.file_log_path: str = file_log_path
        self.file_log_name: str = file_log_name

    def __str__(self) -> str:
        """
        返回配置的字符串表示

        Returns:
            配置字典的字符串形式
        """
        return str(self.__dict__)

    @staticmethod
    def _to_bool(value: Any) -> bool:
        """
        将配置值转换为布尔值

        Args:
            value: 任意配置值

        Returns:
            解析后的布尔值
        """
        if isinstance(value, bool):
            return value
        if value is None:
            return False
        return str(value).strip().lower() in TRUE_VALUES

    @staticmethod
    def _normalize_file_log_path(file_log_path: Any) -> str:
        """
        标准化日志目录配置

        Args:
            file_log_path: 日志目录配置值

        Returns:
            可用的日志目录路径
        """
        path = str(file_log_path).strip()
        return path or "../log"

    @staticmethod
    def _ensure_file_log_path(file_log_flag: bool, file_log_path: str) -> str:
        """
        在启用文件日志时确保日志目录存在

        Args:
            file_log_flag: 是否启用文件日志
            file_log_path: 日志目录路径

        Returns:
            标准化后的日志目录路径
        """
        file_log_path = XlogConfig._normalize_file_log_path(file_log_path)
        if file_log_flag:
            os.makedirs(file_log_path, exist_ok=True)
        return file_log_path

    @staticmethod
    def _from_mapping(data: Optional[Mapping[str, Any]]) -> "XlogConfig":
        """
        从映射对象解析日志配置

        Args:
            data: 配置映射

        Returns:
            XlogConfig实例
        """
        log_conf = XlogConfig()
        if not data:
            return log_conf

        for key, value in data.items():
            if key in log_conf.__dict__:
                setattr(log_conf, key, value)

        log_conf.log_level = XlogConfig.get_log_level(log_conf.log_level)
        log_conf.console_log_flag = XlogConfig._to_bool(log_conf.console_log_flag)
        log_conf.file_log_flag = XlogConfig._to_bool(log_conf.file_log_flag)

        # 至少开启一种日志模式
        if not log_conf.console_log_flag and not log_conf.file_log_flag:
            log_conf.console_log_flag = True

        log_conf.file_log_path = XlogConfig._ensure_file_log_path(
            log_conf.file_log_flag,
            log_conf.file_log_path,
        )
        log_conf.file_log_name = str(log_conf.file_log_name).strip() or "xlog.log"

        return log_conf

    @staticmethod
    def get_config(conf_file: str, conf_section: str = "xlog") -> "XlogConfig":
        """
        从配置文件加载日志配置

        Args:
            conf_file: 配置文件路径
            conf_section: 配置Section名称

        Returns:
            XlogConfig实例
        """
        config_reader = ConfigReader.get_instance(conf_file)
        default_conf = XlogConfig()

        conf_dict = {
            "log_level": config_reader.get(
                conf_section,
                "log_level",
                logging.getLevelName(default_conf.log_level),
            ),
            "console_log_flag": config_reader.get(
                conf_section,
                "console_log_flag",
                default_conf.console_log_flag,
            ),
            "file_log_flag": config_reader.get(
                conf_section,
                "file_log_flag",
                default_conf.file_log_flag,
            ),
            "file_log_path": config_reader.get(
                conf_section,
                "file_log_path",
                default_conf.file_log_path,
            ),
            "file_log_name": config_reader.get(
                conf_section,
                "file_log_name",
                default_conf.file_log_name,
            ),
        }
        return XlogConfig._from_mapping(conf_dict)

    @staticmethod
    def parse_dict(data_dict: Optional[Dict[str, Any]]) -> "XlogConfig":
        """
        从字典解析日志配置

        Args:
            data_dict: 配置字典

        Returns:
            XlogConfig实例
        """
        return XlogConfig._from_mapping(data_dict)

    @staticmethod
    def get_log_level(log_level: Union[str, int]) -> int:
        """
        获取日志级别的数值

        Args:
            log_level: 日志级别（字符串或整数）

        Returns:
            日志级别数值
        """
        if isinstance(log_level, int):
            return log_level

        if str(log_level).strip().isdigit():
            return int(str(log_level).strip())

        level = logging.getLevelName(str(log_level).upper())
        if isinstance(level, int):
            return level
        return logging.INFO


class Xlog:
    """
    日志管理类

    提供线程安全的日志记录功能，支持控制台和文件输出。

    Attributes:
        logger: 类级别的单例logger实例
        log_conf: 日志配置

    Example:
        >>> logger = Xlog.get_logger("config.ini")
        >>> logger.info("Processing data...")
        >>> logger.error("Error occurred", exc_info=True)
    """

    logger: Optional["Xlog"] = None

    def __init__(self, log_conf: Union[XlogConfig, Dict[str, Any]]):
        """
        初始化日志管理器

        Args:
            log_conf: 日志配置对象或字典
        """
        if isinstance(log_conf, dict):
            log_conf = XlogConfig.parse_dict(log_conf)
        self.log_conf: XlogConfig = log_conf
        self._logger: Optional[logging.Logger] = None
        self._init()

    def _init(self) -> None:
        """
        初始化日志处理器

        配置控制台和文件日志处理器。
        """
        self._logger = logging.getLogger(DEFAULT_XLOG_NAME)
        self._close_handlers(self._logger)
        self._logger.setLevel(self.log_conf.log_level)
        self._logger.propagate = False

        formatter = logging.Formatter(
            fmt=DEFAULT_LOG_FORMAT,
            datefmt=DEFAULT_DATE_FORMAT
        )

        # 控制台日志处理器
        if self.log_conf.console_log_flag:
            handler_console = logging.StreamHandler()
            handler_console.setLevel(self.log_conf.log_level)
            handler_console.setFormatter(formatter)
            self._logger.addHandler(handler_console)

        # 文件日志处理器（按天轮转，保留45天）
        if self.log_conf.file_log_flag:
            log_file = os.path.join(self.log_conf.file_log_path, self.log_conf.file_log_name)
            handler_file = TimedRotatingFileHandler(
                log_file,
                when="D",
                interval=1,
                backupCount=45,
                encoding="utf-8"
            )
            handler_file.setLevel(self.log_conf.log_level)
            handler_file.setFormatter(formatter)
            self._logger.addHandler(handler_file)

    @staticmethod
    def _close_handlers(logger: logging.Logger) -> None:
        """
        移除并关闭logger上的所有handler

        Args:
            logger: 目标logger
        """
        for handler in list(logger.handlers):
            logger.removeHandler(handler)
            handler.close()

    @staticmethod
    def _resolve_log_conf(
        conf_file: Optional[Union[str, Dict[str, Any], XlogConfig]],
        conf_section: str,
        **kwargs: Any
    ) -> XlogConfig:
        """
        解析日志初始化参数

        兼容以下调用方式：
        - Xlog.get_logger("conf.ini")
        - Xlog.get_logger(config_path="conf.ini")
        - Xlog.get_logger({"log_level": "INFO"})
        - Xlog.get_logger(config_dict={...})
        - Xlog.get_logger()

        Args:
            conf_file: 配置文件路径、配置字典或配置对象
            conf_section: 配置Section名称
            **kwargs: 兼容旧版本的额外关键字参数

        Returns:
            XlogConfig实例
        """
        config_path = kwargs.pop("config_path", None)
        config_dict = kwargs.pop("config_dict", None)
        if kwargs:
            unexpected_keys = ", ".join(sorted(kwargs.keys()))
            raise TypeError(f"Unexpected keyword arguments: {unexpected_keys}")

        sources = [
            source for source in (conf_file, config_path, config_dict)
            if source is not None
        ]
        if len(sources) > 1:
            raise TypeError("Only one log config source can be specified")

        config_source = sources[0] if sources else None

        if isinstance(config_source, XlogConfig):
            return config_source
        if isinstance(config_source, dict):
            return XlogConfig.parse_dict(config_source)
        if config_source is None or str(config_source).strip() == "":
            return XlogConfig()
        return XlogConfig.get_config(str(config_source), conf_section)

    @staticmethod
    def get_logger(
        conf_file: Optional[Union[str, Dict[str, Any], XlogConfig]] = None,
        conf_section: str = "xlog",
        **kwargs: Any
    ) -> "Xlog":
        """
        获取日志管理器单例

        线程安全的单例模式，确保全局只有一个日志实例。

        Args:
            conf_file: 配置文件路径
            conf_section: 配置Section名称

        Returns:
            Xlog单例实例
        """
        if Xlog.logger is None:
            with _lock:
                if Xlog.logger is None:
                    log_conf = Xlog._resolve_log_conf(conf_file, conf_section, **kwargs)
                    Xlog.logger = Xlog(log_conf)
        return Xlog.logger

    @staticmethod
    def get_default_logger(log_level: Union[str, int] = "INFO") -> logging.Logger:
        """
        获取默认的Python logger

        Args:
            log_level: 日志级别

        Returns:
            标准logging.Logger实例
        """
        level = XlogConfig.get_log_level(log_level)
        logger = logging.getLogger(DEFAULT_PYTHON_LOGGER_NAME)
        logger.setLevel(level)
        logger.propagate = False

        formatter = logging.Formatter(
            fmt=DEFAULT_LOG_FORMAT,
            datefmt=DEFAULT_DATE_FORMAT
        )

        if not logger.handlers:
            handler_console = logging.StreamHandler()
            handler_console.setFormatter(formatter)
            logger.addHandler(handler_console)

        for handler in logger.handlers:
            handler.setLevel(level)

        return logger

    def set_level(self, log_level: str = "INFO") -> None:
        """
        动态设置日志级别

        Args:
            log_level: 日志级别字符串
        """
        level = XlogConfig.get_log_level(log_level)
        self._logger.setLevel(level)
        for handler in self._logger.handlers:
            handler.setLevel(level)

    @staticmethod
    def _get_stack_level(kwargs: Dict[str, Any]) -> int:
        """
        从kwargs中提取stacklevel参数

        Args:
            kwargs: 关键字参数字典

        Returns:
            stacklevel值
        """
        sl1 = kwargs.pop("stacklevel", None)
        sl2 = kwargs.pop("stack_level", None)
        stack_level = sl1 if sl1 is not None else sl2

        try:
            stack_level = int(str(stack_level))
        except (ValueError, TypeError):
            stack_level = 2

        return max(1, stack_level)

    def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录DEBUG级别日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.debug(msg, *args, stacklevel=stack_level, **kwargs)

    def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录INFO级别日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.info(msg, *args, stacklevel=stack_level, **kwargs)

    def warn(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录WARNING级别日志（warn别名）

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.warning(msg, *args, stacklevel=stack_level, **kwargs)

    def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录WARNING级别日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.warning(msg, *args, stacklevel=stack_level, **kwargs)

    def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录ERROR级别日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.error(msg, *args, stacklevel=stack_level, **kwargs)

    def fatal(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """
        记录FATAL级别日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.fatal(msg, *args, stacklevel=stack_level, **kwargs)

    def exception(
        self,
        msg: str,
        *args: Any,
        exc_info: bool = True,
        **kwargs: Any
    ) -> None:
        """
        记录异常日志

        Args:
            msg: 日志消息
            *args: 格式化参数
            exc_info: 是否包含异常信息
            **kwargs: 关键字参数
        """
        stack_level = Xlog._get_stack_level(kwargs)
        self._logger.exception(msg, *args, exc_info=exc_info, stacklevel=stack_level, **kwargs)


if __name__ == "__main__":
    pass
