"""
Custom exception hierarchy.

Why this matters in production: a raw Python exception (KeyError,
ValueError, AttributeError from deep inside a model) should NEVER reach
the client directly. It might leak file paths, internal variable names,
or stack traces — that's an information disclosure risk, and it's also
just a bad client experience.

Every exception here carries:
- a client-safe `message` (shown to the user)
- an internal `detail` (logged, never returned)
- an HTTP status code
- an error `code` (machine-readable, e.g. "MODEL_INFERENCE_FAILED") so
  the frontend can branch on it without parsing message text.
"""

from typing import Any, Optional


class AppException(Exception):
    """Base class for all application-raised exceptions."""

    status_code: int = 500
    error_code: str = "INTERNAL_ERROR"

    def __init__(
        self,
        message: str,
        detail: Optional[str] = None,
        extra: Optional[dict[str, Any]] = None,
    ):
        self.message = message
        self.detail = detail or message
        self.extra = extra or {}
        super().__init__(message)


class ValidationFailedError(AppException):
    status_code = 422
    error_code = "VALIDATION_FAILED"


class UnsupportedCropError(AppException):
    status_code = 400
    error_code = "UNSUPPORTED_CROP"


class UnsupportedDistrictError(AppException):
    status_code = 400
    error_code = "UNSUPPORTED_DISTRICT"


class ModelNotLoadedError(AppException):
    """Raised when a required model file is missing or failed to load at startup."""
    status_code = 503
    error_code = "MODEL_NOT_LOADED"


class ModelInferenceError(AppException):
    """Raised when a model runs but produces an invalid/unusable result."""
    status_code = 500
    error_code = "MODEL_INFERENCE_FAILED"


class LLMServiceError(AppException):
    """Raised when the Claude API call fails after retries."""
    status_code = 502
    error_code = "LLM_SERVICE_UNAVAILABLE"


class CacheServiceError(AppException):
    """Raised when Redis is unreachable. Should degrade gracefully, not crash."""
    status_code = 503
    error_code = "CACHE_UNAVAILABLE"


class TaskNotFoundError(AppException):
    status_code = 404
    error_code = "TASK_NOT_FOUND"


class TaskTimeoutError(AppException):
    status_code = 504
    error_code = "TASK_TIMEOUT"


class RateLimitExceededError(AppException):
    status_code = 429
    error_code = "RATE_LIMIT_EXCEEDED"


class AuthenticationError(AppException):
    status_code = 401
    error_code = "AUTHENTICATION_FAILED"
