"""Normalized error categories for the inference gateway.

Maps provider-specific failures into a protocol-agnostic taxonomy
so that retry logic, alerting, and metrics work uniformly across
Ollama, OpenAI-compatible, and specialist endpoints.

Requirements: 2.1, 2.9
"""
from __future__ import annotations

from enum import Enum


class InferenceErrorCategory(str, Enum):
    """Normalized error categories for inference failures."""

    # Network / transport
    TIMEOUT = "timeout"
    CONNECTION_REFUSED = "connection_refused"
    CONNECTION_ERROR = "connection_error"

    # Authentication / authorization
    AUTH_FAILED = "auth_failed"
    FORBIDDEN = "forbidden"

    # Rate limiting
    RATE_LIMITED = "rate_limited"

    # Server errors
    SERVER_ERROR = "server_error"
    SERVICE_UNAVAILABLE = "service_unavailable"

    # Client errors
    BAD_REQUEST = "bad_request"
    MODEL_NOT_FOUND = "model_not_found"
    INVALID_REQUEST = "invalid_request"

    # Response problems
    INVALID_RESPONSE = "invalid_response"
    EMPTY_RESPONSE = "empty_response"
    SCHEMA_VIOLATION = "schema_violation"

    # Capability / policy
    CAPABILITY_UNAVAILABLE = "capability_unavailable"
    POLICY_VIOLATION = "policy_violation"

    # Ollama-specific
    STALL_DETECTED = "stall_detected"

    # Unknown
    UNKNOWN = "unknown"

    @property
    def retryable(self) -> bool:
        """Whether this error category should generally be retried."""
        return self in _RETRYABLE_CATEGORIES


_RETRYABLE_CATEGORIES = frozenset({
    InferenceErrorCategory.TIMEOUT,
    InferenceErrorCategory.CONNECTION_ERROR,
    InferenceErrorCategory.CONNECTION_REFUSED,
    InferenceErrorCategory.SERVER_ERROR,
    InferenceErrorCategory.SERVICE_UNAVAILABLE,
    InferenceErrorCategory.RATE_LIMITED,
    InferenceErrorCategory.STALL_DETECTED,
    InferenceErrorCategory.EMPTY_RESPONSE,
})


class InferenceError(Exception):
    """Typed inference error with category and optional provider detail."""

    def __init__(
        self,
        category: InferenceErrorCategory,
        message: str = "",
        *,
        provider_detail: str | None = None,
        status_code: int | None = None,
    ) -> None:
        self.category = category
        self.provider_detail = provider_detail
        self.status_code = status_code
        super().__init__(message or category.value)

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