from typing import Optional

import pytest
from django.utils import timezone

from promptgame.gameui.cooldown import annotate_cooldown_info, cooldown_active
from promptgame.gameui.models import Account, AuthAttempt, Defense
from promptgame.gameui.user_account_tools import create_user_and_account


@pytest.fixture
def attacker_account() -> Account:
    _, account = create_user_and_account()
    return account


@pytest.fixture
def defender_account() -> Account:
    _, account = create_user_and_account()
    return account


def create_attack(
    defender: Account,
    attacker: Account,
    *,
    was_successful: bool = True,
    timestamp: Optional[timezone.datetime] = None
) -> AuthAttempt:
    """Helper function to create an attack."""
    attempt = AuthAttempt.objects.create(
        defending_account=defender,
        attacking_account=attacker,
        was_successful=was_successful,
    )
    if timestamp:
        attempt.timestamp = timestamp
        attempt.save()
    return attempt


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_never_attacked(
    defender_account: Account, attacker_account: Account
) -> None:
    assert not cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_attacked_long_ago(
    defender_account: Account, attacker_account: Account, settings
) -> None:
    attack_time = timezone.now() - (
        settings.COOLDOWN_MAX_DELTA + timezone.timedelta(seconds=10)
    )
    create_attack(defender_account, attacker_account, timestamp=attack_time)
    assert not cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_after_defense_change(
    defender_account: Account, attacker_account: Account
) -> None:
    create_attack(defender_account, attacker_account)
    defender_account.active_defense = Defense.objects.create(
        creator_account=defender_account
    )
    defender_account.save()
    assert not cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_active(defender_account: Account, attacker_account: Account) -> None:
    create_attack(defender_account, attacker_account)
    assert cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_unsuccessful_attack(
    defender_account: Account, attacker_account: Account
) -> None:
    create_attack(defender_account, attacker_account, was_successful=False)
    assert not cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_both_success_states(
    defender_account: Account, attacker_account: Account
) -> None:
    create_attack(defender_account, attacker_account)
    create_attack(defender_account, attacker_account, was_successful=False)
    assert cooldown_active(defender=defender_account, attacker=attacker_account)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_multiple_attackers(defender_account: Account) -> None:
    _, attacker1 = create_user_and_account()
    _, attacker2 = create_user_and_account()

    create_attack(defender_account, attacker1)
    create_attack(defender_account, attacker2)

    assert cooldown_active(defender=defender_account, attacker=attacker1)
    assert cooldown_active(defender=defender_account, attacker=attacker2)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_after_defense_change_second_attack(defender_account: Account) -> None:
    _, attacker1 = create_user_and_account()
    _, attacker2 = create_user_and_account()

    create_attack(defender_account, attacker1)
    defender_account.active_defense = Defense.objects.create(
        creator_account=defender_account
    )
    defender_account.save()
    create_attack(defender_account, attacker2)

    assert not cooldown_active(defender=defender_account, attacker=attacker1)
    assert cooldown_active(defender=defender_account, attacker=attacker2)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_cooldown_two_attackers_separated_by_time(
    defender_account: Account, settings
) -> None:
    _, attacker1 = create_user_and_account()
    _, attacker2 = create_user_and_account()

    attack_time = timezone.now() - (
        settings.COOLDOWN_MAX_DELTA + timezone.timedelta(seconds=10)
    )
    create_attack(defender_account, attacker1, timestamp=attack_time)
    create_attack(defender_account, attacker2)

    assert not cooldown_active(defender=defender_account, attacker=attacker1)
    assert cooldown_active(defender=defender_account, attacker=attacker2)


@pytest.mark.filterwarnings("ignore:cooldown_active")
@pytest.mark.django_db
def test_annotate_cooldown_info() -> None:
    # Create two defender accounts and one attacker account
    _, defender1 = create_user_and_account()
    _, defender2 = create_user_and_account()
    _, attacker = create_user_and_account()

    # Attack only one of the defenders
    create_attack(defender1, attacker)

    # Fetch the two defender accounts and annotate them
    defenders = Account.objects.filter(pk__in=[defender1.pk, defender2.pk])
    annotated_defenders = annotate_cooldown_info(defenders, attacker)

    # Convert the annotated queryset to a dictionary with account id as key and
    # 'attacker_in_cooldown' as value for easier lookup
    cooldown_status = {
        account.pk: account.attacker_in_cooldown for account in annotated_defenders
    }

    # Assert that the attacker is only locked w.r.t. defender1
    assert cooldown_status[defender1.pk] == True
    assert cooldown_status[defender2.pk] == False
