"""RIMS Operations Celery tasks.

P1 (assessment §3 #10 + §4 N7) — MOU lifecycle automation.

- ``expire_mous`` flips ACTIVE MOUs to EXPIRED once their ``expires_on`` date
  has passed and notifies the relationship owner / ADMIN role.
- ``remind_mou_renewals`` warns the same recipients when an ACTIVE MOU is
  within 30 days of expiry; per-MOU dedup via ``last_renewal_reminder_at``
  prevents daily spam.
"""
from __future__ import annotations

import logging
from datetime import timedelta

from celery import shared_task
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.urls import reverse
from django.utils import timezone

from apps.core.notifications.emailing import rims_email_context
from apps.core.notifications.models import Notification
from apps.core.notifications.services import bulk_notify
from apps.core.permissions.roles import UserRole
from apps.rims.operations.models import MOU

logger = logging.getLogger(__name__)
User = get_user_model()

# 7-day window between renewal reminders. The 30-day pre-expiry warning fires
# once when the MOU first enters the window, then re-fires every 7 days until
# the MOU is either renewed (status leaves ACTIVE) or expires.
RENEWAL_REMINDER_DEDUP_DAYS = 7
RENEWAL_REMINDER_HORIZON_DAYS = 30


def _admin_pool():
    return list(
        User.objects.filter(is_active=True)
        .filter(Q(role__in=[UserRole.ADMIN, UserRole.SYSTEM_ADMIN]))
        .distinct()
    )


def _detail_path(mou: MOU) -> str:
    try:
        return reverse("rims_operations:mou_detail", kwargs={"pk": mou.pk})
    except Exception:  # noqa: BLE001
        return reverse("rims_operations:mou_list")


@shared_task
def expire_mous() -> int:
    """Flip ACTIVE MOUs to EXPIRED once their `expires_on` date has elapsed."""
    today = timezone.now().date()
    qs = (
        MOU.objects.filter(status=MOU.Status.ACTIVE, expires_on__isnull=False)
        .filter(expires_on__lt=today)
        .select_related("partner")
    )
    flipped = 0
    admins = _admin_pool()
    ct = ContentType.objects.get_for_model(MOU)
    for mou in qs:
        mou.status = MOU.Status.EXPIRED
        mou.save(update_fields=["status"])
        flipped += 1
        if not admins:
            continue
        ctx = rims_email_context(
            subject=f"MOU expired: {mou.title}",
            headline="MOU has expired",
            action_path=_detail_path(mou),
            action_label="Open MOU list",
            preheader=f"{mou.partner.name} — {mou.title}",
        )
        bulk_notify(
            admins,
            (
                f'The MOU "{mou.title}" with {mou.partner.name} has expired '
                f"(expires_on={mou.expires_on}). Plan a renewal or archive the "
                "record."
            ),
            verb=Notification.Verb.RIMS_MOU_EXPIRED,
            email_context=ctx,
            content_type=ct,
            object_id=str(mou.pk),
            action_url=_detail_path(mou),
        )
    if flipped:
        logger.info("expire_mous: flipped %s ACTIVE MOU(s) to EXPIRED", flipped)
    return flipped


@shared_task
def remind_mou_renewals() -> int:
    """Warn admins when an ACTIVE MOU is within 30 days of `expires_on`.

    Per-MOU dedup via `last_renewal_reminder_at` — re-fires every 7 days so
    a long-running MOU doesn't spam recipients daily but also doesn't fall
    silent for a month.
    """
    today = timezone.now().date()
    horizon = today + timedelta(days=RENEWAL_REMINDER_HORIZON_DAYS)
    qs = (
        MOU.objects.filter(status=MOU.Status.ACTIVE, expires_on__isnull=False)
        .filter(expires_on__gte=today, expires_on__lte=horizon)
        .select_related("partner")
    )
    sent = 0
    now = timezone.now()
    dedup_cutoff = now - timedelta(days=RENEWAL_REMINDER_DEDUP_DAYS)
    admins = _admin_pool()
    if not admins:
        return 0
    ct = ContentType.objects.get_for_model(MOU)
    for mou in qs:
        if mou.last_renewal_reminder_at and mou.last_renewal_reminder_at > dedup_cutoff:
            continue
        days_remaining = (mou.expires_on - today).days
        ctx = rims_email_context(
            subject=f"MOU expires in {days_remaining}d: {mou.title}",
            headline="MOU renewal due soon",
            action_path=_detail_path(mou),
            action_label="Open MOU list",
            preheader=f"{mou.partner.name} — expires {mou.expires_on}",
        )
        bulk_notify(
            admins,
            (
                f'The MOU "{mou.title}" with {mou.partner.name} expires on '
                f"{mou.expires_on} ({days_remaining} day(s) remaining). Start "
                "renewal discussions or supersede the record."
            ),
            verb=Notification.Verb.RIMS_MOU_EXPIRY_WARNING,
            email_context=ctx,
            content_type=ct,
            object_id=str(mou.pk),
            action_url=_detail_path(mou),
        )
        mou.last_renewal_reminder_at = now
        mou.save(update_fields=["last_renewal_reminder_at"])
        sent += 1
    if sent:
        logger.info("remind_mou_renewals: sent %s reminder(s)", sent)
    return sent
