"""
Prometheus metrics middleware for FastAPI.

This middleware collects HTTP request metrics with OpenTelemetry context correlation:
- Request counts by method, endpoint, and status code
- Request latency histograms
- 5xx error logging with trace_id for drill-down investigation

The middleware shares the same request context as OpenTelemetry tracing,
enabling seamless trace-metric correlation in observability tools.
"""

import time
import structlog
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from typing import Callable, Set

from control_plane_api.app.observability.metrics import (
    normalize_endpoint,
    record_http_request,
)

logger = structlog.get_logger(__name__)


class PrometheusMiddleware(BaseHTTPMiddleware):
    """
    Starlette middleware to collect Prometheus HTTP metrics.
    
    Features:
    - Request counting with method, endpoint, status_code labels
    - Latency histogram with configurable buckets
    - Endpoint path normalization to prevent cardinality explosion
    - Health check exclusion
    - OpenTelemetry trace ID correlation for 5xx errors
    
    Usage:
        app.add_middleware(PrometheusMiddleware)
    
    The middleware should be added BEFORE other middleware to capture
    the full request duration including all middleware processing.
    """
    
    # Paths to exclude from metrics collection
    # These are high-frequency, low-value endpoints that would bloat metrics
    EXCLUDED_PATHS: Set[str] = {
        "/health",
        "/api/health",
        "/health/live",
        "/health/ready",
        "/health/detailed",
        "/health/event-bus",
        "/health/temporal-credentials",
        "/ready",
        "/metrics",
        "/favicon.ico",
    }
    
    # Paths that start with these prefixes should be excluded
    EXCLUDED_PREFIXES: tuple = (
        "/health",
    )
    
    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        """
        Process the request and collect metrics.
        
        Args:
            request: Incoming HTTP request
            call_next: Next middleware/handler in chain
            
        Returns:
            HTTP response
        """
        path = request.url.path
        
        # Skip metrics collection for excluded paths
        if self._should_exclude(path):
            return await call_next(request)
        
        method = request.method
        endpoint = normalize_endpoint(path)
        
        # Start timing
        start_time = time.perf_counter()
        
        try:
            # Process the request
            response = await call_next(request)
            
            # Calculate duration
            duration = time.perf_counter() - start_time
            status_code = response.status_code
            
            # Get trace ID from response headers (set by TraceContextMiddleware)
            trace_id = response.headers.get("X-Trace-ID")
            
            # Record metrics
            record_http_request(
                method=method,
                endpoint=endpoint,
                status_code=status_code,
                duration_seconds=duration,
                trace_id=trace_id,
            )
            
            return response
            
        except Exception as e:
            # Record the exception as a 500 error
            duration = time.perf_counter() - start_time
            
            # Try to get trace ID from current span
            trace_id = self._get_current_trace_id()
            
            record_http_request(
                method=method,
                endpoint=endpoint,
                status_code=500,
                duration_seconds=duration,
                trace_id=trace_id,
            )
            
            logger.error(
                "prometheus_middleware_exception",
                method=method,
                endpoint=endpoint,
                error=str(e),
                trace_id=trace_id,
            )
            
            raise
    
    def _should_exclude(self, path: str) -> bool:
        """
        Check if the path should be excluded from metrics.
        
        Args:
            path: Request path
            
        Returns:
            True if path should be excluded
        """
        if path in self.EXCLUDED_PATHS:
            return True
        
        if path.startswith(self.EXCLUDED_PREFIXES):
            return True
        
        return False
    
    @staticmethod
    def _get_current_trace_id() -> str:
        """
        Get the current trace ID from OpenTelemetry context.
        
        Returns:
            Trace ID as hex string, or "unknown" if not available
        """
        try:
            from control_plane_api.app.observability.optional import get_current_span
            
            span = get_current_span()
            if span and span.is_recording():
                span_context = span.get_span_context()
                if span_context and hasattr(span_context, 'trace_id'):
                    return format(span_context.trace_id, '032x')
        except Exception:
            pass
        
        return "unknown"
