import re
import time
import logging

from django.conf import settings

from .models import AccessLog
from .ua_parser import parse_ua
from .utils import client_ip, hash_ip, hash_visitor, referer_host

log = logging.getLogger(__name__)


# Default tracked patterns. Each entry: (resource_type, compiled regex with optional `id`/`slug` named groups).
# Override via settings.ANALYTICS_TRACKED_PATTERNS (list of dicts with `type` + `pattern`).
DEFAULT_PATTERNS = [
    ("shorten_url", re.compile(r"^/api/urls/(?P<id>[^/]+)/?(?:click/?)?$")),
    ("publication", re.compile(r"^/api/csis-publications/(?P<id>\d+)/?$")),
    ("publication", re.compile(r"^/api/publications/(?P<id>[^/]+)/?$")),
    ("event", re.compile(r"^/api/events/(?P<id>[^/]+)/?$")),
]


def _compile_patterns():
    raw = getattr(settings, "ANALYTICS_TRACKED_PATTERNS", None)
    if not raw:
        return DEFAULT_PATTERNS
    out = []
    for entry in raw:
        out.append((entry["type"], re.compile(entry["pattern"])))
    return out


def _resolve_organization_id(resource_type, resource_id, path):
    """Best-effort org lookup for a tracked resource. Returns None when the
    resource type has no organization (e.g. the CSISPublication mirror) or
    the id can't be resolved — AccessLog.organization stays null in that case.
    """
    if not resource_id:
        return None
    try:
        if resource_type == "shorten_url":
            from apps.tools.models import URL
            return URL.objects.filter(short_code=resource_id).values_list("organization_id", flat=True).first()
        if resource_type == "publication":
            if path.startswith("/api/csis-publications/"):
                return None  # external CSIS mirror, no organization
            from apps.publications.models import Publication
            return Publication.objects.filter(pk=resource_id).values_list("organization_id", flat=True).first()
        if resource_type == "event":
            from apps.events.models import Event
            return Event.objects.filter(pk=resource_id).values_list("organization_id", flat=True).first()
    except Exception:
        return None
    return None


class AccessLogMiddleware:
    """Log public access to tracked resources for analytics."""

    def __init__(self, get_response):
        self.get_response = get_response
        self.patterns = _compile_patterns()
        self.salt = getattr(settings, "ANALYTICS_HASH_SALT", settings.SECRET_KEY)
        self.enabled = getattr(settings, "ANALYTICS_ENABLED", True)
        # Skip noisy/admin/internal paths even if they match.
        skip = getattr(settings, "ANALYTICS_SKIP_PREFIXES", ["/admin/", "/api/schema", "/api/auth/"])
        self.skip = tuple(skip)

    def __call__(self, request):
        start = time.perf_counter()
        response = self.get_response(request)
        if not self.enabled:
            return response

        try:
            self._maybe_log(request, response, start)
        except Exception:
            log.exception("AccessLogMiddleware failed to log request")
        return response

    def _maybe_log(self, request, response, start):
        path = request.path
        if path.startswith(self.skip):
            return

        match = None
        resource_type = None
        for rtype, pat in self.patterns:
            m = pat.match(path)
            if m:
                resource_type = rtype
                match = m
                break
        if not match:
            return

        resource_id = match.groupdict().get("id", "") or ""
        resource_slug = match.groupdict().get("slug", "") or ""

        ip = client_ip(request)
        ua = request.META.get("HTTP_USER_AGENT", "")[:512]
        ref = request.META.get("HTTP_REFERER", "")[:1024]
        ua_info = parse_ua(ua)

        query = request.GET
        tags_raw = query.get("tag") or query.get("tags") or ""
        tags = [t.strip() for t in tags_raw.split(",") if t.strip()][:10]

        elapsed_ms = int((time.perf_counter() - start) * 1000)
        organization_id = _resolve_organization_id(resource_type, resource_id, path)

        # All request-derived values are extracted above (the request object is
        # not safe to share across threads); only the INSERT goes to the pool.
        from apps.core.background import run_in_background

        run_in_background(
            AccessLog.objects.create,
            organization_id=organization_id,
            resource_type=resource_type,
            resource_id=str(resource_id)[:64],
            resource_slug=str(resource_slug)[:255],
            path=path[:512],
            method=request.method[:8],
            status_code=response.status_code,
            response_time_ms=elapsed_ms,
            ip_hash=hash_ip(ip, self.salt),
            visitor_hash=hash_visitor(ip, ua, self.salt),
            user_agent=ua,
            device=ua_info["device"],
            os=ua_info["os"],
            browser=ua_info["browser"],
            referer=ref,
            referer_host=referer_host(ref)[:255],
            tags=tags,
            utm_source=query.get("utm_source", "")[:128],
            utm_medium=query.get("utm_medium", "")[:128],
            utm_campaign=query.get("utm_campaign", "")[:128],
        )
