from __future__ import annotations

import logging
from logging.config import dictConfig
from pathlib import Path

import structlog

from core.config.settings import RuntimeSettings, Settings
from core.logging.formatters import (
    build_plain_formatter,
    build_processor_formatter,
    ensure_message_key,
)
from core.logging.filters import build_sensitive_data_processor
from core.logging.handlers import build_file_handler_config


def _ensure_log_dirs(runtime: RuntimeSettings) -> None:
    Path(runtime.log_dir).mkdir(parents=True, exist_ok=True)
    Path(runtime.log_error_dir).mkdir(parents=True, exist_ok=True)


def build_logging_config(runtime: RuntimeSettings) -> dict[str, object]:
    log_dir = Path(runtime.log_dir)
    error_dir = Path(runtime.log_error_dir)
    formatter_name = "json" if runtime.log_json else "plain"

    file_handler = build_file_handler_config(
        runtime,
        file_path=log_dir / runtime.log_file_name,
        level=runtime.log_level,
        formatter=formatter_name,
    )
    error_handler = build_file_handler_config(
        runtime,
        file_path=error_dir / runtime.log_error_file_name,
        level="ERROR",
        formatter=formatter_name,
        filters=["error_only"],
    )

    return {
        "version": 1,
        "disable_existing_loggers": False,
        "filters": {
            "error_only": {
                "()": "core.logging.filters.ErrorLevelFilter",
            }
        },
        "formatters": {
            "json": {
                "()": build_processor_formatter,
                "sensitive_fields": runtime.log_sensitive_fields,
            },
            "plain": {
                "()": build_plain_formatter,
                "sensitive_fields": runtime.log_sensitive_fields,
            },
        },
        "handlers": {
            "file": file_handler,
            "error": error_handler,
        },
        "root": {
            "handlers": ["file", "error"],
            "level": runtime.log_level,
        },
    }


def configure_logging(settings: Settings | None = None) -> None:
    active_settings = settings or Settings()
    runtime = active_settings.runtime

    try:
        _ensure_log_dirs(runtime)
        dictConfig(build_logging_config(runtime))
    except (OSError, ValueError) as exc:
        logging.basicConfig(level=runtime.log_level)
        logging.getLogger(__name__).error("Logging setup failed", exc_info=exc)

    structlog.configure(
        processors=[
            structlog.contextvars.merge_contextvars,
            structlog.processors.add_log_level,
            structlog.processors.TimeStamper(fmt="iso", utc=True),
            structlog.processors.CallsiteParameterAdder(
                parameters=[
                    structlog.processors.CallsiteParameter.MODULE,
                    structlog.processors.CallsiteParameter.FUNC_NAME,
                    structlog.processors.CallsiteParameter.LINENO,
                ]
            ),
            build_sensitive_data_processor(runtime.log_sensitive_fields),
            ensure_message_key,
            structlog.processors.format_exc_info,
            structlog.processors.UnicodeDecoder(),
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )
