"""PKCE (Proof Key for Code Exchange) utilities.

Implements code_verifier generation and S256 code_challenge derivation
per RFC 7636.
"""

from __future__ import annotations

import base64
import hashlib
import secrets
import string

# Unreserved URI characters allowed in a code verifier (RFC 7636 §4.1)
_VERIFIER_CHARS = string.ascii_letters + string.digits + "-._~"

# Valid verifier length range
_MIN_VERIFIER_LENGTH = 43
_MAX_VERIFIER_LENGTH = 128
_DEFAULT_VERIFIER_LENGTH = 128


def generate_code_verifier(length: int = _DEFAULT_VERIFIER_LENGTH) -> str:
    """Generate a cryptographically random code verifier.

    Args:
        length: Length of the verifier string (43–128 inclusive).

    Returns:
        A random string of unreserved URI characters.

    Raises:
        ValueError: If *length* is outside the allowed range.
    """
    if not (_MIN_VERIFIER_LENGTH <= length <= _MAX_VERIFIER_LENGTH):
        raise ValueError(
            f"code_verifier length must be between {_MIN_VERIFIER_LENGTH} and "
            f"{_MAX_VERIFIER_LENGTH}, got {length}"
        )
    return "".join(secrets.choice(_VERIFIER_CHARS) for _ in range(length))


def generate_code_challenge(verifier: str) -> str:
    """Derive the S256 code challenge from a code verifier.

    ``code_challenge = BASE64URL(SHA256(code_verifier))``

    Args:
        verifier: The code verifier string.

    Returns:
        Base64url-encoded (no padding) SHA-256 hash of the verifier.
    """
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
