"""
core/logger.py - 日志管理模块
==============================
提供统一的日志记录功能，支持:
  - 文件 + 控制台双输出
  - 按时间/大小自动轮转
  - ANSI 彩色控制台输出
  - JSON 结构化日志输出（可选）
  - 动态日志级别调整
"""
from __future__ import annotations

import os
import sys
import json
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from pathlib import Path
from typing import Optional, Dict, Any
from datetime import datetime
from zoneinfo import ZoneInfo
import time as _time


# ANSI 颜色码
COLORS = {
    "DEBUG": "\033[36m",     # 青色
    "INFO": "\033[32m",      # 绿色
    "WARNING": "\033[33m",   # 黄色
    "ERROR": "\033[31m",     # 红色
    "CRITICAL": "\033[35m",  # 紫色
    "RESET": "\033[0m",
}


def _tz_converter(timestamp):
    """logging.Formatter.converter — 使用配置时区替代系统时区"""
    try:
        from config import ConfigManager
        tz_name = ConfigManager().get("timezone", "Asia/Shanghai")
        tz = ZoneInfo(tz_name)
    except Exception:
        tz = ZoneInfo("Asia/Shanghai")
    dt = datetime.fromtimestamp(timestamp, tz=tz)
    return dt.timetuple()


class ColorFormatter(logging.Formatter):
    """带颜色的控制台日志格式化器"""
    converter = staticmethod(_tz_converter)

    def format(self, record):
        color = COLORS.get(record.levelname, COLORS["RESET"])
        record.levelname = f"{color}{record.levelname:8s}{COLORS['RESET']}"
        return super().format(record)


class FileFormatter(logging.Formatter):
    """文件日志格式化器(无颜色)

    [v1.18.8+] 同时清理 record.levelname 中的 ANSI 残留。
    原因: ColorFormatter 和 FileFormatter 共享同一个 LogRecord 对象，
    ColorFormatter.format() 先执行时会修改 record.levelname 为带颜色码的字符串，
    FileFormatter.format() 后执行时 record.levelname 已被污染。
    """
    converter = staticmethod(_tz_converter)

    # 预编译 ANSI 转义码正则，避免每次 format 都拼接字符串
    _ansi_re = None

    def format(self, record):
        # 去除 ANSI 颜色码（包括 msg 和 levelname）
        record.msg = str(record.msg)
        # 去除 msg 中的 ANSI 码
        self._strip_ansi(record, "msg")
        # 去除 levelname 中的 ANSI 码（ColorFormatter 污染的残留）
        if hasattr(record, "levelname"):
            clean = self._strip_ansi_str(record.levelname)
            record.levelname = clean.strip()
        return super().format(record)

    @classmethod
    def _strip_ansi(cls, record, attr):
        """去除 record 属性中的 ANSI 转义码"""
        val = getattr(record, attr, "")
        if isinstance(val, str):
            setattr(record, attr, cls._strip_ansi_str(val))

    @classmethod
    def _strip_ansi_str(cls, s):
        """去除字符串中的 ANSI 转义码"""
        if not cls._ansi_re:
            import re
            cls._ansi_re = re.compile(r"\033\[[0-9;]*m")
        return cls._ansi_re.sub("", s)


class JsonFormatter(logging.Formatter):
    """
    JSON 结构化日志格式化器。

    输出格式:
    {"timestamp": "...", "level": "INFO", "logger": "...", "message": "...", "extra": {}}
    """

    def format(self, record):
        try:
            from config import ConfigManager
            tz_name = ConfigManager().get("timezone", "Asia/Shanghai")
            tz = ZoneInfo(tz_name)
        except Exception:
            tz = ZoneInfo("Asia/Shanghai")
        log_entry: Dict[str, Any] = {
            "timestamp": datetime.now(tz).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
        }

        # 附加异常信息
        if record.exc_info and record.exc_info[0] is not None:
            log_entry["exception"] = {
                "type": record.exc_info[0].__name__,
                "message": str(record.exc_info[1]),
            }

        # 附加自定义字段
        if hasattr(record, "extra_fields"):
            log_entry.update(record.extra_fields)

        return json.dumps(log_entry, ensure_ascii=False, default=str)


class LevelFilter(logging.Filter):
    """只允许指定级别以上的日志通过"""

    def __init__(self, min_level: str = "DEBUG"):
        super().__init__()
        self.min_level = getattr(logging, min_level.upper(), logging.DEBUG)

    def filter(self, record):
        return record.levelno >= self.min_level


# ==============================================================================
# 全局日志器注册表（支持动态级别调整）
# ==============================================================================

_loggers: Dict[str, logging.Logger] = {}
_log_configs: Dict[str, Dict[str, Any]] = {}


def setup_logger(
    name: str = "myagent",
    log_dir: Optional[str] = None,
    level: str = "INFO",
    console: bool = True,
    json_format: bool = False,
    rotation: str = "size",
    max_bytes: int = 10 * 1024 * 1024,  # 10MB
    backup_count: int = 5,
    rotation_when: str = "midnight",
) -> logging.Logger:
    """
    初始化日志系统。

    Args:
        name: 日志器名称
        log_dir: 日志文件目录，默认为 ~/.myagent/logs/
        level: 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)
        console: 是否输出到控制台
        json_format: 是否使用 JSON 结构化格式输出到文件
        rotation: 轮转策略 "size"(按大小) | "time"(按时间) | "none"(不轮转)
        max_bytes: 按大小轮转时，单个文件最大字节数
        backup_count: 保留的备份文件数量
        rotation_when: 按时间轮转的时间点 ("midnight"/"H"/"D"/"W0" 等)

    Returns:
        配置好的 Logger 实例
    """
    logger = logging.getLogger(name)

    # 避免重复添加 handler
    if logger.handlers:
        return logger

    logger.setLevel(getattr(logging, level.upper(), logging.INFO))
    # 防止日志向上层传播（避免重复输出）
    logger.propagate = False

    # 日志格式
    fmt = "[%(asctime)s] %(levelname)s %(name)s - %(message)s"
    datefmt = "%Y-%m-%d %H:%M:%S"

    # 控制台输出
    if console:
        ch = logging.StreamHandler(sys.stdout)
        # Windows 兼容 UTF-8
        if sys.platform == "win32":
            try:
                sys.stdout.reconfigure(encoding="utf-8")
            except Exception:
                pass
        ch.setFormatter(ColorFormatter(fmt, datefmt=datefmt))
        ch.setLevel(logging.DEBUG)  # 控制台始终显示所有级别
        logger.addHandler(ch)

    # 文件输出
    if log_dir:
        log_path = Path(log_dir)
        log_path.mkdir(parents=True, exist_ok=True)

        if rotation == "size":
            # 按大小轮转
            log_file = log_path / f"{name}.log"
            fh = RotatingFileHandler(
                log_file,
                maxBytes=max_bytes,
                backupCount=backup_count,
                encoding="utf-8",
            )
            fh.setFormatter(
                JsonFormatter() if json_format
                else FileFormatter(fmt, datefmt=datefmt)
            )
            logger.addHandler(fh)

        elif rotation == "time":
            # 按时间轮转
            log_file = log_path / f"{name}.log"
            fh = TimedRotatingFileHandler(
                log_file,
                when=rotation_when,
                backupCount=backup_count,
                encoding="utf-8",
            )
            fh.setFormatter(
                JsonFormatter() if json_format
                else FileFormatter(fmt, datefmt=datefmt)
            )
            # 日志文件名后缀
            fh.suffix = "%Y%m%d"
            logger.addHandler(fh)

        else:
            # 不轮转，按日期命名
            try:
                from config import ConfigManager
                _tz = ZoneInfo(ConfigManager().get("timezone", "Asia/Shanghai"))
            except Exception:
                _tz = ZoneInfo("Asia/Shanghai")
            log_file = log_path / f"{name}_{datetime.now(_tz).strftime('%Y%m%d')}.log"
            fh = logging.FileHandler(log_file, encoding="utf-8")
            fh.setFormatter(
                JsonFormatter() if json_format
                else FileFormatter(fmt, datefmt=datefmt)
            )
            logger.addHandler(fh)

    # [v1.18.8+] 将文件 handler 也添加到 root logger，
    # 捕获所有第三方库日志（aiohttp/openai/httpx/urllib3 等）。
    # 原因: myagent logger 设置了 propagate=False，第三方库的日志不会经过它。
    # 终端能看到这些日志（通过 stderr lastResort handler），但日志文件里看不到。
    if log_dir and name == "myagent":
        root = logging.getLogger()
        # 只添加文件 handler，不添加控制台 handler（避免重复输出到终端）
        _add_file_handlers_to_root(root, log_path, name, rotation, max_bytes, backup_count, rotation_when, json_format, fmt, datefmt)
        root.setLevel(logging.INFO)
        # 抑制过度嘈杂的第三方库日志到 WARNING 级别
        _suppress_verbose_loggers()

    # [v1.18.8+] 捕获未处理异常，写入日志文件（终端通过 stderr 能看到，但文件里没有）
    if name == "myagent":
        _install_excepthook()

    # 保存配置
    _loggers[name] = logger
    _log_configs[name] = {
        "level": level,
        "log_dir": log_dir,
        "console": console,
        "json_format": json_format,
        "rotation": rotation,
    }

    return logger


def get_logger(name: str = "myagent") -> logging.Logger:
    """获取已存在的 Logger，如果不存在则创建默认的。

    [v1.18.8] 子 logger 不再创建独立文件，而是继承最近有 handlers 的父 logger。
    例如 get_logger("myagent.api") 会复用 "myagent" 的文件 handler，
    确保所有日志集中写入同一个 myagent.log 文件。
    """
    logger = logging.getLogger(name)
    if not logger.handlers:
        # 向上查找最近有 handlers 的父 logger（如 "myagent"）
        parts = name.split(".")
        parent_found = None
        for i in range(len(parts) - 1, 0, -1):
            parent_name = ".".join(parts[:i])
            parent = logging.getLogger(parent_name)
            if parent.handlers:
                parent_found = parent
                break
        if parent_found:
            for handler in parent_found.handlers:
                logger.addHandler(handler)
            logger.setLevel(parent_found.level)
            logger.propagate = False  # 复制 handlers 后禁止再向上传播（避免重复）
        else:
            # 没有找到有 handlers 的父 logger，创建默认配置
            return setup_logger(name)
    return logger


def set_level(name: str = "myagent", level: str = "INFO"):
    """
    动态调整日志级别。

    Args:
        name: 日志器名称，"all" 表示调整所有已注册的日志器
        level: 新的日志级别
    """
    if name == "all":
        for logger_name, logger in _loggers.items():
            logger.setLevel(getattr(logging, level.upper(), logging.INFO))
    else:
        logger = logging.getLogger(name)
        logger.setLevel(getattr(logging, level.upper(), logging.INFO))


def get_log_config(name: str = "myagent") -> Dict[str, Any]:
    """获取日志配置"""
    return _log_configs.get(name, {})


# ==============================================================================
# [v1.18.8+] 内部辅助函数
# ==============================================================================

# 防止重复安装 excepthook
_excepthook_installed = False


def _add_file_handlers_to_root(root, log_path, name, rotation, max_bytes, backup_count, rotation_when, json_format, fmt, datefmt):
    """将文件 handler 添加到 root logger，写入同一个 myagent.log 文件。

    注意: 使用独立的 handler 实例而非共享 myagent 的 handler。
    虽然共享同一个 handler 技术上可行，但 RotatingFileHandler 在多个 logger
    共享同一个实例时，close() 可能导致问题。
    """
    if rotation == "size":
        log_file = log_path / f"{name}.log"
        fh = RotatingFileHandler(
            log_file,
            maxBytes=max_bytes,
            backupCount=backup_count,
            encoding="utf-8",
        )
    elif rotation == "time":
        log_file = log_path / f"{name}.log"
        fh = TimedRotatingFileHandler(
            log_file,
            when=rotation_when,
            backupCount=backup_count,
            encoding="utf-8",
        )
        fh.suffix = "%Y%m%d"
    else:
        try:
            from config import ConfigManager
            _tz = ZoneInfo(ConfigManager().get("timezone", "Asia/Shanghai"))
        except Exception:
            _tz = ZoneInfo("Asia/Shanghai")
        log_file = log_path / f"{name}_{datetime.now(_tz).strftime('%Y%m%d')}.log"
        fh = logging.FileHandler(log_file, encoding="utf-8")

    fh.setFormatter(FileFormatter(fmt, datefmt=datefmt))
    fh.setLevel(logging.INFO)
    root.addHandler(fh)


def _suppress_verbose_loggers():
    """将过度嘈杂的第三方库日志级别提升到 WARNING，避免日志文件膨胀。

    这些库在 INFO/DEBUG 级别会产生大量输出（如每个 HTTP 请求的详细信息），
    但终端通过 lastResort handler 只显示 WARNING+，所以终端看不到这些。
    为了让日志文件和终端保持一致，也抑制这些输出。
    """
    noisy_loggers = [
        "urllib3.connectionpool",
        "httpx",
        "httpcore",
        "hpack",
        "asyncio",
        "charset_normalizer",
        "PIL.PngImagePlugin",
        "transformers",
        "sentencepiece",
        "tokenizers",
        "aiohttp.access",      # HTTP 访问日志（每个请求一行，过于嘈杂）
        "aiohttp.server",      # HTTP 服务器内部日志
    ]
    for logger_name in noisy_loggers:
        try:
            logging.getLogger(logger_name).setLevel(logging.WARNING)
        except Exception:
            pass


def _install_excepthook():
    """安装全局异常钩子，将未捕获的异常写入日志文件。

    默认行为: Python 将未捕获异常的 traceback 直接输出到 stderr。
    在终端运行时能看到，但日志文件中看不到。
    """
    global _excepthook_installed
    if _excepthook_installed:
        return
    _excepthook_installed = True

    _original_excepthook = sys.excepthook

    def _logging_excepthook(exc_type, exc_value, exc_traceback):
        # 先调用原始 hook（输出到 stderr，保持终端行为不变）
        if _original_excepthook:
            try:
                _original_excepthook(exc_type, exc_value, exc_traceback)
            except Exception:
                pass
        # 同时写入日志文件
        try:
            root_logger = logging.getLogger()
            if root_logger.handlers:
                root_logger.critical(
                    "未捕获异常",
                    exc_info=(exc_type, exc_value, exc_traceback),
                )
        except Exception:
            pass

    sys.excepthook = _logging_excepthook
