"""
Monitoring and logging configuration utilities.

This module provides structured logging setup using structlog.
"""

import logging
from enum import StrEnum
from typing import Any

import structlog
from opentelemetry import trace
from pydantic import BaseModel
from structlog.typing import EventDict, Processor


class Env(StrEnum):
    DEV = "development"
    STAGING = "staging"
    PROD = "production"


class LogFormat(StrEnum):
    JSON = "json"
    CONSOLE = "console"


class LogLevel(StrEnum):
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class LoggerConfig(BaseModel):
    name: str
    level: LogLevel


def _get_log_level_value(level: LogLevel) -> int:
    level_map = {
        LogLevel.DEBUG: logging.DEBUG,
        LogLevel.INFO: logging.INFO,
        LogLevel.WARNING: logging.WARNING,
        LogLevel.ERROR: logging.ERROR,
        LogLevel.CRITICAL: logging.CRITICAL,
    }
    return level_map[level]


def _add_otel_trace_processor(logger: Any, method_name: str, event_dict: EventDict) -> EventDict:
    span = trace.get_current_span()
    if span:
        ctx = span.get_span_context()
        if ctx.is_valid:
            event_dict["otel.trace_id"] = format(ctx.trace_id, "032x")
            event_dict["otel.span_id"] = format(ctx.span_id, "016x")

    return event_dict


def setup_logging(
    log_level: LogLevel = LogLevel.INFO,
    log_format: LogFormat = LogFormat.CONSOLE,
    app_version: str = "0.0.0",
    inject_otel_trace: bool = False,
    extra_config: list[LoggerConfig] | None = None,
) -> None:
    logging.basicConfig(
        format="%(message)s",
        level=_get_log_level_value(log_level),
    )

    if extra_config:
        for logger_config in extra_config:
            logger = logging.getLogger(logger_config.name)
            logger.setLevel(_get_log_level_value(logger_config.level))

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

    if inject_otel_trace:
        processors.append(_add_otel_trace_processor)

    processors.append(
        structlog.processors.CallsiteParameterAdder(
            {
                structlog.processors.CallsiteParameter.PATHNAME,
                structlog.processors.CallsiteParameter.LINENO,
            }
        )
    )

    if log_format == LogFormat.JSON:
        processors.extend(
            [
                structlog.processors.format_exc_info,
                structlog.processors.JSONRenderer(),
            ]
        )
    else:
        processors.extend(
            [
                structlog.processors.format_exc_info,
                structlog.dev.ConsoleRenderer(),
            ]
        )

    structlog.configure(
        processors=processors,
        wrapper_class=structlog.stdlib.BoundLogger,
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )
