"""
errors.py
---------
Structured error codes and exception classes for HIEROMEM API.

Provides consistent error responses across all endpoints.

Author: Sai Rishi Kiran Mannava
"""

from enum import Enum
from typing import Any, Optional
from fastapi import HTTPException, status


class ErrorCode(str, Enum):
    """Standard error codes for HIEROMEM API."""

    # Client Errors (4xx)
    INVALID_REQUEST = "INVALID_REQUEST"
    MISSING_FIELD = "MISSING_FIELD"
    INVALID_FIELD = "INVALID_FIELD"
    UNAUTHORIZED = "UNAUTHORIZED"
    FORBIDDEN = "FORBIDDEN"
    NOT_FOUND = "NOT_FOUND"
    NAMESPACE_NOT_FOUND = "NAMESPACE_NOT_FOUND"
    EPISODE_NOT_FOUND = "EPISODE_NOT_FOUND"
    CONFLICT = "CONFLICT"
    RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
    QUOTA_EXCEEDED = "QUOTA_EXCEEDED"

    # Server Errors (5xx)
    INTERNAL_ERROR = "INTERNAL_ERROR"
    SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"
    STORAGE_ERROR = "STORAGE_ERROR"
    ENCODING_ERROR = "ENCODING_ERROR"
    GRAPH_ERROR = "GRAPH_ERROR"


class HieromemException(HTTPException):
    """Base exception for HIEROMEM API errors."""

    def __init__(
        self,
        status_code: int,
        error_code: ErrorCode,
        message: str,
        details: Optional[dict[str, Any]] = None,
    ):
        self.error_code = error_code
        self.details = details or {}

        super().__init__(
            status_code=status_code,
            detail={
                "error": message,
                "code": error_code.value,
                "details": self.details,
            }
        )


# Client Error Exceptions (4xx)

class InvalidRequestError(HieromemException):
    """Raised when request is malformed or invalid."""

    def __init__(self, message: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_400_BAD_REQUEST,
            error_code=ErrorCode.INVALID_REQUEST,
            message=message,
            details=details,
        )


class MissingFieldError(HieromemException):
    """Raised when required field is missing."""

    def __init__(self, field: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_400_BAD_REQUEST,
            error_code=ErrorCode.MISSING_FIELD,
            message=f"Missing required field: {field}",
            details={"field": field, **(details or {})},
        )


class InvalidFieldError(HieromemException):
    """Raised when field value is invalid."""

    def __init__(self, field: str, reason: str,
                 details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_400_BAD_REQUEST,
            error_code=ErrorCode.INVALID_FIELD,
            message=f"Invalid field '{field}': {reason}",
            details={"field": field, "reason": reason, **(details or {})},
        )


class UnauthorizedError(HieromemException):
    """Raised when authentication fails."""

    def __init__(
            self,
            message: str = "Invalid or missing API key",
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_401_UNAUTHORIZED,
            error_code=ErrorCode.UNAUTHORIZED,
            message=message,
            details=details,
        )


class ForbiddenError(HieromemException):
    """Raised when user lacks permission."""

    def __init__(
            self,
            message: str = "Access forbidden",
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_403_FORBIDDEN,
            error_code=ErrorCode.FORBIDDEN,
            message=message,
            details=details,
        )


class NotFoundError(HieromemException):
    """Raised when resource is not found."""

    def __init__(
            self,
            resource: str,
            resource_id: str,
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_404_NOT_FOUND,
            error_code=ErrorCode.NOT_FOUND,
            message=f"{resource} not found: {resource_id}",
            details={"resource": resource, "id": resource_id, **(details or {})},
        )


class NamespaceNotFoundError(HieromemException):
    """Raised when namespace does not exist."""

    def __init__(self, namespace: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_404_NOT_FOUND,
            error_code=ErrorCode.NAMESPACE_NOT_FOUND,
            message=f"Namespace not found: {namespace}",
            details={"namespace": namespace, **(details or {})},
        )


class EpisodeNotFoundError(HieromemException):
    """Raised when episode does not exist."""

    def __init__(self, episode_id: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_404_NOT_FOUND,
            error_code=ErrorCode.EPISODE_NOT_FOUND,
            message=f"Episode not found: {episode_id}",
            details={"episode_id": episode_id, **(details or {})},
        )


class ConflictError(HieromemException):
    """Raised when resource already exists."""

    def __init__(self, message: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_409_CONFLICT,
            error_code=ErrorCode.CONFLICT,
            message=message,
            details=details,
        )


class RateLimitExceededError(HieromemException):
    """Raised when rate limit is exceeded."""

    def __init__(self, retry_after: int = 60, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            error_code=ErrorCode.RATE_LIMIT_EXCEEDED,
            message="Rate limit exceeded",
            details={"retry_after": retry_after, **(details or {})},
        )


class QuotaExceededError(HieromemException):
    """Raised when quota is exceeded."""

    def __init__(
            self,
            quota_type: str,
            limit: int,
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            error_code=ErrorCode.QUOTA_EXCEEDED,
            message=f"{quota_type} quota exceeded (limit: {limit})",
            details={"quota_type": quota_type, "limit": limit, **(details or {})},
        )


# Server Error Exceptions (5xx)

class InternalError(HieromemException):
    """Raised when internal server error occurs."""

    def __init__(
            self,
            message: str = "Internal server error",
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error_code=ErrorCode.INTERNAL_ERROR,
            message=message,
            details=details,
        )


class ServiceUnavailableError(HieromemException):
    """Raised when service is temporarily unavailable."""

    def __init__(
            self,
            message: str = "Service temporarily unavailable",
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            error_code=ErrorCode.SERVICE_UNAVAILABLE,
            message=message,
            details=details,
        )


class StorageError(HieromemException):
    """Raised when storage backend fails."""

    def __init__(self, backend: str, reason: str,
                 details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error_code=ErrorCode.STORAGE_ERROR,
            message=f"Storage error ({backend}): {reason}",
            details={"backend": backend, "reason": reason, **(details or {})},
        )


class EncodingError(HieromemException):
    """Raised when encoding fails."""

    def __init__(self, reason: str, details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error_code=ErrorCode.ENCODING_ERROR,
            message=f"Encoding error: {reason}",
            details={"reason": reason, **(details or {})},
        )


class GraphError(HieromemException):
    """Raised when graph operation fails."""

    def __init__(
            self,
            operation: str,
            reason: str,
            details: Optional[dict] = None):
        super().__init__(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            error_code=ErrorCode.GRAPH_ERROR,
            message=f"Graph error ({operation}): {reason}",
            details={"operation": operation, "reason": reason, **(details or {})},
        )
