"""
Centralised logging configuration using structlog.

Goals (Story 6.4: Structlog Integration):
- JSON logs in production with fields: timestamp, level, event, request_id, user_id
- Pretty console logs in development
- Single configuration entry-point called from main.py
- Context helpers to bind request_id / user_id per request
"""

from __future__ import annotations

import logging
import os
import sys
from typing import Optional

import structlog
from structlog.contextvars import bind_contextvars, clear_contextvars, merge_contextvars


def configure_logging(
    *,
    json_logs: bool = True,
    log_level: str = "INFO",
) -> None:
    """
    Configure structlog + stdlib logging.

    - json_logs=True  → JSONRenderer (production, Docker-friendly)
    - json_logs=False → ConsoleRenderer (development)
    - log_level driven by LOG_LEVEL env (INFO/DEBUG/WARNING/ERROR/CRITICAL)
    """
    level = getattr(logging, log_level.upper(), logging.INFO)

    timestamper = structlog.processors.TimeStamper(fmt="iso")

    # Common processor chain (used both for structlog and stdlib records)
    pre_chain = [
        merge_contextvars,  # adds request_id / user_id when bound
        structlog.stdlib.add_log_level,
        timestamper,
        structlog.processors.format_exc_info,
    ]

    if json_logs:
        renderer = structlog.processors.JSONRenderer()
    else:
        # Developer-friendly console output
        renderer = structlog.dev.ConsoleRenderer(colors=True)

    processor_formatter = structlog.stdlib.ProcessorFormatter(
        processor=renderer,
        foreign_pre_chain=pre_chain,
    )

    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(processor_formatter)

    # Reset root logger handlers and attach our handler
    root_logger = logging.getLogger()
    root_logger.handlers.clear()
    root_logger.addHandler(handler)
    root_logger.setLevel(level)

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


def get_logger(name: Optional[str] = None):
    """
    Return a structlog logger.

    Fallbacks to stdlib logger if structlog misconfigured for any reason.
    """
    try:
        return structlog.get_logger(name) if name is not None else structlog.get_logger()
    except Exception:  # pragma: no cover - defensive fallback
        return logging.getLogger(name)


def bind_request_context(
    *,
    request_id: Optional[str] = None,
    user_id: Optional[str] = None,
) -> None:
    """
    Bind per-request context so every log line includes these fields.
    Safe to call multiple times; only provided keys are updated.
    """
    kv = {}
    if request_id:
        kv["request_id"] = request_id
    if user_id:
        kv["user_id"] = user_id
    if kv:
        bind_contextvars(**kv)


def clear_request_context() -> None:
    """Clear contextvars after a request has been processed."""
    clear_contextvars()

