import hashlib
import json
import uuid
from dataclasses import dataclass

from django.db import transaction

from api.models import AppliedEngineResponse


class EngineResponseConflict(RuntimeError):
    pass


def response_hash(response_data: dict) -> str:
    encoded = json.dumps(response_data, sort_keys=True, separators=(",", ":"), default=str).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()


@dataclass(frozen=True)
class EngineResponseClaim:
    receipt: AppliedEngineResponse
    duplicate: bool


def claim_engine_response(
    *,
    request_id: str,
    response_data: dict,
    change_set_id: uuid.UUID,
    response_received_at=None,
    protocol_version: str = "1",
) -> EngineResponseClaim:
    if not transaction.get_connection().in_atomic_block:
        raise RuntimeError("Engine response receipts must be claimed inside transaction.atomic()")
    digest = response_hash(response_data)
    existing = AppliedEngineResponse.objects.select_for_update().filter(request_id=request_id).first()
    if existing:
        if existing.response_hash != digest or existing.protocol_version != protocol_version:
            raise EngineResponseConflict(
                f"Engine request_id {request_id!r} was already applied with different response content"
            )
        return EngineResponseClaim(receipt=existing, duplicate=True)

    receipt = AppliedEngineResponse.objects.create(
        request_id=request_id,
        response_hash=digest,
        protocol_version=protocol_version,
        change_set_id=change_set_id,
        response_received_at=response_received_at,
    )
    return EngineResponseClaim(receipt=receipt, duplicate=False)
