from concurrent.futures import ThreadPoolExecutor

from app.runtime.cache import TTLCache
from app.application.security.passkey import PasskeyChallengeStore


def setup_function():
    PasskeyChallengeStore._cache.clear()


def test_challenge_can_only_be_consumed_once():
    token = PasskeyChallengeStore.issue(
        challenge="server-challenge",
        purpose="authentication",
        user_id=1,
    )

    challenge = PasskeyChallengeStore.consume(
        transaction_token=token,
        purpose="authentication",
    )

    assert challenge is not None
    assert challenge.challenge == "server-challenge"
    assert challenge.user_id == 1
    assert (
        PasskeyChallengeStore.consume(
            transaction_token=token,
            purpose="authentication",
        )
        is None
    )


def test_wrong_purpose_invalidates_transaction():
    token = PasskeyChallengeStore.issue(
        challenge="server-challenge",
        purpose="registration",
        user_id=1,
    )

    assert (
        PasskeyChallengeStore.consume(
            transaction_token=token,
            purpose="authentication",
        )
        is None
    )
    assert (
        PasskeyChallengeStore.consume(
            transaction_token=token,
            purpose="registration",
        )
        is None
    )


def test_expired_challenge_cannot_be_consumed(monkeypatch):
    expired_cache = TTLCache(region="expired_passkey_challenge", maxsize=1, ttl=0)
    monkeypatch.setattr(PasskeyChallengeStore, "_cache", expired_cache)
    token = PasskeyChallengeStore.issue(
        challenge="server-challenge",
        purpose="authentication",
        user_id=None,
    )

    assert (
        PasskeyChallengeStore.consume(
            transaction_token=token,
            purpose="authentication",
        )
        is None
    )


def test_concurrent_consumers_have_single_winner():
    token = PasskeyChallengeStore.issue(
        challenge="server-challenge",
        purpose="authentication",
        user_id=None,
    )

    def consume():
        return PasskeyChallengeStore.consume(
            transaction_token=token,
            purpose="authentication",
        )

    with ThreadPoolExecutor(max_workers=8) as executor:
        results = list(executor.map(lambda _: consume(), range(8)))

    assert sum(result is not None for result in results) == 1
