"""Custom exception types for frameio.auth."""

from __future__ import annotations

import re

# Pattern to detect JWT-like tokens (eyJ...) in error messages
_JWT_PATTERN = re.compile(r"eyJ[A-Za-z0-9_-]{10,}")


def _sanitize_message(msg: str) -> str:
    """Strip JWT-like tokens from error messages to prevent token leakage."""
    return _JWT_PATTERN.sub("[REDACTED]", msg)


class FrameioAuthError(Exception):
    """Base exception for all frameio.auth errors."""


class AuthenticationError(FrameioAuthError):
    """Raised when token exchange or refresh fails.

    Attributes:
        error_code: The OAuth error code (e.g. ``invalid_grant``).
        error_description: Human-readable description from the IMS response.
    """

    def __init__(
        self,
        error_code: str,
        error_description: str | None = None,
    ) -> None:
        self.error_code = error_code
        self.error_description = error_description
        msg = error_code
        if error_description:
            msg = f"{error_code} — {_sanitize_message(error_description)}"
        super().__init__(msg)


class TokenExpiredError(FrameioAuthError):
    """Raised when the refresh token is expired and re-authentication is required."""

    def __init__(self, message: str = "Refresh token expired. Re-authentication required.") -> None:
        super().__init__(_sanitize_message(message))


class NetworkError(FrameioAuthError):
    """Raised when a network request fails (timeout, connection error, etc.)."""

    def __init__(self, message: str = "Network request failed.") -> None:
        super().__init__(message)


class RateLimitError(FrameioAuthError):
    """Raised when the API returns 429 Too Many Requests and retries are exhausted.

    Attributes:
        retry_after: Seconds to wait before retrying (from Retry-After header), or ``None``.
    """

    def __init__(self, retry_after: float | None = None) -> None:
        self.retry_after = retry_after
        msg = "Rate limited by Adobe IMS."
        if retry_after is not None:
            msg += f" Retry after {retry_after:.0f}s."
        super().__init__(msg)


class PKCEError(FrameioAuthError):
    """Raised when PKCE verification fails."""

    def __init__(self, message: str = "PKCE verification failed.") -> None:
        super().__init__(message)


class ConfigurationError(FrameioAuthError):
    """Raised when required configuration is missing or invalid."""

    def __init__(self, message: str) -> None:
        super().__init__(message)
