"""Smart logging tiers (v0.34.0 Part A).

Maps a user-facing `--log-level` flag (quiet/normal/verbose/debug) onto
Python's stdlib logging levels and installs a Rich-formatted handler on the
top-level "soup" logger.

Tier semantics:
    quiet   -> only ERROR and worse; drop info/warning chatter
    normal  -> WARNING (default; matches prior CLI behaviour)
    verbose -> INFO; per-step monitoring messages
    debug   -> DEBUG; trainer internals + library warnings surface
"""

from __future__ import annotations

import enum
import logging
from typing import Tuple

LOGGER_NAME = "soup"


class LogLevel(enum.Enum):
    QUIET = "quiet"
    NORMAL = "normal"
    VERBOSE = "verbose"
    DEBUG = "debug"


LOG_LEVELS: Tuple[str, ...] = tuple(level.value for level in LogLevel)


_PY_LEVEL_BY_TIER = {
    LogLevel.QUIET: logging.ERROR,
    LogLevel.NORMAL: logging.WARNING,
    LogLevel.VERBOSE: logging.INFO,
    LogLevel.DEBUG: logging.DEBUG,
}


def parse_log_level(value: str) -> LogLevel:
    """Parse a CLI string into a LogLevel. Case-insensitive."""
    if not isinstance(value, str):
        raise ValueError(f"log level must be a string, got {type(value).__name__}")
    if "\x00" in value:
        raise ValueError("log level contains null byte")
    try:
        return LogLevel(value.strip().lower())
    except ValueError as exc:
        valid = ", ".join(LOG_LEVELS)
        raise ValueError(f"invalid log level {value!r}; choose one of: {valid}") from exc


def resolve_python_log_level(tier: LogLevel) -> int:
    """Map a tier onto a stdlib logging level integer."""
    return _PY_LEVEL_BY_TIER[tier]


def setup_logging(tier: LogLevel) -> logging.Logger:
    """Configure the top-level "soup" logger for the given tier.

    Idempotent — repeated calls update the level. When the tier changes
    relative to a prior call (e.g. NORMAL → DEBUG), the existing handler
    is replaced so format toggles like ``show_time``/``show_path`` reflect
    the new tier.
    """
    py_level = resolve_python_log_level(tier)
    logger = logging.getLogger(LOGGER_NAME)
    logger.setLevel(py_level)

    # Drop any prior soup handler whose tier no longer matches so we can
    # rebuild with the right RichHandler kwargs for the new tier.
    for existing in list(logger.handlers):
        if getattr(existing, "_soup_log_tier", None) is not None:
            if getattr(existing, "_soup_log_tier", None) != tier:
                logger.removeHandler(existing)

    if not any(getattr(handler, "_soup_log_tier", None) == tier for handler in logger.handlers):
        handler: logging.Handler
        try:
            from rich.logging import RichHandler

            handler = RichHandler(
                show_time=tier == LogLevel.DEBUG,
                show_path=tier == LogLevel.DEBUG,
                rich_tracebacks=True,
                markup=False,
            )
        except Exception:  # pragma: no cover - rich is a hard dep, defensive
            handler = logging.StreamHandler()
            handler.setFormatter(
                logging.Formatter("%(levelname)s %(name)s: %(message)s")
            )
        handler._soup_log_tier = tier  # type: ignore[attr-defined]
        logger.addHandler(handler)
        logger.propagate = False

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

    return logger


def apply_logging_level(tier: LogLevel) -> None:
    """Set the *root* logger level so non-soup libraries respect the tier (#G2/N1).

    ``setup_logging`` only configures the ``soup`` logger; library logs
    (``transformers``, ``peft``, ``trl``) flow through the root logger.
    Without this, ``--log-level quiet`` left third-party INFO chatter on
    stdout. ``apply_logging_level`` plumbs the tier into the root level so
    QUIET silences libraries and DEBUG surfaces them.
    """
    py_level = resolve_python_log_level(tier)
    logging.getLogger().setLevel(py_level)
