"""Structured logging setup using structlog with Rich console + JSON file output."""

from __future__ import annotations

import logging
import sys
from collections import deque
from pathlib import Path

import structlog


class RingBufferHandler(logging.Handler):
    """Stores recent formatted log lines in a fixed-size ring buffer for live display."""

    def __init__(self, capacity: int = 12, level: int = logging.WARNING):
        super().__init__(level)
        self.records: deque[str] = deque(maxlen=capacity)
        self.setFormatter(structlog.stdlib.ProcessorFormatter(
            processors=[
                structlog.stdlib.ProcessorFormatter.remove_processors_meta,
                structlog.dev.ConsoleRenderer(colors=True, pad_event=0),
            ],
        ))

    def emit(self, record: logging.LogRecord) -> None:
        try:
            self.records.append(self.format(record))
        except Exception:
            self.handleError(record)


def setup_logging(
    level: str = "INFO",
    log_file: Path | None = None,
    console_level: str = "ERROR",
    live_handler: logging.Handler | None = None,
) -> None:
    """Configure structlog with console (Rich) and optional JSON-lines file output.

    Args:
        level: Log level for the file handler (and root minimum).
        log_file: Optional path for JSON-lines log output.
        console_level: Log level for the console handler (default ERROR to avoid
            corrupting Rich progress/live displays). Pass the same value as *level*
            for verbose mode (``--verbose``).
        live_handler: Optional pre-configured handler (e.g. ``RingBufferHandler``)
            to attach to the root logger for in-display log capture.
    """
    log_level = getattr(logging, level.upper(), logging.INFO)
    console_log_level = getattr(logging, console_level.upper(), logging.WARNING)

    shared_processors: list[structlog.types.Processor] = [
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.add_log_level,
        structlog.stdlib.add_logger_name,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
    ]

    structlog.configure(
        processors=[
            *shared_processors,
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )

    console_formatter = structlog.stdlib.ProcessorFormatter(
        processors=[
            structlog.stdlib.ProcessorFormatter.remove_processors_meta,
            structlog.dev.ConsoleRenderer(colors=sys.stderr.isatty()),
        ],
    )

    console_handler = logging.StreamHandler(sys.stderr)
    console_handler.setFormatter(console_formatter)

    console_handler.setLevel(console_log_level)

    root = logging.getLogger()
    root.handlers.clear()
    root.addHandler(console_handler)
    # Root level must be the minimum of all handlers so each receives its events
    min_level = min(log_level, console_log_level)
    if live_handler:
        min_level = min(min_level, live_handler.level)
    root.setLevel(min_level)

    if log_file:
        log_file.parent.mkdir(parents=True, exist_ok=True)
        json_formatter = structlog.stdlib.ProcessorFormatter(
            processors=[
                structlog.stdlib.ProcessorFormatter.remove_processors_meta,
                structlog.processors.JSONRenderer(),
            ],
        )
        file_handler = logging.FileHandler(str(log_file))
        file_handler.setLevel(log_level)
        file_handler.setFormatter(json_formatter)
        root.addHandler(file_handler)

    if live_handler:
        root.addHandler(live_handler)

    # Silence noisy libraries
    logging.getLogger("aiohttp").setLevel(logging.WARNING)
    logging.getLogger("filelock").setLevel(logging.WARNING)
