"""LLMHosts proxy middleware -- request logging and request-ID injection.

Middleware is added to the FastAPI app in :func:`llmhost.proxy.app.create_app`.
"""

from __future__ import annotations

import logging
import time
import uuid
from typing import TYPE_CHECKING

from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint

if TYPE_CHECKING:
    from starlette.requests import Request
    from starlette.responses import Response

logger = logging.getLogger(__name__)

REQUEST_ID_HEADER = "X-Request-ID"

# Security headers applied to every API response (defense-in-depth;
# Traefik applies these in production, but we also set them here so
# dev/direct access is equally protected).
# Aut-O T-10 compliance: CSP, HSTS, Permissions-Policy, X-Content-Type-Options,
# X-Frame-Options, Referrer-Policy, Cross-Origin-Opener-Policy.
_API_SECURITY_HEADERS: dict[str, str] = {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Cross-Origin-Opener-Policy": "same-origin",
    "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
    "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
    "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()",
}

# Swagger UI / ReDoc require inline scripts and eval for rendering.
_DOCS_CSP = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://fastapi.tiangolo.com; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
_DOCS_PATHS = frozenset({"/docs", "/redoc", "/openapi.json"})


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """Add security headers to every API response (defense-in-depth).

    In production, Traefik's ``security-headers`` middleware applies these at
    the edge. This middleware ensures the same protection applies in development
    and when the API is accessed directly (bypassing Traefik).
    """

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        response = await call_next(request)
        for header, value in _API_SECURITY_HEADERS.items():
            response.headers.setdefault(header, value)
        # Relax CSP for Swagger UI / ReDoc pages
        if request.url.path in _DOCS_PATHS:
            response.headers["Content-Security-Policy"] = _DOCS_CSP
        return response


class RequestIdMiddleware(BaseHTTPMiddleware):
    """Inject an ``X-Request-ID`` header on every request and response.

    If the client supplies the header it is preserved; otherwise a new
    UUID-4 is generated.
    """

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        request_id = request.headers.get(REQUEST_ID_HEADER) or uuid.uuid4().hex
        request.state.request_id = request_id

        response = await call_next(request)
        response.headers[REQUEST_ID_HEADER] = request_id
        return response


class RequestLoggingMiddleware(BaseHTTPMiddleware):
    """Log method, path, status code, and latency for every request.

    Also records metrics in Prometheus format if prometheus-client is available.
    """

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        start = time.perf_counter()
        response: Response | None = None
        try:
            response = await call_next(request)
            return response
        finally:
            elapsed = time.perf_counter() - start
            elapsed_ms = elapsed * 1000
            status = response.status_code if response else 500

            # Structured logging with correlation ID if available
            correlation_id = getattr(request.state, "correlation_id", "")
            log_extra = {"correlation_id": correlation_id} if correlation_id else {}

            logger.info(
                "%s %s -> %s (%.1fms)",
                request.method,
                request.url.path,
                status,
                elapsed_ms,
                extra=log_extra,
            )

            # Record Prometheus metrics if available
            try:
                from llmhosts.metrics import record_http_request

                if record_http_request is not None:
                    record_http_request(
                        method=request.method,
                        path=request.url.path,
                        status=status,
                        duration_seconds=elapsed,
                    )
            except ImportError:
                pass  # prometheus-client not installed
