"""Unified error taxonomy for OpenAI API interactions.

Provides a single error classifier used by retry logic and client wrappers
to determine whether errors are transient (retryable) or fatal.
"""

from __future__ import annotations

from enum import Enum
from typing import Optional

import openai


class ErrorCategory(str, Enum):
    """Classification of OpenAI API errors."""

    TRANSIENT = "transient"       # 5xx, connection errors — safe to retry
    RATE_LIMIT = "rate_limit"     # 429 — safe to retry with backoff
    TIMEOUT = "timeout"           # Request timeout — safe to retry
    AUTH = "auth"                 # 401/403 — never retry
    VALIDATION = "validation"     # 400 bad request — never retry
    UNKNOWN = "unknown"           # Unclassified — do not retry by default


# Categories that are safe to retry
RETRYABLE_CATEGORIES = frozenset({
    ErrorCategory.TRANSIENT,
    ErrorCategory.RATE_LIMIT,
    ErrorCategory.TIMEOUT,
})


class LLMError(Exception):
    """Wrapper exception with error classification metadata.

    Attributes:
        category: The classified error category.
        original: The original exception that was classified.
    """

    def __init__(
        self,
        message: str,
        category: ErrorCategory,
        original: Optional[Exception] = None,
    ):
        super().__init__(message)
        self.category = category
        self.original = original

    @property
    def retryable(self) -> bool:
        return self.category in RETRYABLE_CATEGORIES

    def __repr__(self) -> str:
        return f"LLMError(category={self.category.value!r}, message={str(self)!r})"


def classify_openai_error(exc: Exception) -> ErrorCategory:
    """Classify an OpenAI SDK exception into an ErrorCategory.

    Mapping:
        RateLimitError        → RATE_LIMIT
        APITimeoutError       → TIMEOUT
        APIConnectionError    → TRANSIENT
        InternalServerError   → TRANSIENT
        AuthenticationError   → AUTH
        BadRequestError       → VALIDATION
        PermissionDeniedError → AUTH
        NotFoundError         → VALIDATION
        Other                 → UNKNOWN
    """
    if isinstance(exc, openai.RateLimitError):
        return ErrorCategory.RATE_LIMIT
    if isinstance(exc, openai.APITimeoutError):
        return ErrorCategory.TIMEOUT
    if isinstance(exc, openai.APIConnectionError):
        return ErrorCategory.TRANSIENT
    if isinstance(exc, openai.InternalServerError):
        return ErrorCategory.TRANSIENT
    if isinstance(exc, openai.AuthenticationError):
        return ErrorCategory.AUTH
    if isinstance(exc, openai.BadRequestError):
        return ErrorCategory.VALIDATION
    if isinstance(exc, openai.PermissionDeniedError):
        return ErrorCategory.AUTH
    if isinstance(exc, openai.NotFoundError):
        return ErrorCategory.VALIDATION

    # Fallback: check for status_code attribute (older SDK or wrapped exceptions)
    status = getattr(exc, "status_code", None) or getattr(exc, "status", None)
    if status is not None:
        if status == 429:
            return ErrorCategory.RATE_LIMIT
        if status in {500, 502, 503, 504}:
            return ErrorCategory.TRANSIENT
        if status in {401, 403}:
            return ErrorCategory.AUTH
        if status == 400:
            return ErrorCategory.VALIDATION

    return ErrorCategory.UNKNOWN
