"""Test account unlocking."""

from http import HTTPStatus

import pytest

# Weirdly, pylance in vscode claims that AsyncClient doesn't exist, even though
# ctrl+clicking through to django.test.client shows that it does. Thus we apply
# `type: ignore` to this line.
from django.test.client import AsyncClient  # type: ignore
from django.urls import reverse
from django.utils import timezone

import promptgame.gameui.notifications as notify
from promptgame.gameui.models import Account
from promptgame.gameui.user_account_tools import async_create_user_and_account
from promptgame.tests.helpers import async_login, sync_clear_redis_test_db


# setup and teardown should clear the DB
def setup_function() -> None:
    """Clear the Redis test database."""
    sync_clear_redis_test_db()


# setup and teardown should clear the DB
def teardown_function() -> None:
    """Clear the Redis test database."""
    sync_clear_redis_test_db()


# fixture for unlock_url
@pytest.fixture
def unlock_url() -> str:
    """Return the URL for the unlock_account view."""
    return reverse("unlock_account")


@pytest.mark.django_db
@pytest.mark.asyncio
async def test_unlock_account_unauthenticated(
    unlock_url: str, async_client: AsyncClient
) -> None:
    # first case: unauthenticated, POST should redirect to create_account
    resp = await async_client.post(unlock_url)
    assert resp.status_code == HTTPStatus.FOUND
    assert reverse("create_account") == resp.url


@pytest.mark.django_db
@pytest.mark.asyncio
async def test_unlock_account_get(unlock_url: str, async_client: AsyncClient) -> None:
    # second case: GET should give 405
    resp = await async_client.get(unlock_url)
    assert resp.status_code == HTTPStatus.METHOD_NOT_ALLOWED


async def got_unlock_message(store: notify.MessageStore) -> None:
    messages = await store.get_messages()
    assert len(messages) == 1
    (message,) = messages
    assert isinstance(message, notify.LockStateChanged)


async def check_locked(account: Account) -> None:
    await account.arefresh_from_db()
    assert account.locked


async def check_unlocked(account: Account) -> None:
    await account.arefresh_from_db()
    assert not account.locked


@pytest.mark.django_db
@pytest.mark.asyncio
async def test_unlock_unlocked_account(
    unlock_url: str, async_client: AsyncClient
) -> None:
    # third case: an unlocked account should remain unlocked, send a
    # notification (regardless), and redirect to subaccounts
    user, account = await async_create_user_and_account()
    await async_login(user, async_client)

    # unlock one minute before now
    account.unlock_at = timezone.now() - timezone.timedelta(minutes=1)
    await account.asave()
    await check_unlocked(account)

    async with notify.MessageStore(for_account_id=account.account_id) as message_store:
        # fetch
        resp = await async_client.post(unlock_url)

        # make sure we redirect to subaccounts
        assert resp.status_code == HTTPStatus.FOUND
        assert reverse("subaccounts") == resp.url

        # make sure we unlock in the past
        await check_unlocked(account)

        # make sure that the event stream yields one event
        await got_unlock_message(message_store)


@pytest.mark.django_db
@pytest.mark.asyncio
async def test_unlock_locked_account(
    unlock_url: str, async_client: AsyncClient
) -> None:
    # fourth case: a locked account should become unlocked, send a notification,
    # and redirect to subaccounts
    user, account = await async_create_user_and_account()
    await async_login(user, async_client)

    # unlock in the future (and sanity check)
    account.unlock_at = timezone.now() + timezone.timedelta(minutes=1)
    await account.asave()
    await check_locked(account)

    async with notify.MessageStore(for_account_id=account.account_id) as message_store:
        # fetch
        resp = await async_client.post(unlock_url)

        # make sure we also redirect to subaccounts
        assert resp.status_code == HTTPStatus.FOUND
        assert reverse("subaccounts") == resp.url

        # make sure we unlock in the past
        await check_unlocked(account)

        # make sure that the event stream yields one event
        await got_unlock_message(message_store)


@pytest.mark.django_db
@pytest.mark.asyncio
async def test_unlock_time(unlock_url: str, async_client: AsyncClient) -> None:
    user, account = await async_create_user_and_account()
    assert account.locked
    assert account.unlock_at is None
    await async_login(user, async_client)
    resp = await async_client.post(unlock_url)
    account = await Account.objects.aget(account_id=account.account_id)
    assert not account.unlock_at is None
    assert not account.locked
