import json
import logging
import re
from uuid import UUID

from django.utils.deprecation import MiddlewareMixin

from .models import AuditLog

logger = logging.getLogger(__name__)

# Sensitive fields to scrub from stored payloads
SENSITIVE_FIELDS = {"password", "password_confirm", "refresh", "access", "token"}

# ---------------------------------------------------------------------------
# URL-to-action mapping registry
# Each entry: (url_regex, http_method) -> (action, target_model, description_template)
#
# The description_template can use {tracking_code}, {id} etc. from the response.
# ---------------------------------------------------------------------------
ROUTE_MAP = [
    # ── Auth ──────────────────────────────────────────────────────────────
    (
        r"/api/v1/users/auth/login/$",
        "POST",
        AuditLog.ActionChoices.LOGIN,
        "User",
        "User logged in",
    ),
    (
        r"/api/v1/users/auth/logout/$",
        "POST",
        AuditLog.ActionChoices.LOGOUT,
        "User",
        "User logged out",
    ),
    (
        r"/api/v1/users/auth/register/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "User",
        "Registered new user",
    ),
    (r"/api/v1/users/auth/refresh/$", "POST", None, None, None),  # skip token refreshes
    # ── Import Submissions ────────────────────────────────────────────────
    (
        r"/api/v1/record/import-submissions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "ImportSubmission",
        "Created Import Submission",
    ),
    (
        r"/api/v1/record/import-submissions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "ImportSubmission",
        "Updated Import Submission",
    ),
    (
        r"/api/v1/record/import-submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "ImportSubmission",
        "Deleted Import Submission",
    ),
    # ── Export Submissions ────────────────────────────────────────────────
    (
        r"/api/v1/record/export-submissions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "ExportSubmission",
        "Created Export Submission",
    ),
    (
        r"/api/v1/record/export-submissions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "ExportSubmission",
        "Updated Export Submission",
    ),
    (
        r"/api/v1/record/export-submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "ExportSubmission",
        "Deleted Export Submission",
    ),
    # ── Revenue / Fine Submissions ────────────────────────────────────────
    (
        r"/api/v1/record/revenue-fine-submissions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "RevenueFineSubmission",
        "Created Revenue/Fine Submission",
    ),
    (
        r"/api/v1/record/revenue-fine-submissions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "RevenueFineSubmission",
        "Updated Revenue/Fine Submission",
    ),
    (
        r"/api/v1/record/revenue-fine-submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "RevenueFineSubmission",
        "Deleted Revenue/Fine Submission",
    ),
    # ── Health Inspection Submissions ─────────────────────────────────────
    (
        r"/api/v1/record/health-inspection-submissions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "HealthInspectionSubmission",
        "Created Health Inspection Submission",
    ),
    (
        r"/api/v1/record/health-inspection-submissions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "HealthInspectionSubmission",
        "Updated Health Inspection Submission",
    ),
    (
        r"/api/v1/record/health-inspection-submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "HealthInspectionSubmission",
        "Deleted Health Inspection Submission",
    ),
    # ── Destruction Submissions ───────────────────────────────────────────
    (
        r"/api/v1/record/destruction-submissions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "DestructionSubmission",
        "Created Destruction Submission",
    ),
    (
        r"/api/v1/record/destruction-submissions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "DestructionSubmission",
        "Updated Destruction Submission",
    ),
    (
        r"/api/v1/record/destruction-submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "DestructionSubmission",
        "Deleted Destruction Submission",
    ),
    # ── Submission status change ──────────────────────────────────────────
    (
        r"/api/v1/record/submissions/(?P<pk>[^/]+)/change-status/$",
        "POST",
        AuditLog.ActionChoices.STATUS_CHANGE,
        "Submission",
        "Changed status of Submission",
    ),
    # ── Submission delete (unified) ───────────────────────────────────────
    (
        r"/api/v1/record/submissions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "Submission",
        "Deleted Submission",
    ),
    # ── Documents ─────────────────────────────────────────────────────────
    (
        r"/api/v1/record/documents/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "Document",
        "Uploaded Document",
    ),
    (
        r"/api/v1/record/documents/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "Document",
        "Updated Document",
    ),
    (
        r"/api/v1/record/documents/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "Document",
        "Deleted Document",
    ),
    # ── Deletion requests ─────────────────────────────────────────────────
    (
        r"/api/v1/record/deletion-requests/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "MetadataDeletionRequest",
        "Created Deletion Request",
    ),
    (
        r"/api/v1/record/deletion-requests/(?P<pk>[^/]+)/approve/$",
        "POST",
        AuditLog.ActionChoices.UPDATE,
        "MetadataDeletionRequest",
        "Approved Deletion Request",
    ),
    (
        r"/api/v1/record/deletion-requests/(?P<pk>[^/]+)/deny/$",
        "POST",
        AuditLog.ActionChoices.UPDATE,
        "MetadataDeletionRequest",
        "Denied Deletion Request",
    ),
    # ── User management ───────────────────────────────────────────────────
    (
        r"/api/v1/users/me/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "User",
        "Updated own profile",
    ),
    (
        r"/api/v1/users/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "User",
        "Updated user",
    ),
    # ── Master data (commodity categories, types, etc.) ───────────────────
    (
        r"/api/v1/record/commodity-categories/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "CommodityCategory",
        "Created Commodity Category",
    ),
    (
        r"/api/v1/record/commodity-categories/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "CommodityCategory",
        "Updated Commodity Category",
    ),
    (
        r"/api/v1/record/commodity-categories/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "CommodityCategory",
        "Deleted Commodity Category",
    ),
    (
        r"/api/v1/record/commodity-types/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "CommodityType",
        "Created Commodity Type",
    ),
    (
        r"/api/v1/record/commodity-types/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "CommodityType",
        "Updated Commodity Type",
    ),
    (
        r"/api/v1/record/commodity-types/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "CommodityType",
        "Deleted Commodity Type",
    ),
    (
        r"/api/v1/record/collection-categories/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "CollectionCategory",
        "Created Collection Category",
    ),
    (
        r"/api/v1/record/collection-categories/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "CollectionCategory",
        "Updated Collection Category",
    ),
    (
        r"/api/v1/record/collection-categories/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "CollectionCategory",
        "Deleted Collection Category",
    ),
    (
        r"/api/v1/record/collection-types/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "CollectionType",
        "Created Collection Type",
    ),
    (
        r"/api/v1/record/collection-types/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "CollectionType",
        "Updated Collection Type",
    ),
    (
        r"/api/v1/record/collection-types/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "CollectionType",
        "Deleted Collection Type",
    ),
    (
        r"/api/v1/record/submission-types/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "SubmissionType",
        "Created Submission Type",
    ),
    (
        r"/api/v1/record/submission-types/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "SubmissionType",
        "Updated Submission Type",
    ),
    (
        r"/api/v1/record/submission-types/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "SubmissionType",
        "Deleted Submission Type",
    ),
    (
        r"/api/v1/record/units-of-measurement/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "UnitOfMeasurement",
        "Created Unit of Measurement",
    ),
    (
        r"/api/v1/record/units-of-measurement/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "UnitOfMeasurement",
        "Updated Unit of Measurement",
    ),
    (
        r"/api/v1/record/units-of-measurement/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "UnitOfMeasurement",
        "Deleted Unit of Measurement",
    ),
    (
        r"/api/v1/record/unit-conversions/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "UnitConversion",
        "Created Unit Conversion",
    ),
    (
        r"/api/v1/record/unit-conversions/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "UnitConversion",
        "Updated Unit Conversion",
    ),
    (
        r"/api/v1/record/unit-conversions/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "UnitConversion",
        "Deleted Unit Conversion",
    ),
    (
        r"/api/v1/record/submission-status/$",
        "POST",
        AuditLog.ActionChoices.CREATE,
        "SubmissionStatus",
        "Created Submission Status",
    ),
    (
        r"/api/v1/record/submission-status/(?P<pk>[^/]+)/$",
        "PATCH",
        AuditLog.ActionChoices.UPDATE,
        "SubmissionStatus",
        "Updated Submission Status",
    ),
    (
        r"/api/v1/record/submission-status/(?P<pk>[^/]+)/$",
        "DELETE",
        AuditLog.ActionChoices.DELETE,
        "SubmissionStatus",
        "Deleted Submission Status",
    ),
]

# Pre-compile the patterns for performance
_COMPILED_ROUTES = [
    (re.compile(pattern), method, action, target_model, desc_template)
    for pattern, method, action, target_model, desc_template in ROUTE_MAP
]


def _scrub_payload(data):
    """Remove sensitive fields from a dict payload."""
    if not isinstance(data, dict):
        return data
    return {k: "***" if k.lower() in SENSITIVE_FIELDS else v for k, v in data.items()}


def _extract_response_summary(response):
    """Extract key identifying fields from a JSON response body."""
    try:
        if not hasattr(response, "content") or not response.content:
            return None
        body = json.loads(response.content.decode("utf-8"))
        if not isinstance(body, dict):
            return None
        summary = {}
        for key in ("id", "tracking_code", "refrence_no", "name", "email", "code"):
            if key in body:
                summary[key] = body[key]
        return summary or None
    except Exception:
        return None


def _resolve_route(path, method):
    """Match the request path + method against our route map."""
    for (
        compiled_re,
        route_method,
        action,
        target_model,
        desc_template,
    ) in _COMPILED_ROUTES:
        if method == route_method and compiled_re.search(path):
            match = compiled_re.search(path)
            pk = match.group("pk") if "pk" in (match.groupdict() or {}) else None
            return action, target_model, desc_template, pk
    return None, None, None, None


class AuditLogMiddleware(MiddlewareMixin):
    """
    Middleware to log meaningful staff activity with rich context.
    Only logs POST, PATCH, PUT, DELETE requests (mutations + auth events).
    Skips GET requests entirely to avoid noise.
    """

    def process_request(self, request):
        # Copy body early — it may be consumed by parsers before we can read it
        try:
            request._audit_body = request.body
        except Exception:
            request._audit_body = None

    def process_response(self, request, response):
        # Skip if not an API request
        if not request.path.startswith("/api/"):
            return response

        method = request.method

        # For auth endpoints (login/logout), the user might not be on request yet
        is_auth_login = request.path.endswith("/auth/login/") and method == "POST"
        is_auth_logout = request.path.endswith("/auth/logout/") and method == "POST"

        # Skip GET/HEAD/OPTIONS — they're read-only noise
        if method in ("GET", "HEAD", "OPTIONS") and not is_auth_login:
            return response

        # Determine the user
        user = None
        if hasattr(request, "user") and request.user.is_authenticated:
            user = request.user

        # For login, extract user from response body if available
        if is_auth_login and user is None:
            try:
                body = json.loads(response.content.decode("utf-8"))
                if "user" in body and "email" in body["user"]:
                    from django.contrib.auth import get_user_model

                    User = get_user_model()
                    user = User.objects.filter(
                        email__iexact=body["user"]["email"]
                    ).first()
            except Exception:
                pass

        # Skip if no authenticated user (except failed login attempts we still want to see)
        if user is None and not is_auth_login:
            return response

        # Match against our route map
        action, target_model, desc_template, url_pk = _resolve_route(
            request.path, method
        )

        # If action is None, this route explicitly wants to be skipped (e.g. token refresh)
        if action is None and desc_template is None and target_model is None:
            # Check if this was a known skip entry (refresh) vs simply unmatched
            for compiled_re, route_method, a, tm, dt in _COMPILED_ROUTES:
                if method == route_method and compiled_re.search(request.path):
                    # Explicit skip
                    return response
            # Unmatched mutation — log as OTHER
            action = AuditLog.ActionChoices.OTHER
            target_model = ""
            desc_template = f"{method} {request.path}"

        # Build payload
        payload = None
        if method in ("POST", "PUT", "PATCH"):
            try:
                raw = getattr(request, "_audit_body", None)
                if raw:
                    payload = _scrub_payload(json.loads(raw.decode("utf-8")))
            except Exception:
                payload = None

        # Extract response summary for identifying the object
        resp_summary = _extract_response_summary(response)

        # Build the human-readable description
        action_description = desc_template or ""
        tracking_code = ""
        if resp_summary:
            tracking_code = resp_summary.get("tracking_code", "")
            if tracking_code:
                action_description = f"{desc_template} {tracking_code}"
            elif resp_summary.get("name"):
                action_description = f"{desc_template} '{resp_summary['name']}'"
            elif resp_summary.get("email"):
                action_description = f"{desc_template} ({resp_summary['email']})"

        # Resolve target_object_id
        target_object_id = None
        if url_pk:
            try:
                target_object_id = UUID(str(url_pk))
            except (ValueError, AttributeError):
                pass
        if target_object_id is None and resp_summary and resp_summary.get("id"):
            try:
                target_object_id = UUID(str(resp_summary["id"]))
            except (ValueError, AttributeError):
                pass

        # For login action, set target to the user themselves
        if action == AuditLog.ActionChoices.LOGIN and user and target_object_id is None:
            target_object_id = user.pk

        # For failed login attempts
        if is_auth_login and response.status_code >= 400:
            action_description = "Failed login attempt"

        # Query params
        query_params = dict(request.GET) if request.GET else None
        # Flatten single-value lists
        if query_params:
            query_params = {
                k: v[0] if isinstance(v, list) and len(v) == 1 else v
                for k, v in query_params.items()
            }

        # User agent
        user_agent = request.META.get("HTTP_USER_AGENT", "")[:500]

        # IP address
        ip_address = self._get_client_ip(request)

        try:
            AuditLog.objects.create(
                user=user,
                action=action,
                action_description=action_description[:500],
                method=method,
                path=request.path[:500],
                payload=payload,
                status_code=response.status_code,
                ip_address=ip_address,
                target_model=target_model or "",
                target_object_id=target_object_id,
                query_params=query_params,
                user_agent=user_agent,
                response_summary=resp_summary,
            )
        except Exception as e:
            logger.error("Failed to create AuditLog: %s", e)

        return response

    @staticmethod
    def _get_client_ip(request):
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        if x_forwarded_for:
            return x_forwarded_for.split(",")[0].strip()
        return request.META.get("REMOTE_ADDR")
