"""Transactional event outbox.

Use ``dispatch_async()`` from :mod:`simorgh.apps.events.bus` to enqueue an
event. A row is written to :class:`EventOutboxEntry` **inside the current
DB transaction**; on commit a Celery task picks pending entries and
delivers them to the in-process handlers.

This gives modules the "fire-and-forget" affordance without losing events
when a transaction rolls back, and without coupling business logic to a
broker URL.
"""

from __future__ import annotations

from typing import ClassVar

from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TimeStampedModel, UUIDModel


class OutboxStatus(models.TextChoices):
    PENDING = "pending", _("Pending")
    DISPATCHED = "dispatched", _("Dispatched")
    FAILED = "failed", _("Failed (will retry)")
    DEAD = "dead", _("Dead (max attempts reached)")


class EventOutboxEntry(UUIDModel, TimeStampedModel):
    """A queued event awaiting delivery."""

    name = models.CharField(_("event name"), max_length=128, db_index=True)
    payload = models.JSONField(_("payload"), default=dict, blank=True)
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=OutboxStatus.choices,
        default=OutboxStatus.PENDING,
        db_index=True,
    )
    attempt = models.PositiveIntegerField(_("attempt"), default=0)
    max_attempts = models.PositiveIntegerField(_("max attempts"), default=5)
    next_retry_at = models.DateTimeField(_("next retry at"), default=timezone.now, db_index=True)
    dispatched_at = models.DateTimeField(_("dispatched at"), null=True, blank=True)
    last_error = models.TextField(_("last error"), blank=True)
    tenant_id_hint = models.PositiveBigIntegerField(
        _("tenant id (denormalised)"), null=True, blank=True, db_index=True
    )

    class Meta:
        verbose_name = _("event outbox entry")
        verbose_name_plural = _("event outbox entries")
        ordering = ("created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("status", "next_retry_at")),
        ]

    def __str__(self) -> str:
        return f"{self.name} [{self.status}] #{self.pk}"
