"""Acceptance bookkeeping for the adjustment intake path.

Delivery is at-least-once, so the same `adjustment_id` can arrive more than
once. We keep the set of identifiers we have already accepted, and the
per-account acceptance ordinal that fixes the order corrections were taken in,
in the shared cache so that every web and worker process sees the same view
without a round trip to the database on the hot path.
"""

from __future__ import annotations

from django.core.cache import cache

_ACCEPTED_KEY = "accepted:{adjustment_id}"
_ORDINAL_KEY = "ordinal:{account_id}"
_RUN_LOCK_KEY = "run-lock:{account_id}"


def already_accepted(adjustment_id: str) -> bool:
    """Report whether this identifier has been accepted before."""

    return cache.get(_ACCEPTED_KEY.format(adjustment_id=adjustment_id)) is not None


def mark_accepted(adjustment_id: str) -> None:
    """Record that this identifier has been accepted."""

    cache.set(_ACCEPTED_KEY.format(adjustment_id=adjustment_id), 1, timeout=None)


def next_ordinal(account_id: str) -> int:
    """Consume and return the account's next acceptance ordinal.

    Ordinals are dense and strictly increasing per account. They fix the order
    in which corrections were accepted, which the settlement-run cutoff and the
    effective-order resolution both depend on.
    """

    key = _ORDINAL_KEY.format(account_id=account_id)
    try:
        return int(cache.incr(key))
    except ValueError:
        # First correction for this account.
        cache.set(key, 1, timeout=None)
        return 1


def acquire_run_lock(account_id: str, ttl_seconds: int = 30) -> bool:
    """Take the per-account statement-run lock, if it is free."""

    return bool(
        cache.add(_RUN_LOCK_KEY.format(account_id=account_id), 1, timeout=ttl_seconds)
    )


def release_run_lock(account_id: str) -> None:
    cache.delete(_RUN_LOCK_KEY.format(account_id=account_id))
