"""``PendingKick``: durable record of an in-flight or pending kick task."""

from __future__ import annotations

from django.db import models
from django.utils.translation import gettext_lazy as _

from ._audit import AuditFinding


class PendingKick(models.Model):
    """
    Durable record of a kick that needs delivery to Celery.

    Created inside ``_apply_finding``'s phase 3 ``transaction.atomic``
    so the row commits together with the ``AuditFinding`` APPLIED
    transition. The follow-up ``transaction.on_commit`` callback
    issues ``delete_user.delay(uid)`` and on success marks
    ``completed_at``; on broker failure the row stays as the
    forensic + retry record so ``retry_pending_kicks`` (Celery
    beat task) can sweep it later.

    The contract on ``completed_at`` is "we successfully handed off
    to Celery" — not "the user is deleted". The actual user
    deletion is Alliance Auth core's ``delete_user`` task's
    responsibility; if that task fails inside a worker, AA's own
    retry / failure handling owns the recovery.

    Mutable model (NOT append-only): the same row is updated by
    both the on_commit callback and ``retry_pending_kicks`` as
    delivery progresses.
    """

    audit_finding = models.OneToOneField(
        AuditFinding,
        on_delete=models.CASCADE,
        related_name="pending_kick",
    )
    # Snapshotted Discord uid + AA user pk for forensics so a
    # forensic query stays answerable after the cascading deletion
    # of the AA-side User (which `delete_user` performs).
    uid = models.PositiveBigIntegerField(db_index=True)
    # AA core's User pk is BigAutoField on a fresh AA 4.x install (and
    # large operators may be past 2**31 anyway), so the snapshot mirrors
    # ``settings.AUTH_USER_MODEL.id`` width. The other User-FKs in the
    # module inherit BigInt through the FK; this is the lone parallel
    # snapshot column and must match.
    aa_user_id = models.PositiveBigIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    attempts = models.PositiveIntegerField(default=0)
    last_attempt_at = models.DateTimeField(null=True, blank=True)
    last_error = models.TextField(blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        verbose_name = _("pending kick")
        verbose_name_plural = _("pending kicks")
        # Hot path for ``retry_pending_kicks``: scan rows where
        # completed_at IS NULL. A partial index on (completed_at)
        # WHERE completed_at IS NULL would be ideal but Django 4.2
        # lacks the predicate kwarg on ``models.Index``; this
        # full-column index still serves the IS NULL filter via
        # PostgreSQL's btree on a sparse column.
        indexes = [
            models.Index(
                fields=["completed_at"], name="pendingkick_status_idx"
            ),
        ]

    def __str__(self) -> str:
        status = "completed" if self.completed_at else "pending"
        return (
            f"PendingKick(uid={self.uid}, "
            f"aa_user_id={self.aa_user_id}, {status}, "
            f"attempts={self.attempts})"
        )
