import pytest
import re # For regular expressions to check URL-safe characters
import hashlib
import base64
from posit_keyring.pkce import new_pkce_verifier, new_pkce_challenge

# Test for new_pkce_verifier function
def test_new_pkce_verifier_default_length():
    """
    Test new_pkce_verifier with default length (43).
    """
    verifier = new_pkce_verifier()
    assert isinstance(verifier, str)
    assert len(verifier) == 43
    # Check if it's URL-safe Base64 characters (alphanumeric, -, _)
    assert re.fullmatch(r"^[a-zA-Z0-9\-_]+$", verifier)

def test_new_pkce_verifier_min_length():
    """
    Test new_pkce_verifier with minimum length (43).
    """
    verifier = new_pkce_verifier(length=43)
    assert isinstance(verifier, str)
    assert len(verifier) == 43
    assert re.fullmatch(r"^[a-zA-Z0-9\-_]+$", verifier)

def test_new_pkce_verifier_max_length():
    """
    Test new_pkce_verifier with maximum length (128).
    """
    verifier = new_pkce_verifier(length=128)
    assert isinstance(verifier, str)
    assert len(verifier) == 128
    assert re.fullmatch(r"^[a-zA-Z0-9\-_]+$", verifier)

@pytest.mark.parametrize("invalid_length", [0, 42, 129, 200])
def test_new_pkce_verifier_invalid_length(invalid_length: int):
    """
    Test new_pkce_verifier with invalid lengths, expecting a ValueError.
    """
    with pytest.raises(ValueError) as excinfo:
        new_pkce_verifier(length=invalid_length)
    assert "Verifier length must be between 43 and 128 characters." in str(excinfo.value)

def test_new_pkce_verifier_randomness():
    """
    Test that consecutive verifiers are different, ensuring randomness.
    (Though with very high probability, not 100% guarantee for randomness)
    """
    verifier1 = new_pkce_verifier()
    verifier2 = new_pkce_verifier()
    assert verifier1 != verifier2

# Test for new_pkce_challenge function
def test_new_pkce_challenge_valid_verifier():
    """
    Test new_pkce_challenge with a known valid verifier.
    """
    # This verifier is known to produce a specific challenge
    test_verifier = "M_s-Q0i7cK4_r7z_d3f8m9g2h1j6k5l0p9o8i7u6y5t4r3e2w1q" # Example 43-char verifier

    # Manually calculate the expected challenge
    sha256_hash = hashlib.sha256(test_verifier.encode('ascii')).digest()
    expected_challenge = base64.urlsafe_b64encode(sha256_hash).rstrip(b'=').decode('ascii')

    challenge = new_pkce_challenge(test_verifier)

    assert isinstance(challenge, str)
    assert challenge == expected_challenge
    # PKCE challenges are always 43 characters long (after SHA256 + Base64url-encode without padding)
    assert len(challenge) == 43
    # Check if it's URL-safe Base64 characters (alphanumeric, -, _)
    assert re.fullmatch(r"^[a-zA-Z0-9\-_]+$", challenge)


def test_new_pkce_challenge_with_generated_verifier():
    """
    Test new_pkce_challenge with a verifier generated by new_pkce_verifier.
    Ensures the two functions work together as expected.
    """
    verifier = new_pkce_verifier(length=100) # Use a different length for more comprehensive testing
    challenge = new_pkce_challenge(verifier)

    # Manually calculate expected challenge from the generated verifier
    sha256_hash = hashlib.sha256(verifier.encode('ascii')).digest()
    expected_challenge = base64.urlsafe_b64encode(sha256_hash).rstrip(b'=').decode('ascii')

    assert isinstance(challenge, str)
    assert challenge == expected_challenge
    assert len(challenge) == 43 # Challenge length is fixed for SHA256 hashes
    assert re.fullmatch(r"^[a-zA-Z0-9\-_]+$", challenge)

def test_new_pkce_challenge_empty_verifier():
    """
    Test new_pkce_challenge with an empty string verifier.
    """
    empty_verifier = ""
    sha256_hash = hashlib.sha256(empty_verifier.encode('ascii')).digest()
    expected_challenge = base64.urlsafe_b64encode(sha256_hash).rstrip(b'=').decode('ascii')

    challenge = new_pkce_challenge(empty_verifier)
    assert challenge == expected_challenge
    assert len(challenge) == 43

def test_new_pkce_challenge_non_ascii_verifier_error():
    """
    Test new_pkce_challenge with a non-ASCII verifier.
    It should ideally raise an error if not handled, but your current code
    encodes with 'ascii' which will raise an error for non-ASCII chars.
    """
    non_ascii_verifier = "verifier_with_é"
    with pytest.raises(UnicodeEncodeError) as excinfo:
        new_pkce_challenge(non_ascii_verifier)
    assert "codec can't encode character" in str(excinfo.value)
    # This confirms the expected behavior if non-ASCII is passed to .encode('ascii')
    # If your application logic expects non-ASCII, you'd change the encoding.