import warnings
from typing import Optional

from django.conf import settings
from django.db.models import Exists, OuterRef, Value
from django.db.models.functions import Now
from django.db.models.query import QuerySet

from promptgame.gameui.models import Account, AuthAttempt


def accounts_with_cooldown_info(attacker: Optional[Account]) -> QuerySet[Account]:
    """Queryset of all accounts, including cooldown info."""
    return annotate_cooldown_info(queryset=Account.objects.all(), attacker=attacker)


def annotate_cooldown_info(
    queryset: QuerySet[Account],
    attacker: Optional[Account],
) -> QuerySet[Account]:
    """Annotates the provided queryset with information about cooldown state.

    An attacker is in coooldown state with respect to an account if:

    1. They've successfully attacked within the last
       COOLDOWN_MAX_DELTA, and
    2. The defender has not updated their defense since the last such attack.

    Args:
        queryset: The queryset of Account instances.
        attacker: The Account instance representing the attacker (or None if the
            user is signed out).

    Returns:
        QuerySet[Account]: The annotated queryset Has a new attribute
            `attacker_in_cooldown` which indicates whether the attacker is in
            cooldown state w.r.t. the corresponding defender account."""
    if attacker is None:
        # Special case for when user is signed out (e.g. when viewing
        # leaderboard)
        return queryset.annotate(attacker_in_cooldown=Value(False))

    period_delta = settings.COOLDOWN_MAX_DELTA
    # Look for successful attacks which trigger a cooldown
    cooldown_authattempts = (
        AuthAttempt.objects.filter(
            # successful attacks
            was_successful=True,
            # on the defender
            defending_account=OuterRef("pk"),
            # by the attacker
            attacking_account=attacker,
            # that are newer than the active defense
            # (i.e. defender has not updated active defense since then)
            timestamp__gte=OuterRef("active_defense__timestamp"),
        )
        .filter(
            # that happened within the cooldown period
            timestamp__gte=Now()
            - Value(period_delta),
        )
        .exclude(
            # and which aren't self-attacks
            attacking_account=OuterRef("pk")
        )
    )

    return queryset.annotate(attacker_in_cooldown=Exists(cooldown_authattempts))


def cooldown_active(*, defender: Account, attacker: Account) -> bool:
    """Check whether attacker is in cooldown w.r.t. a specific defender."""
    warnings.warn(
        "cooldown_active requires an extra query and is mostly useful for testing; "
        "and migrating old code. Prefer to use annotate_cooldown_info on the source "
        "Account queryset instead, which saves one DB roundtrip per account",
        stacklevel=2,
    )
    result: Optional[bool] = (
        annotate_cooldown_info(Account.objects.filter(pk=defender.pk), attacker)
        .values_list("attacker_in_cooldown", flat=True)
        .first()
    )
    return bool(result)
