"""
Единый логгер: автодефолты для extra, контекст (ContextLogger), управление полями и цветами из конфига.
Объединяет практики из log_test.py и logger1.py.
"""
import sys
import logging
from dataclasses import dataclass, field
from contextvars import ContextVar
from string import Formatter
from typing import Set, Dict, Any, Optional, Tuple, List

from loguru import logger as _loguru_logger


# =============================================================================
# Конфиг: цвета, уровни, список полей (вкл/выкл, ширина, цвет)
# =============================================================================

class LogConfig:
    """Конфигурация логирования. Формат строится из EXTRA_FIELDS в build_console_format()."""

    MAX_FRAME_DEPTH = 20
    SHOW_MAGIC_METHODS = False

    # Иконки и цвета уровней (loguru: level + color в формате)
    LEVELS = {
        "DEBUG": {"icon": "•", "color": "<fg #61afef>"},
        "INFO": {"icon": "»", "color": "<fg #abb2bf>"},
        "SUCCESS": {"icon": "✓", "color": "<fg #98c379>"},
        "WARNING": {"icon": "⚠", "color": "<fg #e5c07b>"},
        "ERROR": {"icon": "✗", "color": "<fg #e06c75>"},
        "CRITICAL": {"icon": "‼", "color": "<bg #e06c75><fg #ffffff>"},
    }

    # Цвета для элементов (ключ — value "color" в EXTRA_FIELDS)
    COLORS = {
        "time": "#5c6370",
        "worker": "magenta",
        "context": "cyan",
        "class_method": "#61afef",
        "url": "#61afef",
        "message_dim": "#abb2bf",
    }

    # Поля: visible, len, color; опционально default для автодефолтов. По этому списку билдится формат.
    EXTRA_FIELDS: List[Dict[str, Any]] = [
        {"extra": "worker_id", "visible": True, "len": None, "color": "worker", "default": "0"},
        {"extra": "worker_name", "visible": False, "len": 10, "color": "worker", "default": ""},
        {"extra": "elapsed", "visible": True, "default": 0},
        {"extra": "function", "visible": True, "len": 8, "color": "class_method"},
        {"extra": "ip", "visible": False, "len": 15, "color": "context", "default": "000.000.000.000"},

        {"extra": "id", "visible": True, "len": 13, "color": "context"},
        {"extra": "status_code_formatted", "visible": True, "default": ""},
    ]

    EXTRA_DEFAULT_VALUE: Any = "N/A"


# Иконки уровней заданы явно (для использования в формате через extra[level_icon])
LEVEL_ICONS = {
    "DEBUG": "•",
    "INFO": "ℹ",
    "SUCCESS": "✓",
    "WARNING": "⚠",
    "ERROR": "✗",
    "CRITICAL": "‼",
}

def context_function_filter(record: Dict[str, Any]) -> None:
    """Автоматически определяет 'Class.method' или 'function'."""
    # Если поле уже заполнено вручную (например через bind), не трогаем
    if record["extra"].get("function") and record["extra"]["function"] != LogConfig.EXTRA_DEFAULT_VALUE:
        return

    frame = record.get("frame")
    func_name = record["function"]

    if frame:
        args = frame.f_locals
        # Ищем self (обычный метод) или cls (classmethod)
        instance = args.get("self")
        cls = args.get("cls")

        if instance:
            class_name = instance.__class__.__name__
            record["extra"]["function"] = f"{class_name}.{func_name}"
        elif cls and isinstance(cls, type):
            class_name = cls.__name__
            record["extra"]["function"] = f"{class_name}.{func_name}"
        else:
            record["extra"]["function"] = func_name
    else:
        record["extra"]["function"] = func_name
# =============================================================================
# Время и статус-код: цветовые схемы (ANSI)
# =============================================================================

@dataclass
class SpeedLevelConfig:
    """Цвет времени по длительности (сек)."""
    max_time: float
    color: str  # #rrggbb

    def __post_init__(self):
        if self.max_time < 0:
            raise ValueError("max_time >= 0")


@dataclass
class RequestLogConfig:
    """Палитра времени и статус-кода."""
    speed_levels: list = field(default_factory=lambda: [
        SpeedLevelConfig(max_time=1.0, color="#4b5263"),
        SpeedLevelConfig(max_time=2.0, color="#e5c07b"),
        SpeedLevelConfig(max_time=3.5, color="#c678dd"),
        SpeedLevelConfig(max_time=float("inf"), color="#e06c75"),
    ])

    def get_speed_color(self, execution_time: float) -> str:
        for level in sorted(self.speed_levels, key=lambda x: x.max_time):
            if execution_time <= level.max_time:
                return level.color
        return self.speed_levels[-1].color if self.speed_levels else "#4b5263"

    def get_status_palette(self, status_code: Optional[int]) -> Tuple[str, str, Optional[str], bool]:
        if status_code is None:
            return ("---", "#abb2bf", "#3e4451", False)
        code = int(status_code)
        text = f"{code:>3}"
        if code == 200:
            return (text, "#1e2127", "#98c379", True)
        if 200 < code < 300:
            return (text, "#1e2127", "#56b6c2", False)
        if code == 404:
            return (text, "#1e2127", "#e5c07b", True)
        if 300 <= code < 400:
            return (text, "#1e2127", "#c678dd", False)
        if 400 <= code < 500:
            return (text, "#ffffff", "#e06c75", False)
        if 500 <= code < 600:
            return (text, "#ffffff", "#be5046", False)
        return (text, "#d7dae0", "#3e4451", False)


def _hex_to_ansi_fg(hex_color: str) -> str:
    h = hex_color.lstrip("#")
    r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
    return f"\033[38;2;{r};{g};{b}m"


def _hex_to_ansi_bg(hex_color: str) -> str:
    h = hex_color.lstrip("#")
    r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
    return f"\033[48;2;{r};{g};{b}m"


_ANSI_RESET = "\033[0m"
_ANSI_BOLD = "\033[1m"
_request_log_config = RequestLogConfig()


def _wrap_status_code_ansi(text: str, fg: str, bg: Optional[str], bold: bool) -> str:
    inner = f" {text} "
    parts = [_ANSI_BOLD] if bold else []
    if fg:
        parts.append(_hex_to_ansi_fg(fg))
    if bg:
        parts.append(_hex_to_ansi_bg(bg))
    parts.append(inner)
    parts.append(_ANSI_RESET)
    return "".join(parts)


def request_format_filter(record: Dict[str, Any]) -> bool:
    """Заполняет elapsed, status_code_formatted из elapsed_seconds/status_code; func_name → function_name."""
    extra = record["extra"]

    if extra.get("function_name") is None and extra.get("func_name") is not None:
        extra["function_name"] = extra["func_name"]
    try:
        es = extra.get("elapsed")
        if es is not None and isinstance(es, (int, float)):
            color = _request_log_config.get_speed_color(float(es))
            extra["elapsed"] = f"{_hex_to_ansi_fg(color)}{float(es):6.2f}с{_ANSI_RESET}"
        else:
            extra.setdefault("elapsed", "")
    except Exception:
        extra.setdefault("elapsed", "")
    try:
        sc = extra.get("status_code")
        if sc is not None:
            code_text, code_fg, code_bg, code_bold = _request_log_config.get_status_palette(int(sc))
            extra["status_code_formatted"] = _wrap_status_code_ansi(code_text, code_fg, code_bg, code_bold)
        else:
            extra.setdefault("status_code_formatted", "")
    except Exception:
        extra.setdefault("status_code_formatted", "")
    return True


# =============================================================================
# Автобилд формата из EXTRA_FIELDS (logger1-стиль)
# =============================================================================

def build_console_format() -> str:
    """Собирает формат из LogConfig: только EXTRA_FIELDS с visible=True, цвета из COLORS (hex или имя, напр. magenta)."""
    time_color = LogConfig.COLORS.get("time", "#5c6370")
    parts = [
        f"<fg {time_color}>{{time:HH:mm:ss}}</fg {time_color}>",
        "<level>{level.icon} {level.name:<8}</level>",

    ]
    for f in LogConfig.EXTRA_FIELDS:
        if not f.get("visible", True):
            continue
        key = f["extra"]
        length = f.get("len")
        color_key = f.get("color")
        color = LogConfig.COLORS.get(color_key, "#abb2bf") if color_key else None
        if color_key and color_key not in LogConfig.COLORS and isinstance(color_key, str) and color_key.startswith("#"):
            color = color_key
        if key == "worker_id":
            seg = f"<fg {color}>W{{extra[worker_id]}}</fg {color}>" if color else "W{extra[worker_id]}"
        elif length is not None:
            seg = f"<fg {color}>{{extra[{key}]:<{length}}}</fg {color}>" if color else f"{{extra[{key}]:<{length}}}"
        else:
            seg = f"<fg {color}>{{extra[{key}]}}</fg {color}>" if color else f"{{extra[{key}]}}"
        parts.append(seg)
    parts.append("<level>{message}</level>")
    return " | ".join(parts)


# =============================================================================
# Автодефолты для extra (log_test-стиль)
# =============================================================================

def extract_extra_fields_from_format(format_string: str) -> Set[str]:
    """Извлекает имена полей extra из строки формата (extra[field_name])."""
    formatter = Formatter()
    out: Set[str] = set()
    for _, field_name, _, _ in formatter.parse(format_string):
        if field_name and field_name.startswith("extra[") and field_name.endswith("]"):
            out.add(field_name[6:-1])
    return out


def get_extra_defaults_from_config() -> Dict[str, Any]:
    """Дефолты для полей: из EXTRA_FIELDS['default'] и общий EXTRA_DEFAULT_VALUE."""
    custom: Dict[str, Any] = {}
    for f in LogConfig.EXTRA_FIELDS:
        if not f.get("visible", True):
            continue
        key = f.get("extra")
        if key and "default" in f:
            custom[key] = f["default"]
    return custom


def create_default_filter(
    format_string: str,
    custom_defaults: Optional[Dict[str, Any]] = None,
    default_value: Any = None,
) -> Any:
    """Фильтр: проставляет в record['extra'] дефолты для всех полей из формата."""
    if default_value is None:
        default_value = LogConfig.EXTRA_DEFAULT_VALUE
    required = extract_extra_fields_from_format(format_string)
    custom_defaults = custom_defaults or {}
    defaults = {**get_extra_defaults_from_config(), **custom_defaults}

    def default_filter(record: Dict[str, Any]) -> bool:
        for fld in required:
            record["extra"].setdefault(fld, defaults.get(fld, default_value))
        return True

    return default_filter


# =============================================================================
# Контекст логирования: один ContextVar на модуль — подмешивается при любом
# вызове stdlib logging (InterceptHandler), чтобы id/worker_id и т.д. пробрасывались
# без участия вызывающего кода (декораторы, сторонние библиотеки).
# =============================================================================

LOGGING_CONTEXT: ContextVar[dict] = ContextVar("logging_context", default={})


# =============================================================================
# ContextLogger: контекст + авто-биндинг полей из extra (log_test)
# =============================================================================

class ContextLogger:
    """Логгер с контекстом и авто-подхватом полей из extra (AUTO_BIND_FIELDS)."""

    AUTO_BIND_FIELDS: Set[str] = {"ip", "id", "worker_name"}

    def __init__(self) -> None:
        self._context_logger: ContextVar = ContextVar("context_logger", default=_loguru_logger)
        self._context_data: ContextVar[dict] = LOGGING_CONTEXT

    def bind(self, **kwargs: Any) -> "ContextLogger":
        current = self._context_data.get().copy()
        current.update(kwargs)
        self._context_data.set(current)
        self._context_logger.set(_loguru_logger.bind(**current))
        return self

    def unbind(self, *keys: str) -> "ContextLogger":
        current = self._context_data.get().copy()
        for k in keys:
            current.pop(k, None)
        self._context_data.set(current)
        self._context_logger.set(_loguru_logger.bind(**current))
        return self

    def clear_context(self) -> "ContextLogger":
        self._context_data.set({})
        self._context_logger.set(_loguru_logger)
        return self

    def get_context(self) -> dict:
        return self._context_data.get().copy()

    def _auto_bind_from_extra(self, **kwargs: Any) -> None:
        auto = {k: v for k, v in kwargs.items() if k in self.AUTO_BIND_FIELDS}
        if auto:
            self.bind(**auto)

    def _log(self, level: str, message: str, *args: Any, **kwargs: Any) -> None:
        self._auto_bind_from_extra(**kwargs)
        getattr(self._context_logger.get(), level)(message, *args, **kwargs)

    def trace(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("trace", message, *args, **kwargs)

    def debug(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("debug", message, *args, **kwargs)

    def info(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("info", message, *args, **kwargs)

    def success(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("success", message, *args, **kwargs)

    def warning(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("warning", message, *args, **kwargs)

    def error(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("error", message, *args, **kwargs)

    def critical(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._log("critical", message, *args, **kwargs)

    def exception(self, message: str, *args: Any, **kwargs: Any) -> None:
        self._auto_bind_from_extra(**kwargs)
        self._context_logger.get().exception(message, *args, **kwargs)

    def log(self, level: Any, message: str, *args: Any, **kwargs: Any) -> None:
        self._auto_bind_from_extra(**kwargs)
        self._context_logger.get().log(level, message, *args, **kwargs)

    def __getattr__(self, name: str) -> Any:
        return getattr(self._context_logger.get(), name)


# =============================================================================
# Сборка формата и фильтров, синглтон логгера
# =============================================================================

LOG_FORMAT = build_console_format()

_default_filter_fn = create_default_filter(LOG_FORMAT)


def _combined_filter(record: Dict[str, Any]) -> bool:
    # 1. Сначала авто-определение функции
    context_function_filter(record)
    # 2. Потом дефолты для остальных полей
    _default_filter_fn(record)
    # 3. Форматирование запросов
    request_format_filter(record)
    return True

# def _combined_filter(record: Dict[str, Any]) -> bool:
#     _default_filter_fn(record)
#     record["extra"]["level_icon"] = LEVEL_ICONS.get(record["level"].name, "?")
#
#     request_format_filter(record)
#     return True


logger = ContextLogger()


def setup_logger(
    sink: Any = sys.stdout,
    level: str = "INFO",
    colorize: bool = True,
) -> None:
    """
    Удаляет дефолтный handler, регистрирует уровни из LogConfig.LEVELS,
    добавляет один handler: формат из EXTRA_FIELDS + автодефолты + request_format.
    Вызовите один раз при старте приложения.
    """
    _loguru_logger.remove()
    for level_name, level_config in LogConfig.LEVELS.items():
        _loguru_logger.level(level_name, icon=level_config["icon"], color=level_config["color"])
    _loguru_logger.add(
        sink,
        format=LOG_FORMAT,
        colorize=colorize,
        level=level,
        filter=_combined_filter,
    )


# =============================================================================
# Перехват stdlib logging → loguru (extra пробрасываются)
# =============================================================================

_STD_RECORD_KEYS = frozenset({
    "name", "msg", "args", "created", "filename", "funcName", "levelname", "levelno",
    "lineno", "module", "msecs", "message", "pathname", "process", "processName",
    "relativeCreated", "thread", "threadName", "exc_info", "exc_text", "stack_info", "taskName",
})


class InterceptHandler(logging.Handler):
    """Перехват logging → loguru с пробросом extra и подмешиванием текущего контекста (id, worker_id и т.д.)."""

    def emit(self, record: logging.LogRecord) -> None:
        try:
            level = _loguru_logger.level(record.levelname).name
        except ValueError:
            level = record.levelno
        extra_from_record = {k: v for k, v in record.__dict__.items() if k not in _STD_RECORD_KEYS}
        # Контекст из LOGGING_CONTEXT подмешивается первым, extra из вызова — поверх (приоритет у вызова)
        context = LOGGING_CONTEXT.get().copy()
        extra_fields = {**context, **extra_from_record}
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1
        _loguru_logger.opt(depth=depth, exception=record.exc_info).bind(**extra_fields).log(level, record.getMessage())


logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO, force=True)


# Отключаем логи от сторонних библиотек
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("aiohttp").setLevel(logging.WARNING)
logging.getLogger("fox_proxy_kit").setLevel(logging.WARNING)
logging.getLogger("pydoll").setLevel(logging.WARNING)

# Настраиваем перехват логов только из нужных модулей

logger.remove()

# # Применяем настройки уровней логирования
for level_name, level_config in LogConfig.LEVELS.items():
    logger.level(level_name, icon=level_config["icon"], color=level_config["color"])

_loguru_logger.add(
    sys.stdout,
    colorize=True,
    format=LOG_FORMAT,
    level="INFO",
    filter=_combined_filter,
)

if __name__ == "__main__":

    # Устанавливаем user_id
    logger.bind(user_id=12345)
    logger.info("Лог с новым форматом - new_field автоматически имеет дефолт!")

    print("📍 Подключаемся к прокси\n")

    # ✅ Библиотека логирует с ip - он АВТОМАТИЧЕСКИ попадает в контекст!

    print(f"\n✅ Контекст: {logger.get_context()}\n")
    logger.bind(six=12345)

    # ✅ Теперь все последующие логи АВТОМАТИЧЕСКИ содержат ip!
    logger.info("Делаю запросы через прокси")

    print("\n📍 Добавим новое поле в формат и оно автоматически получит дефолт!\n")


    logger.info("Лог с новым форматом - new_field автоматически имеет дефолт!")
    logger.info("Можно задать значение", new_field="CUSTOM_VALUE")
