"""PERF-006: lightweight, vendor-neutral request observability.

Structured log line per request with route, method, status, duration, and
(for slow requests only) cumulative DB time/query count — no metrics
backend chosen yet, just production-safe structured logs as the first step.

Deliberately excluded to avoid unbounded label cardinality / sensitive data:
raw URL path (normalized route name only), user ID, record ID, query string,
request/response bodies, exception messages.
"""

import logging
import time

from django.conf import settings

logger = logging.getLogger("request_observability")

# Static/health-check paths excluded from the slow-request log noise.
_SKIP_PREFIXES = ("/static/", "/media/", "/favicon.ico")


def _route_name(request):
    """Normalized route identifier — resolver's url_name/route pattern, not
    the raw path (which would blow up log/metric cardinality with every
    distinct object ID)."""
    match = getattr(request, "resolver_match", None)
    if match is None:
        return "unresolved"
    return match.view_name or match.route or "unnamed"


class RequestObservabilityMiddleware:
    """Logs one structured line per request: route, method, status, duration.

    For requests slower than REQUEST_SLOW_THRESHOLD_MS (default 1000ms),
    also logs cumulative DB query count and DB time — using
    django.db.connection.queries, which is only populated when DEBUG=True or
    force_debug_cursor is set, so this additionally sets that flag on the
    connection for the duration of slow-request accounting only when
    settings.REQUEST_OBSERVABILITY_DB_STATS is enabled (default off — has a
    small overhead, opt in per deployment).
    """

    def __init__(self, get_response):
        self.get_response = get_response
        self.slow_threshold_ms = getattr(settings, "REQUEST_SLOW_THRESHOLD_MS", 1000)
        self.track_db_stats = getattr(settings, "REQUEST_OBSERVABILITY_DB_STATS", False)

    def __call__(self, request):
        path = request.path
        if path.startswith(_SKIP_PREFIXES):
            return self.get_response(request)

        from django.db import connection

        force_debug_cursor_was = connection.force_debug_cursor
        query_count_before = len(connection.queries) if self.track_db_stats else 0
        if self.track_db_stats:
            connection.force_debug_cursor = True

        start = time.perf_counter()
        try:
            response = self.get_response(request)
        finally:
            if self.track_db_stats:
                connection.force_debug_cursor = force_debug_cursor_was

        elapsed_ms = (time.perf_counter() - start) * 1000
        route = _route_name(request)
        is_slow = elapsed_ms >= self.slow_threshold_ms

        fields = {
            "route": route,
            "method": request.method,
            "status": response.status_code,
            "duration_ms": round(elapsed_ms, 1),
        }

        if is_slow and self.track_db_stats:
            queries_during = connection.queries[query_count_before:]
            fields["db_query_count"] = len(queries_during)
            fields["db_time_ms"] = round(
                sum(float(q.get("time", 0)) for q in queries_during) * 1000, 1
            )

        if is_slow:
            logger.warning("slow_request", extra={"request_fields": fields})
        else:
            logger.info("request", extra={"request_fields": fields})

        return response


class AccountStatusMiddleware:
    """Terminate requests from suspended or locked accounts.

    The auth backend blocks new logins for these accounts, but a session minted
    before the status changed would otherwise stay valid until it expired
    (SEC-12). Checking per request revokes those sessions immediately.

    Runs after AuthenticationMiddleware, which is what populates request.user.
    The check is a single already-loaded attribute read — the user row is
    fetched by AuthenticationMiddleware regardless, so this adds no query.
    """

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

    def __call__(self, request):
        from django.contrib.auth import logout
        from django.http import JsonResponse

        from apps.core.auth_backends import account_status_allows_access

        user = getattr(request, "user", None)
        if user is not None and user.is_authenticated and not account_status_allows_access(user):
            logout(request)
            return JsonResponse(
                {"detail": "This account is not permitted to sign in."}, status=403
            )
        return self.get_response(request)
