"""
PKCE (Proof Key for Code Exchange) utility functions for OAuth 2.0.

This module provides cryptographic utilities for implementing PKCE as defined
in RFC 7636. PKCE is an extension to the OAuth 2.0 Authorization Code Grant
that prevents authorization code interception attacks.

References:
    RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients
    https://datatracker.ietf.org/doc/html/rfc7636
"""

import base64
import hashlib
import re
import secrets
from typing import Pattern


VERIFIER_PATTERN: Pattern = re.compile(r"^[A-Za-z0-9\-._~]+$")
MIN_VERIFIER_LENGTH = 43
MAX_VERIFIER_LENGTH = 128


def generate_code_verifier() -> str:
    """
    Generate a cryptographically secure code verifier for PKCE.

    Creates a high-entropy random string suitable for use as a PKCE code verifier
    according to RFC 7636. The generated string is URL-safe and contains 43
    characters, providing 256 bits of entropy.

    The code verifier is a secret value that must be kept confidential by the
    client and only transmitted during the token exchange step.

    :return: A 43-character URL-safe random string
    :rtype: str

    Example:
        >>> verifier = generate_code_verifier()
        >>> len(verifier)
        43
        >>> print(verifier)
        'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'

    Notes:
        - Uses secrets.token_urlsafe() for cryptographically secure randomness
        - Character set: [A-Z], [a-z], [0-9], "-", "_"
        - Meets RFC 7636 minimum entropy requirement (256 bits)
        - Each invocation produces a unique value

    See Also:
        - generate_code_challenge(): Create challenge from verifier
        - RFC 7636 Section 4.1: https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
    """
    return secrets.token_urlsafe(32)


def generate_code_challenge(code_verifier: str) -> str:
    """
    Generate a code challenge from a code verifier using the S256 method.

    Transforms the code verifier into a code challenge using SHA256 hashing
    and base64url encoding as specified in RFC 7636. This challenge is sent
    to the authorization server and later verified against the code verifier
    during token exchange.

    The S256 transformation method is:
        code_challenge = BASE64URL(SHA256(ASCII(code_verifier)))

    :param code_verifier: The code verifier to transform (43-128 characters)
    :type code_verifier: str
    :return: Base64url-encoded SHA256 hash of the code verifier (43 characters)
    :rtype: str
    :raises ValueError: If code_verifier is invalid (wrong length or characters)

    Example:
        >>> verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
        >>> challenge = generate_code_challenge(verifier)
        >>> print(challenge)
        'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM'

    Security Notes:
        - Only the S256 method is supported (not "plain")
        - The code_verifier should never be transmitted in the authorization request
        - The authorization server will verify: SHA256(verifier) == stored_challenge

    See Also:
        - generate_code_verifier(): Generate a valid verifier
        - RFC 7636 Section 4.2: https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
    """
    _validate_code_verifier(code_verifier)

    digest = hashlib.sha256(code_verifier.encode("ascii")).digest()

    challenge = base64.urlsafe_b64encode(digest).decode("ascii")

    return challenge.rstrip("=")


def _validate_code_verifier(code_verifier: str) -> None:
    """
    Validate that a code verifier meets RFC 7636 requirements.

    Internal validation function that ensures a code verifier conforms to the
    RFC 7636 specification for length and character set.

    :param code_verifier: The code verifier to validate
    :type code_verifier: str
    :raises ValueError: If validation fails with a descriptive error message

    Validation Rules:
        - Length must be between 43 and 128 characters (inclusive)
        - Only unreserved characters allowed: [A-Z], [a-z], [0-9], "-", ".", "_", "~"

    Notes:
        - This is an internal function called by generate_code_challenge()
        - Applications using generate_code_verifier() will always produce valid verifiers

    See Also:
        - RFC 7636 Section 4.1: https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
    """
    if not code_verifier:
        raise ValueError("code_verifier cannot be empty")

    verifier_length = len(code_verifier)
    if verifier_length < MIN_VERIFIER_LENGTH:
        raise ValueError(
            f"code_verifier must be at least {MIN_VERIFIER_LENGTH} characters long, " f"got {verifier_length}"
        )

    if verifier_length > MAX_VERIFIER_LENGTH:
        raise ValueError(
            f"code_verifier must be at most {MAX_VERIFIER_LENGTH} characters long, " f"got {verifier_length}"
        )

    if not VERIFIER_PATTERN.match(code_verifier):
        raise ValueError(
            "code_verifier contains invalid characters. " "Only [A-Z], [a-z], [0-9], '-', '.', '_', '~' are allowed"
        )
