"""
Unit tests for PKCE utility functions.

These tests verify RFC 7636 compliance for PKCE code generation
and validation functions.
"""

import pytest
import re

from pygeai_gam.pkce import (
    generate_code_verifier,
    generate_code_challenge,
    _validate_code_verifier,
)


class TestGenerateCodeVerifier:
    """Test generate_code_verifier function."""

    def test_generate_code_verifier_returns_string(self):
        """Test that generate_code_verifier returns a string."""
        verifier = generate_code_verifier()
        assert isinstance(verifier, str)

    def test_generate_code_verifier_correct_length(self):
        """Test that code_verifier is exactly 43 characters."""
        verifier = generate_code_verifier()
        assert len(verifier) == 43

    def test_generate_code_verifier_valid_characters(self):
        """Test that code_verifier contains only unreserved characters."""
        verifier = generate_code_verifier()
        pattern = r"^[A-Za-z0-9\-._~]+$"
        assert re.match(pattern, verifier) is not None

    def test_generate_code_verifier_is_unique(self):
        """Test that multiple calls generate different verifiers."""
        verifier1 = generate_code_verifier()
        verifier2 = generate_code_verifier()
        verifier3 = generate_code_verifier()

        assert verifier1 != verifier2
        assert verifier2 != verifier3
        assert verifier1 != verifier3

    def test_generate_code_verifier_cryptographically_random(self):
        """Test that generated verifiers appear random."""
        verifiers = [generate_code_verifier() for _ in range(10)]
        unique_verifiers = set(verifiers)
        assert len(unique_verifiers) == 10


class TestGenerateCodeChallenge:
    """Test generate_code_challenge function."""

    def test_generate_code_challenge_returns_string(self):
        """Test that generate_code_challenge returns a string."""
        verifier = generate_code_verifier()
        challenge = generate_code_challenge(verifier)
        assert isinstance(challenge, str)

    def test_generate_code_challenge_correct_length(self):
        """Test that code_challenge is 43 characters (base64url SHA256)."""
        verifier = generate_code_verifier()
        challenge = generate_code_challenge(verifier)
        assert len(challenge) == 43

    def test_generate_code_challenge_valid_characters(self):
        """Test that code_challenge contains only base64url characters."""
        verifier = generate_code_verifier()
        challenge = generate_code_challenge(verifier)
        pattern = r"^[A-Za-z0-9\-_]+$"
        assert re.match(pattern, challenge) is not None

    def test_generate_code_challenge_no_padding(self):
        """Test that code_challenge has no padding characters."""
        verifier = generate_code_verifier()
        challenge = generate_code_challenge(verifier)
        assert "=" not in challenge

    def test_generate_code_challenge_rfc7636_test_vector(self):
        """Test against RFC 7636 Appendix B test vector."""
        verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
        expected_challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

        challenge = generate_code_challenge(verifier)
        assert challenge == expected_challenge

    def test_generate_code_challenge_deterministic(self):
        """Test that same verifier produces same challenge."""
        verifier = generate_code_verifier()
        challenge1 = generate_code_challenge(verifier)
        challenge2 = generate_code_challenge(verifier)
        assert challenge1 == challenge2

    def test_generate_code_challenge_different_for_different_verifiers(self):
        """Test that different verifiers produce different challenges."""
        verifier1 = generate_code_verifier()
        verifier2 = generate_code_verifier()
        challenge1 = generate_code_challenge(verifier1)
        challenge2 = generate_code_challenge(verifier2)
        assert challenge1 != challenge2


class TestValidateCodeVerifier:
    """Test _validate_code_verifier function."""

    def test_validate_code_verifier_valid_43_chars(self):
        """Test validation passes for valid 43-char verifier."""
        verifier = generate_code_verifier()
        _validate_code_verifier(verifier)

    def test_validate_code_verifier_valid_128_chars(self):
        """Test validation passes for maximum length verifier."""
        verifier = "A" * 128
        _validate_code_verifier(verifier)

    def test_validate_code_verifier_valid_64_chars(self):
        """Test validation passes for mid-range length verifier."""
        verifier = "A" * 64
        _validate_code_verifier(verifier)

    def test_validate_code_verifier_empty_raises_error(self):
        """Test that empty string raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            _validate_code_verifier("")
        assert "cannot be empty" in str(exc_info.value)

    def test_validate_code_verifier_too_short_raises_error(self):
        """Test that verifier shorter than 43 chars raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            _validate_code_verifier("A" * 42)
        assert "at least 43 characters" in str(exc_info.value)

    def test_validate_code_verifier_too_long_raises_error(self):
        """Test that verifier longer than 128 chars raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            _validate_code_verifier("A" * 129)
        assert "at most 128 characters" in str(exc_info.value)

    def test_validate_code_verifier_invalid_char_space_raises_error(self):
        """Test that verifier with space character raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            _validate_code_verifier("A" * 42 + " ")
        assert "invalid characters" in str(exc_info.value)

    def test_validate_code_verifier_invalid_char_special_raises_error(self):
        """Test that verifier with special character raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            _validate_code_verifier("A" * 42 + "@")
        assert "invalid characters" in str(exc_info.value)

    def test_validate_code_verifier_all_valid_chars(self):
        """Test that all unreserved characters are accepted."""
        valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
        verifier = (valid_chars * 2)[:43]
        _validate_code_verifier(verifier)


class TestPKCEIntegration:
    """Integration tests for PKCE flow."""

    def test_complete_pkce_flow(self):
        """Test complete PKCE generation and validation."""
        verifier = generate_code_verifier()
        challenge = generate_code_challenge(verifier)

        assert len(verifier) == 43
        assert len(challenge) == 43
        assert verifier != challenge

    def test_multiple_pkce_flows_are_unique(self):
        """Test that multiple PKCE flows generate unique values."""
        flows = []
        for _ in range(5):
            verifier = generate_code_verifier()
            challenge = generate_code_challenge(verifier)
            flows.append((verifier, challenge))

        verifiers = [f[0] for f in flows]
        challenges = [f[1] for f in flows]

        assert len(set(verifiers)) == 5
        assert len(set(challenges)) == 5
