"""Monitoring and observability configuration.

Implements:
- Prometheus metrics integration
- Custom business metrics
- Health check endpoints
- Application performance monitoring
"""

import time
import asyncio
from typing import Optional, Callable
from functools import wraps
from contextlib import contextmanager

from prometheus_client import (
    Counter,
    Histogram,
    Gauge,
    Info,
    generate_latest,
    CONTENT_TYPE_LATEST,
    CollectorRegistry,
)
from fastapi import Request, Response
from fastapi.responses import PlainTextResponse

from src.core.config import settings


# Create custom registry
REGISTRY = CollectorRegistry()


class MetricsCollector:
    """Centralized metrics collection for the application."""

    def __init__(self):
        self._initialized = False
        self._metrics = {}

    def initialize(self):
        """Initialize all metrics."""
        if self._initialized:
            return

        # HTTP metrics
        self._metrics["http_requests_total"] = Counter(
            "http_requests_total",
            "Total HTTP requests",
            ["method", "endpoint", "status_code"],
            registry=REGISTRY,
        )

        self._metrics["http_request_duration_seconds"] = Histogram(
            "http_request_duration_seconds",
            "HTTP request duration in seconds",
            ["method", "endpoint"],
            buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0],
            registry=REGISTRY,
        )

        self._metrics["http_request_size_bytes"] = Histogram(
            "http_request_size_bytes",
            "HTTP request size in bytes",
            ["method", "endpoint"],
            buckets=[100, 1000, 10000, 100000, 1000000],
            registry=REGISTRY,
        )

        self._metrics["http_response_size_bytes"] = Histogram(
            "http_response_size_bytes",
            "HTTP response size in bytes",
            ["method", "endpoint"],
            buckets=[100, 1000, 10000, 100000, 1000000],
            registry=REGISTRY,
        )

        # Database metrics
        self._metrics["db_queries_total"] = Counter(
            "db_queries_total",
            "Total database queries",
            ["operation", "table"],
            registry=REGISTRY,
        )

        self._metrics["db_query_duration_seconds"] = Histogram(
            "db_query_duration_seconds",
            "Database query duration in seconds",
            ["operation", "table"],
            buckets=[0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0],
            registry=REGISTRY,
        )

        self._metrics["db_connections_active"] = Gauge(
            "db_connections_active",
            "Number of active database connections",
            registry=REGISTRY,
        )

        # Cache metrics
        self._metrics["cache_hits_total"] = Counter(
            "cache_hits_total",
            "Total cache hits",
            ["cache_level"],
            registry=REGISTRY,
        )

        self._metrics["cache_misses_total"] = Counter(
            "cache_misses_total",
            "Total cache misses",
            ["cache_level"],
            registry=REGISTRY,
        )

        # Business metrics
        self._metrics["scenarios_created_total"] = Counter(
            "scenarios_created_total",
            "Total scenarios created",
            ["region", "status"],
            registry=REGISTRY,
        )

        self._metrics["scenarios_active"] = Gauge(
            "scenarios_active",
            "Number of active scenarios",
            ["region"],
            registry=REGISTRY,
        )

        self._metrics["reports_generated_total"] = Counter(
            "reports_generated_total",
            "Total reports generated",
            ["format"],
            registry=REGISTRY,
        )

        self._metrics["reports_generation_duration_seconds"] = Histogram(
            "reports_generation_duration_seconds",
            "Report generation duration in seconds",
            ["format"],
            buckets=[1.0, 2.5, 5.0, 10.0, 30.0, 60.0, 120.0, 300.0],
            registry=REGISTRY,
        )

        self._metrics["api_keys_active"] = Gauge(
            "api_keys_active",
            "Number of active API keys",
            registry=REGISTRY,
        )

        self._metrics["users_registered_total"] = Counter(
            "users_registered_total",
            "Total users registered",
            registry=REGISTRY,
        )

        self._metrics["auth_attempts_total"] = Counter(
            "auth_attempts_total",
            "Total authentication attempts",
            ["type", "success"],
            registry=REGISTRY,
        )

        # Celery metrics
        self._metrics["celery_task_started"] = Counter(
            "celery_task_started",
            "Celery tasks started",
            ["task"],
            registry=REGISTRY,
        )

        self._metrics["celery_task_completed"] = Counter(
            "celery_task_completed",
            "Celery tasks completed",
            ["task", "state"],
            registry=REGISTRY,
        )

        self._metrics["celery_task_failed"] = Counter(
            "celery_task_failed",
            "Celery tasks failed",
            ["task", "exception"],
            registry=REGISTRY,
        )

        # System metrics
        self._metrics["app_info"] = Info(
            "app_info",
            "Application information",
            registry=REGISTRY,
        )

        self._metrics["app_info"].info(
            {
                "version": getattr(settings, "app_version", "1.0.0"),
                "name": getattr(settings, "app_name", "mockupAWS"),
                "environment": "production"
                if not getattr(settings, "debug", False)
                else "development",
            }
        )

        self._initialized = True

    def increment_counter(
        self, name: str, labels: Optional[dict] = None, value: int = 1
    ):
        """Increment a counter metric."""
        if not self._initialized:
            return

        metric = self._metrics.get(name)
        if metric and isinstance(metric, Counter):
            if labels:
                metric.labels(**labels).inc(value)
            else:
                metric.inc(value)

    def observe_histogram(self, name: str, value: float, labels: Optional[dict] = None):
        """Observe a histogram metric."""
        if not self._initialized:
            return

        metric = self._metrics.get(name)
        if metric and isinstance(metric, Histogram):
            if labels:
                metric.labels(**labels).observe(value)
            else:
                metric.observe(value)

    def set_gauge(self, name: str, value: float, labels: Optional[dict] = None):
        """Set a gauge metric."""
        if not self._initialized:
            return

        metric = self._metrics.get(name)
        if metric and isinstance(metric, Gauge):
            if labels:
                metric.labels(**labels).set(value)
            else:
                metric.set(value)

    @contextmanager
    def timer(self, name: str, labels: Optional[dict] = None):
        """Context manager for timing operations."""
        start = time.time()
        try:
            yield
        finally:
            duration = time.time() - start
            self.observe_histogram(name, duration, labels)


# Global metrics instance
metrics = MetricsCollector()
metrics.initialize()


def track_request_metrics(request: Request, response: Response, duration: float):
    """Track HTTP request metrics."""
    method = request.method
    endpoint = request.url.path
    status_code = str(response.status_code)

    metrics.increment_counter(
        "http_requests_total",
        labels={"method": method, "endpoint": endpoint, "status_code": status_code},
    )

    metrics.observe_histogram(
        "http_request_duration_seconds",
        duration,
        labels={"method": method, "endpoint": endpoint},
    )


def track_db_query(operation: str, table: str, duration: float):
    """Track database query metrics."""
    metrics.increment_counter(
        "db_queries_total",
        labels={"operation": operation, "table": table},
    )
    metrics.observe_histogram(
        "db_query_duration_seconds",
        duration,
        labels={"operation": operation, "table": table},
    )


def track_cache_hit(cache_level: str):
    """Track cache hit."""
    metrics.increment_counter("cache_hits_total", labels={"cache_level": cache_level})


def track_cache_miss(cache_level: str):
    """Track cache miss."""
    metrics.increment_counter("cache_misses_total", labels={"cache_level": cache_level})


async def metrics_endpoint() -> Response:
    """Prometheus metrics endpoint."""
    return PlainTextResponse(
        content=generate_latest(REGISTRY),
        media_type=CONTENT_TYPE_LATEST,
    )


class MetricsMiddleware:
    """FastAPI middleware for collecting request metrics."""

    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        request = Request(scope, receive)
        start_time = time.time()

        # Capture response
        response_body = []

        async def wrapped_send(message):
            if message["type"] == "http.response.body":
                response_body.append(message.get("body", b""))
            await send(message)

        try:
            await self.app(scope, receive, wrapped_send)
        finally:
            duration = time.time() - start_time

            # Create a mock response for metrics
            status_code = 200  # Default, actual tracking happens in route handlers

            # Track metrics
            track_request_metrics(
                request,
                Response(status_code=status_code),
                duration,
            )


def timed(metric_name: str, labels: Optional[dict] = None):
    """Decorator to time function execution."""

    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            with metrics.timer(metric_name, labels):
                return await func(*args, **kwargs)

        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            with metrics.timer(metric_name, labels):
                return func(*args, **kwargs)

        return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper

    return decorator
