"""Token and short-code generation (task 2.1)."""

from app.services.tokens import (
    SHORT_CODE_ALPHABET,
    SHORT_CODE_LENGTH,
    TOKEN_LENGTH,
    generate_short_code,
    generate_token,
    mask_token,
    normalize_short_code,
)


def test_token_length_and_charset() -> None:
    token = generate_token()
    assert len(token) == TOKEN_LENGTH
    assert all(c.isalnum() or c in "-_" for c in token)


def test_tokens_are_unique_across_a_large_sample() -> None:
    """128 bits of entropy — a collision in 10,000 draws would mean the RNG is broken."""
    tokens = {generate_token() for _ in range(10_000)}
    assert len(tokens) == 10_000


def test_tokens_are_not_sequential() -> None:
    """Guessing one token must not reveal the next (PRD §7.6)."""
    first, second = generate_token(), generate_token()
    shared_prefix = sum(1 for a, b in zip(first, second, strict=False) if a == b)
    assert shared_prefix < TOKEN_LENGTH // 2


def test_short_code_avoids_confusable_characters() -> None:
    """Guests read these off a printed card, so 0/O and 1/I/L must not appear."""
    code = generate_short_code()
    assert len(code) == SHORT_CODE_LENGTH
    assert set(code) <= set(SHORT_CODE_ALPHABET)
    assert not (set(code) & set("01OIL"))


def test_short_code_entry_is_forgiving() -> None:
    """Someone typing the code will use lowercase, spaces, and the wrong lookalikes."""
    assert normalize_short_code(" a7k2m9 ") == "A7K2M9"
    assert normalize_short_code("a7k-2m9") == "A7K2M9"
    # O and I do not exist in the alphabet, so they can only mean 0 and 1.
    assert normalize_short_code("aOk2I9") == "A0K219"


def test_masked_tokens_are_safe_to_log() -> None:
    token = generate_token()
    masked = mask_token(token)
    assert token not in masked
    assert len(masked) < len(token)
    assert mask_token("abc") == "***"
