import atexit
import logging
import logging.handlers
import os
import pathlib
from logging.handlers import QueueListener, TimedRotatingFileHandler
from queue import Queue

from src.config.settings import settings_instance

# === Constants ===
PROJECT_TITLE = settings_instance.PROJECT_TITLE.lower().replace(" ", "_")

# Absolute path — resolves to <project_root>/logs regardless of cwd
LOG_DIR = str(pathlib.Path(__file__).resolve().parent.parent.parent / "logs")

ENCODING = "utf-8"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)s.%(funcName)s:%(lineno)d | %(message)s"
WHEN = "midnight"
BACKUP_COUNT = 30

PACKAGE_LOGGERS: dict[str, int] = {
    "uvicorn": logging.DEBUG,
    "fastapi": logging.DEBUG,
    "sqlalchemy": logging.WARNING,
    "alembic": logging.INFO,
    "websockets": logging.WARNING,
    "gunicorn": logging.DEBUG,
}

log_queue: Queue = Queue(-1)
log_listener: QueueListener | None = None
_file_handlers: list[logging.Handler] = []


class LevelFilter(logging.Filter):
    def __init__(self, level: int):
        self.level = level

    def filter(self, record: logging.LogRecord) -> bool:
        return record.levelno == self.level


class NameFilter(logging.Filter):
    def __init__(self, prefix: str):
        self.prefix = prefix

    def filter(self, record: logging.LogRecord) -> bool:
        return record.name == self.prefix or record.name.startswith(self.prefix + ".")


class ExcludePackagesFilter(logging.Filter):
    def __init__(self, prefixes: tuple[str, ...]):
        self.prefixes = prefixes

    def filter(self, record: logging.LogRecord) -> bool:
        return not any(
            record.name == p or record.name.startswith(p + ".")
            for p in self.prefixes
        )


def _make_handler(filename: str, level: int, log_filter: logging.Filter | None = None) -> TimedRotatingFileHandler:
    handler = TimedRotatingFileHandler(
        filename=os.path.join(LOG_DIR, filename),
        when=WHEN,
        interval=1,
        backupCount=BACKUP_COUNT,
        encoding=ENCODING,
        utc=True,
    )
    handler.setLevel(level)
    handler.setFormatter(logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT))
    if log_filter is not None:
        handler.addFilter(log_filter)
    return handler


def _attach_queue_handler_to_root() -> None:
    """Attach (or re-attach) our QueueHandler to the root logger.

    Called both at startup and inside lifespan so that uvicorn's own
    dictConfig call (which runs between import-time and lifespan) cannot
    displace our handler permanently.
    """
    queue_handler = logging.handlers.QueueHandler(log_queue)

    root = logging.getLogger()
    root.setLevel(logging.DEBUG)

    # Remove any stale QueueHandlers to avoid duplicates on re-attach
    root.handlers = [h for h in root.handlers if not isinstance(h, logging.handlers.QueueHandler)]
    root.addHandler(queue_handler)

    # Ensure per-package loggers propagate (uvicorn sets propagate=False on its loggers)
    _pkg_levels = {
        "uvicorn":         logging.DEBUG,
        "uvicorn.error":   logging.DEBUG,
        "uvicorn.access":  logging.DEBUG,
        "fastapi":         logging.DEBUG,
        "sqlalchemy":      logging.WARNING,
        "alembic":         logging.INFO,
        "websockets":      logging.WARNING,
        "gunicorn":        logging.DEBUG,
        "gunicorn.error":  logging.DEBUG,
        "gunicorn.access": logging.DEBUG,
    }
    for name, level in _pkg_levels.items():
        pkg = logging.getLogger(name)
        pkg.setLevel(level)
        pkg.propagate = True


def setup_logging() -> None:
    """Set up async file logging.

    Safe to call multiple times — handlers and listener are created only
    once; subsequent calls just re-attach the QueueHandler to root.
    """
    global log_listener, _file_handlers

    os.makedirs(LOG_DIR, exist_ok=True)

    if log_listener is None:
        # Build file handlers once
        _file_handlers = []

        for level_name, level_value in [
            ("info", logging.INFO),
            ("warning", logging.WARNING),
            ("error", logging.ERROR),
            ("critical", logging.CRITICAL),
        ]:
            _file_handlers.append(
                _make_handler(f"{PROJECT_TITLE}_{level_name}.log", level_value, LevelFilter(level_value))
            )

        for package, min_level in PACKAGE_LOGGERS.items():
            _file_handlers.append(
                _make_handler(f"{package}.log", min_level, NameFilter(package))
            )

        _file_handlers.append(
            _make_handler(
                f"{PROJECT_TITLE}.log",
                logging.DEBUG,
                ExcludePackagesFilter(tuple(PACKAGE_LOGGERS.keys())),
            )
        )

        log_listener = QueueListener(log_queue, *_file_handlers, respect_handler_level=True)
        log_listener.start()
        atexit.register(log_listener.stop)

    # Always (re-)attach to root in case uvicorn's dictConfig displaced our handler
    _attach_queue_handler_to_root()


def get_logger(name: str) -> logging.Logger:
    return logging.getLogger(name)
