import hmac
import json
import uuid
from itertools import groupby

from django.conf import settings
from django.db.models import F, Max, OuterRef, Subquery
from django.http import JsonResponse
from django.views.decorators.http import require_GET

from api.models import IntegrationOutbox
from api.services.integration.readiness import phase1_readiness
from api.services.integration.outbox import build_transport_envelope


def _error(message, status):
    return JsonResponse({"error": message}, status=status)


def _authorized(request) -> bool:
    configured = settings.RESOURCE_BOOKING_INTEGRATION.get("SNAPSHOT_SERVICE_TOKEN") or ""
    authorization = request.headers.get("Authorization", "")
    supplied = authorization[7:] if authorization.startswith("Bearer ") else ""
    return bool(configured) and hmac.compare_digest(configured, supplied)


def _decode_cursor(value: str) -> int:
    if not value:
        return 0
    if len(value) > 128 or not value.isdigit():
        raise ValueError("cursor must be a bounded non-negative source sequence")
    return int(value)


@require_GET
def resource_booking_snapshot(request):
    """Return latest canonical states at a fixed committed transport watermark."""

    if not _authorized(request):
        return _error("unauthorized", 401)
    config = settings.RESOURCE_BOOKING_INTEGRATION
    source_scope = request.GET.get("source_scope")
    if source_scope != config["SOURCE_SCOPE"]:
        return _error("source_scope is not available", 404)

    try:
        page_size = int(
            request.GET.get("page_size", str(min(100, config["SNAPSHOT_MAX_PAGE_SIZE"])))
        )
    except ValueError:
        return _error("page_size must be an integer", 400)
    if page_size < 1 or page_size > config["SNAPSHOT_MAX_PAGE_SIZE"]:
        return _error(
            f"page_size must be between 1 and {config['SNAPSHOT_MAX_PAGE_SIZE']}",
            400,
        )
    try:
        cursor_sequence = _decode_cursor(request.GET.get("cursor", ""))
    except ValueError as error:
        return _error(str(error), 400)

    current_watermark = (
        IntegrationOutbox.objects.filter(
            source_scope=source_scope,
            transaction_finalized=True,
        ).aggregate(value=Max("transport_sequence"))["value"]
        or 0
    )
    requested_watermark = request.GET.get("snapshot_watermark")
    if requested_watermark in (None, ""):
        watermark = current_watermark
    else:
        try:
            watermark = int(requested_watermark)
        except ValueError:
            return _error("snapshot_watermark must be an integer", 400)
        if watermark < 0 or watermark > current_watermark:
            return _error("snapshot_watermark is outside the committed range", 409)

    latest_id = (
        IntegrationOutbox.objects.filter(
            source_scope=source_scope,
            transaction_finalized=True,
            transport_sequence__lte=watermark,
            aggregate_type=OuterRef("aggregate_type"),
            aggregate_id=OuterRef("aggregate_id"),
        )
        .order_by("-transport_sequence", "-event_version")
        .values("id")[:1]
    )
    queryset = (
        IntegrationOutbox.objects.filter(
            source_scope=source_scope,
            transaction_finalized=True,
            transport_sequence__lte=watermark,
            aggregate_type__in=["activity", "location", "staff"],
        )
        .annotate(snapshot_latest_id=Subquery(latest_id))
        .filter(id=F("snapshot_latest_id"))
    )
    if cursor_sequence:
        queryset = queryset.filter(transport_sequence__gt=cursor_sequence)
    sequences = list(
        queryset.order_by("transport_sequence")
        .values_list("transport_sequence", flat=True)
        .distinct()[: page_size + 1]
    )
    complete = len(sequences) <= page_size
    selected_sequences = sequences[:page_size]
    rows = list(
        queryset.filter(transport_sequence__in=selected_sequences).order_by(
            "transport_sequence", "transaction_index"
        )
    )
    envelopes = []
    for sequence, group in groupby(rows, key=lambda row: row.transport_sequence):
        members = tuple(group)
        draft = build_transport_envelope(members)
        snapshot_event_id = str(
            uuid.uuid5(
                uuid.NAMESPACE_URL,
                f"timetabler-snapshot:{source_scope}:{watermark}:{sequence}:{draft['events_hash']}",
            )
        )
        envelopes.append(build_transport_envelope(members, event_id=snapshot_event_id))
    next_cursor = None if complete or not selected_sequences else str(selected_sequences[-1])
    body = {
        "schema_version": config["SCHEMA_VERSION"],
        "snapshot_watermark": str(watermark),
        "next_cursor": next_cursor,
        "complete": complete,
        "envelopes": envelopes,
    }
    encoded_size = len(json.dumps(body, separators=(",", ":")).encode("utf-8"))
    if encoded_size > config["SNAPSHOT_MAX_BYTES"]:
        return _error("snapshot page exceeds configured byte limit; request a smaller page", 413)
    response = JsonResponse(body)
    response["Cache-Control"] = "no-store"
    return response


@require_GET
def resource_booking_health(request):
    if not _authorized(request):
        return _error("unauthorized", 401)
    result = phase1_readiness(require_enabled=False)
    status = 200 if result["ready"] else 503
    response = JsonResponse(result, status=status)
    response["Cache-Control"] = "no-store"
    return response
