"""Configuration module for django-o11y.

Provides sensible defaults and validates user configuration from Django settings.

Environment variables follow a single consistent prefix: `DJANGO_O11Y_<SECTION>_<KEY>`.
The three OpenTelemetry spec vars are also honoured where they naturally map:

  OTEL_SERVICE_NAME              → SERVICE_NAME
  OTEL_EXPORTER_OTLP_ENDPOINT    → TRACING.OTLP_ENDPOINT, LOGGING.OTLP_ENDPOINT
  OTEL_TRACES_SAMPLER_ARG        → TRACING.SAMPLE_RATE

All other env vars use the ``DJANGO_O11Y_`` prefix, e.g.::

  DJANGO_O11Y_ENVIRONMENT=production
  DJANGO_O11Y_TRACING_ENABLED=true
  DJANGO_O11Y_LOGGING_LEVEL=WARNING
  DJANGO_O11Y_PROFILING_ENABLED=true
"""

import os
from functools import lru_cache
from typing import Any

from django.conf import settings

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _bool_env(key: str, default: bool) -> bool:
    value = os.getenv(key)
    if value is None:
        return default
    return value.lower() in ("true", "1", "yes", "on")


def _float_env(key: str, default: float) -> float:
    value = os.getenv(key)
    if value is None:
        return default
    return float(value)


def _set_str(config: dict, key: str, env: str) -> None:
    """Set config[key] from env var if present."""
    if (v := os.getenv(env)) is not None:
        config[key] = v


def _set_bool(config: dict, key: str, env: str, default: bool = False) -> None:
    """Set config[key] from bool env var if present."""
    if os.getenv(env) is not None:
        config[key] = _bool_env(env, default)


def _set_float(config: dict, key: str, env: str, default: float = 0.0) -> None:
    """Set config[key] from float env var if present."""
    if os.getenv(env) is not None:
        config[key] = _float_env(env, default)


# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------


def get_config() -> dict[str, Any]:
    """
    Return the merged django-o11y configuration.

    Priority (lowest → highest):
    1. Hardcoded defaults
    2. DJANGO_O11Y settings dict
    3. Environment variables
    """
    default_sample_rate = 1.0 if settings.DEBUG else 0.01

    defaults: dict[str, Any] = {
        "SERVICE_NAME": "django-app",
        "SERVICE_VERSION": "unknown",
        # None means "compute hostname:pid at tracing setup time so forked
        # workers get their own pid rather than the master's".
        # Set OTEL_SERVICE_INSTANCE_ID to fix a specific value (e.g. pod name).
        "SERVICE_INSTANCE_ID": None,
        "ENVIRONMENT": "development",
        "NAMESPACE": "",
        "RESOURCE_ATTRIBUTES": {},
        "CUSTOM_TAGS": {},
        "TRACING": {
            "ENABLED": False,
            "OTLP_ENDPOINT": "http://localhost:4317",
            "SAMPLE_RATE": default_sample_rate,
            "CONSOLE_EXPORTER": False,
            "AWS_ENABLED": False,
        },
        "LOGGING": {
            "FORMAT": "json" if not settings.DEBUG else "console",
            "LEVEL": "INFO",
            "REQUEST_LEVEL": "INFO",
            "DATABASE_LEVEL": "WARNING",
            "CELERY_LEVEL": "INFO",
            "PARSO_LEVEL": "WARNING",
            "AWS_LEVEL": "WARNING",
            "COLORIZED": settings.DEBUG,
            "RICH_EXCEPTIONS": True,
            "OTLP_ENABLED": False,
            "OTLP_ENDPOINT": "http://localhost:4317",
            "FILE_ENABLED": settings.DEBUG,
            "FILE_PATH": "/tmp/django-o11y/django.log",
        },
        "METRICS": {
            "PROMETHEUS_ENABLED": True,
            "PROMETHEUS_ENDPOINT": "/metrics",
            "EXPORT_MIGRATIONS": True,
        },
        "CELERY": {
            "ENABLED": False,
            "TRACING_ENABLED": True,
            "LOGGING_ENABLED": True,
            "METRICS_ENABLED": True,
        },
        "PROFILING": {
            "ENABLED": False,
            "PYROSCOPE_URL": "http://localhost:4040",
            "TAGS": {},
        },
    }

    user_config = getattr(settings, "DJANGO_O11Y", {})
    merged = _deep_merge(defaults, user_config)
    _apply_env_overrides(merged, default_sample_rate)
    return merged


def _apply_env_overrides(config: dict[str, Any], default_sample_rate: float) -> None:
    """Apply environment variable overrides in-place (highest priority)."""
    t, lg, m, c, p = (
        config["TRACING"],
        config["LOGGING"],
        config["METRICS"],
        config["CELERY"],
        config["PROFILING"],
    )

    # Top-level
    _set_str(config, "SERVICE_NAME", "OTEL_SERVICE_NAME")
    _set_str(config, "SERVICE_VERSION", "OTEL_SERVICE_VERSION")
    _set_str(config, "ENVIRONMENT", "DJANGO_O11Y_ENVIRONMENT")
    _set_str(config, "NAMESPACE", "DJANGO_O11Y_NAMESPACE")
    if (v := os.getenv("OTEL_SERVICE_INSTANCE_ID")) is not None:
        config["SERVICE_INSTANCE_ID"] = v or None

    # OTLP endpoint fans out to both tracing and logging
    if (v := os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")) is not None:
        t["OTLP_ENDPOINT"] = v
        lg["OTLP_ENDPOINT"] = v

    # Tracing
    _set_bool(t, "ENABLED", "DJANGO_O11Y_TRACING_ENABLED")
    _set_float(t, "SAMPLE_RATE", "OTEL_TRACES_SAMPLER_ARG", default_sample_rate)
    _set_bool(t, "CONSOLE_EXPORTER", "DJANGO_O11Y_TRACING_CONSOLE_EXPORTER")
    _set_bool(t, "AWS_ENABLED", "DJANGO_O11Y_TRACING_AWS_ENABLED")

    # Logging
    _set_str(lg, "FORMAT", "DJANGO_O11Y_LOGGING_FORMAT")
    _set_str(lg, "LEVEL", "DJANGO_O11Y_LOGGING_LEVEL")
    _set_str(lg, "REQUEST_LEVEL", "DJANGO_O11Y_LOGGING_REQUEST_LEVEL")
    _set_str(lg, "DATABASE_LEVEL", "DJANGO_O11Y_LOGGING_DATABASE_LEVEL")
    _set_str(lg, "CELERY_LEVEL", "DJANGO_O11Y_LOGGING_CELERY_LEVEL")
    _set_str(lg, "PARSO_LEVEL", "DJANGO_O11Y_LOGGING_PARSO_LEVEL")
    _set_str(lg, "AWS_LEVEL", "DJANGO_O11Y_LOGGING_AWS_LEVEL")
    _set_str(lg, "FILE_PATH", "DJANGO_O11Y_LOGGING_FILE_PATH")
    _set_bool(lg, "COLORIZED", "DJANGO_O11Y_LOGGING_COLORIZED", settings.DEBUG)
    _set_bool(lg, "RICH_EXCEPTIONS", "DJANGO_O11Y_LOGGING_RICH_EXCEPTIONS", True)
    _set_bool(lg, "OTLP_ENABLED", "DJANGO_O11Y_LOGGING_OTLP_ENABLED")
    _set_bool(lg, "FILE_ENABLED", "DJANGO_O11Y_LOGGING_FILE_ENABLED", settings.DEBUG)

    # Metrics
    _set_bool(m, "PROMETHEUS_ENABLED", "DJANGO_O11Y_METRICS_PROMETHEUS_ENABLED", True)
    _set_str(m, "PROMETHEUS_ENDPOINT", "DJANGO_O11Y_METRICS_PROMETHEUS_ENDPOINT")
    _set_bool(m, "EXPORT_MIGRATIONS", "DJANGO_O11Y_METRICS_EXPORT_MIGRATIONS", True)

    # Celery
    _set_bool(c, "ENABLED", "DJANGO_O11Y_CELERY_ENABLED")
    _set_bool(c, "TRACING_ENABLED", "DJANGO_O11Y_CELERY_TRACING_ENABLED", True)
    _set_bool(c, "LOGGING_ENABLED", "DJANGO_O11Y_CELERY_LOGGING_ENABLED", True)
    _set_bool(c, "METRICS_ENABLED", "DJANGO_O11Y_CELERY_METRICS_ENABLED", True)

    # Profiling
    _set_bool(p, "ENABLED", "DJANGO_O11Y_PROFILING_ENABLED")
    _set_str(p, "PYROSCOPE_URL", "DJANGO_O11Y_PROFILING_PYROSCOPE_URL")


def _deep_merge(default: dict, override: dict) -> dict:
    """Deep merge two dictionaries."""
    result = default.copy()
    for key, value in override.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = _deep_merge(result[key], value)
        else:
            result[key] = value
    return result


@lru_cache(maxsize=1)
def get_o11y_config() -> dict[str, Any]:
    """Get the global o11y configuration (cached via lru_cache)."""
    return get_config()
