"""Structured logging via structlog.

Emits key=value (dev) or JSON (prod) with a per-request/task ``correlation_id``
bound through a contextvar. Call :func:`configure_logging` once at startup.
"""

import logging
from contextvars import ContextVar
from typing import cast

import structlog
from structlog.typing import EventDict, FilteringBoundLogger, Processor, WrappedLogger

# Bound by middleware (HTTP) and worker entrypoints (tasks) so every log line
# downstream carries the same id without explicit passing.
correlation_id: ContextVar[str | None] = ContextVar("correlation_id", default=None)


def _add_correlation_id(_logger: WrappedLogger, _method: str, event_dict: EventDict) -> EventDict:
    cid = correlation_id.get()
    if cid is not None:
        event_dict["correlation_id"] = cid
    return event_dict


def configure_logging(*, level: str = "INFO", json: bool = False) -> None:
    shared: list[Processor] = [
        structlog.contextvars.merge_contextvars,
        _add_correlation_id,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso", utc=True),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
    ]
    renderer = (
        structlog.processors.JSONRenderer() if json else structlog.dev.ConsoleRenderer(colors=True)
    )

    structlog.configure(
        processors=[*shared, renderer],
        wrapper_class=structlog.make_filtering_bound_logger(logging.getLevelName(level.upper())),
        logger_factory=structlog.PrintLoggerFactory(),
        cache_logger_on_first_use=True,
    )
    # Route stdlib logging (uvicorn, sqlalchemy) through structlog formatting.
    logging.basicConfig(level=level.upper(), format="%(message)s")


def get_logger(name: str | None = None) -> FilteringBoundLogger:
    return cast(FilteringBoundLogger, structlog.get_logger(name))
