"""Queryset builders for judge-reminder and expired-draft Celery tasks.

These are *not* tasks themselves — Celery tasks live in each SP's
``tasks.py`` and call into these builders. Keeping the rules here means
the SP2 reminder, SP4 challenge reminder, and SP5 funding-call reminder
all use one definition of "judge has not scored yet" and "deadline
expired".
"""
from __future__ import annotations

from datetime import timedelta

from django.db.models import QuerySet
from django.utils import timezone

from apps.smehub._shared.calls.status import ACTIVE_REVIEW_STATES, ApplicationStatus


def judge_reminder_candidates(
    assignments_qs: QuerySet,
    *,
    score_exists_field: str,
    days_pending: int = 3,
    notified_at_field: str = "notified_at",
    last_reminded_at_field: str = "last_reminded_at",
    created_at_field: str = "created_at",
    status_field: str = "application__status",
) -> QuerySet:
    """Filter a ``JudgeAssignment``-shaped queryset to assignments that
    deserve a reminder right now (FRSME-INC021 and SP4/SP5 analogues).

    A row is a candidate iff:
      * the linked application is still in an active review state, AND
      * the judge has not yet submitted a score for the application, AND
      * the last reminder (or original notification, or creation) was more
        than ``days_pending`` days ago.

    The caller annotates the queryset with ``score_exists_field`` (e.g.
    ``has_score=Exists(JudgeScore.objects.filter(...))``) so this helper
    can stay storage-agnostic.

    Returns a *queryset* — caller iterates and dispatches the reminder.
    """
    cutoff = timezone.now() - timedelta(days=days_pending)
    qs = assignments_qs.filter(**{f"{status_field}__in": ACTIVE_REVIEW_STATES})
    qs = qs.filter(**{score_exists_field: False})
    # Reminder cadence: prefer last reminder timestamp, fall back to first
    # notification, fall back to creation. Any of these older than the
    # cutoff means the judge is overdue.
    return qs.exclude(**{f"{last_reminded_at_field}__gt": cutoff}).exclude(
        **{f"{last_reminded_at_field}__isnull": True, f"{notified_at_field}__gt": cutoff},
    ).exclude(
        **{
            f"{last_reminded_at_field}__isnull": True,
            f"{notified_at_field}__isnull": True,
            f"{created_at_field}__gt": cutoff,
        }
    )


def expired_draft_candidates(
    application_qs: QuerySet,
    *,
    deadline_field: str = "programme__application_deadline",
    status_field: str = "status",
) -> QuerySet:
    """Filter an Application-shaped queryset to drafts whose call deadline
    has already passed (FRSME-INC014 alt-flow A2).

    Caller is responsible for marking the rows ``NOT_SUBMITTED`` and
    notifying the entrepreneur — this helper just identifies them.
    """
    now = timezone.now()
    return application_qs.filter(
        **{
            f"{status_field}": ApplicationStatus.DRAFT.value,
            f"{deadline_field}__lt": now,
        }
    ).exclude(**{f"{deadline_field}__isnull": True})


# Re-export commonly used imports so SP services don't have to dig.
__all__ = [
    "ACTIVE_REVIEW_STATES",
    "ApplicationStatus",
    "judge_reminder_candidates",
    "expired_draft_candidates",
]
