from django.core.exceptions import ValidationError
from django.core.validators import validate_ipv46_address
from django.db import transaction

from apps.core.audit.models import AuditLog
from apps.core.audit.tasks import write_audit_log


class AuditMiddleware:
    """Record authenticated HTTP activity: mutating methods + full-page GET (ACCESS)."""

    SKIP_PREFIXES = (
        "/static/",
        "/uploads/",
        "/api/v1/schema",
        "/admin/jsi18n/",
        "/admin/axes/",
    )

    # HTMX partials / polling — would drown the log or duplicate list refreshes
    SKIP_GET_PREFIXES = (
        "/notifications/badge/",
    )

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

    def __call__(self, request):
        response = self.get_response(request)
        if not hasattr(request, "user") or not request.user.is_authenticated:
            return response
        path = request.path
        if any(path.startswith(p) for p in self.SKIP_PREFIXES):
            return response

        action = self._action_for_request(request)
        if action is None:
            return response

        ip = self._client_ip(request)
        ua = request.META.get("HTTP_USER_AGENT", "")[:512]

        payload = {
            "actor_id": request.user.pk,
            "action": action,
            "path": path[:512],
            "method": request.method,
            "ip_address": ip,
            "user_agent": ua,
        }

        def queue():
            write_audit_log.delay(**payload)

        transaction.on_commit(queue)
        return response

    def _action_for_request(self, request):
        m = request.method
        if m in ("POST", "PUT", "PATCH", "DELETE"):
            if m in ("PUT", "PATCH"):
                return AuditLog.Action.UPDATE
            if m == "POST":
                return AuditLog.Action.CREATE
            return AuditLog.Action.DELETE
        if m == "GET":
            # Skip HTMX fragment loads (search-as-you-type, boosted regions, badge, etc.)
            if request.META.get("HTTP_HX_REQUEST"):
                return None
            if any(request.path.startswith(p) for p in self.SKIP_GET_PREFIXES):
                return None
            return AuditLog.Action.ACCESS
        return None

    @classmethod
    def _client_ip(cls, request):
        xff = request.META.get("HTTP_X_FORWARDED_FOR")
        if xff:
            candidate = xff.split(",")[0].strip()
        else:
            candidate = request.META.get("REMOTE_ADDR")
        return cls._normalize_ip(candidate)

    @staticmethod
    def _normalize_ip(raw):
        """PostgreSQL inet rejects hostnames and tokens like 'unknown'; store NULL instead."""
        if raw is None:
            return None
        s = str(raw).strip()
        if not s or s.lower() in ("unknown", "null", "-", "undefined"):
            return None
        try:
            validate_ipv46_address(s)
        except ValidationError:
            return None
        return s
