"""
Celery tasks with exponential-backoff retry.

Bug fixes over original:
- Notification record is created INSIDE the task (not before) so the status
  is always persisted even if delivery fails.
- Task failure no longer leaves ``notification`` undefined.
- Proper retry with jitter to avoid thundering-herd on transient FCM errors.
"""
from __future__ import annotations

import logging
from typing import Any

from celery import shared_task
from celery.exceptions import MaxRetriesExceededError
from django.contrib.auth import get_user_model

from django_notify_hub.conf import app_settings
from django_notify_hub.repositories import NotificationRepository
from django_notify_hub.types import (
    NotificationChannel,
    NotificationPayload,
    NotificationPriority,
    NotificationStatus,
)

logger = logging.getLogger("django_notify_hub")

User = get_user_model()


def _build_payload(
    title: dict,
    body: dict,
    data: dict,
    priority: str,
    channels: list[str],
) -> NotificationPayload:
    return NotificationPayload(
        title=title,
        body=body,
        data=data or {},
        priority=NotificationPriority(priority),
        channels=tuple(NotificationChannel(c) for c in channels),
    )


@shared_task(
    bind=True,
    max_retries=None,  # controlled via app_settings
    default_retry_delay=60,
    queue="notifications",
    name="django_notify_hub.send_notification",
)
def send_notification_task(
    self,
    title: dict,
    body: dict,
    data: dict,
    priority: str,
    channels: list[str],
    recipient_id: Any,
) -> None:
    """Deliver one notification to one user."""
    max_retries = app_settings.CELERY_MAX_RETRIES

    try:
        recipient = User.objects.get(pk=recipient_id)
    except User.DoesNotExist:
        logger.error("send_notification_task: User pk=%s not found, dropping task.", recipient_id)
        return

    payload = _build_payload(title, body, data, priority, channels)
    channel = channels[0] if channels else "fcm"

    # Persist FIRST so we always have a record
    notification = NotificationRepository.create(payload, recipient, channel=channel)

    try:
        from django_notify_hub.backends import BackendFactory

        backend = BackendFactory.get(channel)
        backend.send(payload, recipient)
        NotificationRepository.update_status(notification.pk, NotificationStatus.SENT.value)

    except Exception as exc:
        NotificationRepository.update_status(notification.pk, NotificationStatus.FAILED.value)

        attempt = self.request.retries
        if attempt < max_retries:
            backoff = app_settings.CELERY_RETRY_BACKOFF * (2 ** attempt)
            logger.warning(
                "send_notification_task: retry %d/%d in %ds for pk=%s | %s",
                attempt + 1, max_retries, backoff, notification.pk, exc,
            )
            raise self.retry(exc=exc, countdown=backoff)
        else:
            logger.error(
                "send_notification_task: max retries reached for pk=%s | %s",
                notification.pk, exc,
            )


@shared_task(
    bind=True,
    max_retries=None,
    default_retry_delay=60,
    queue="notifications",
    name="django_notify_hub.send_many_notifications",
)
def send_many_notifications_task(
    self,
    title: dict,
    body: dict,
    data: dict,
    priority: str,
    channels: list[str],
    recipient_ids: list[Any],
) -> None:
    """Deliver one notification to many users."""
    max_retries = app_settings.CELERY_MAX_RETRIES

    recipients = list(User.objects.filter(pk__in=recipient_ids))
    if not recipients:
        logger.warning("send_many_notifications_task: no valid recipients found.")
        return

    payload = _build_payload(title, body, data, priority, channels)
    channel = channels[0] if channels else "fcm"

    NotificationRepository.bulk_create(payload, recipients, channel=channel)

    try:
        from django_notify_hub.backends import BackendFactory

        backend = BackendFactory.get(channel)
        backend.send_many(payload, recipients)

    except Exception as exc:
        attempt = self.request.retries
        if attempt < max_retries:
            backoff = app_settings.CELERY_RETRY_BACKOFF * (2 ** attempt)
            logger.warning("send_many_notifications_task: retry %d/%d", attempt + 1, max_retries)
            raise self.retry(exc=exc, countdown=backoff)
        else:
            logger.error("send_many_notifications_task: max retries reached | %s", exc)

