"""
Exception taxonomy for Gmail integration.

gmail_client._call() maps HTTP status codes to these exception types. The retry
loop in gmail_client._with_retry() inspects the class hierarchy to decide
whether to back off + retry or fail fast.
"""


class GmailError(Exception):
    """Base class for all Gmail-integration errors."""

    def __init__(self, message: str = "", *, status: int = 0, payload: object = None):
        super().__init__(message)
        self.status = status
        self.payload = payload


class AuthError(GmailError):
    """401 / 403 that is not a rate-limit. Requires operator intervention
    (bad service account key, revoked OAuth, missing DWD scope). Not retried."""


class RateLimitError(GmailError):
    """429 or 403 with reason in {rateLimitExceeded, userRateLimitExceeded}.
    Retried with exponential backoff."""


class TransientError(GmailError):
    """5xx or network error. Retried with exponential backoff."""


class NotFoundError(GmailError):
    """404. For messages this usually means 'deleted in Gmail after we saw it';
    for history this is HistoryExpiredError."""


class HistoryExpiredError(NotFoundError):
    """404 on history.list with startHistoryId — Gmail only retains history
    for a limited window (~7 days). Triggers date-based backfill fallback."""


class PermanentError(GmailError):
    """400 or other permanent failure. Skip and log; do not retry."""


def classify_http(status: int, payload: object) -> GmailError:
    """Map a Gmail API response to the appropriate exception type.

    `payload` is the decoded JSON body if any; used to distinguish rate-limit
    403s from pure auth 403s via the `reason` field Google returns.
    """
    reason = ""
    if isinstance(payload, dict):
        try:
            errs = payload.get("error", {}).get("errors") or []
            if errs:
                reason = str(errs[0].get("reason", ""))
        except Exception:  # pragma: no cover — defensive
            pass

    if status == 429:
        return RateLimitError(f"rate limited: {reason}", status=status, payload=payload)
    if status == 403:
        if reason in ("rateLimitExceeded", "userRateLimitExceeded", "quotaExceeded"):
            return RateLimitError(f"quota: {reason}", status=status, payload=payload)
        return AuthError(f"forbidden: {reason}", status=status, payload=payload)
    if status == 401:
        return AuthError("unauthorized", status=status, payload=payload)
    if status == 404:
        return NotFoundError("not found", status=status, payload=payload)
    if 500 <= status < 600:
        return TransientError(f"server error {status}", status=status, payload=payload)
    if 400 <= status < 500:
        return PermanentError(f"client error {status}: {reason}", status=status, payload=payload)
    return GmailError(f"unexpected status {status}", status=status, payload=payload)


RETRYABLE = (RateLimitError, TransientError)
