"""
Custom exceptions for the game engine.

Provides a hierarchy of specific exception types to replace broad `except Exception`
patterns, improving debugging and error handling precision.

Exception Hierarchy:
    GameEngineError (base)
    ├── GameNotFoundError - Game doesn't exist in state manager
    ├── InvalidGameStateError - Game in wrong state for operation
    ├── SubstitutionError - Invalid player substitution
    ├── AuthorizationError - User lacks permission
    ├── DecisionTimeoutError - Decision not submitted in time
    └── ExternalAPIError - External service call failed
        └── PlayerDataError - Failed to fetch player data

Author: Claude
Date: 2025-11-27
"""

from uuid import UUID


class GameEngineError(Exception):
    """
    Base exception for all game engine errors.

    All game-specific exceptions should inherit from this class
    to allow catching game errors separately from system errors.
    """

    pass


class GameNotFoundError(GameEngineError):
    """
    Raised when a game doesn't exist in state manager.

    Attributes:
        game_id: The UUID of the missing game
    """

    def __init__(self, game_id: UUID | str):
        self.game_id = game_id
        super().__init__(f"Game not found: {game_id}")


class InvalidGameStateError(GameEngineError):
    """
    Raised when game is in invalid state for requested operation.

    Examples:
        - Trying to submit decision when game is completed
        - Rolling dice before both decisions submitted
        - Starting an already-started game

    Attributes:
        message: Description of the state violation
        current_state: Optional current state value for debugging
        expected_state: Optional expected state value for debugging
    """

    def __init__(
        self,
        message: str,
        current_state: str | None = None,
        expected_state: str | None = None,
    ):
        self.current_state = current_state
        self.expected_state = expected_state
        super().__init__(message)


class SubstitutionError(GameEngineError):
    """
    Raised when a player substitution is invalid.

    Used for substitution rule violations, not found errors.

    Attributes:
        message: Description of the substitution error
        error_code: Machine-readable error code for frontend handling
    """

    def __init__(self, message: str, error_code: str = "SUBSTITUTION_ERROR"):
        self.error_code = error_code
        super().__init__(message)


class AuthorizationError(GameEngineError):
    """
    Raised when user lacks permission for an operation.

    Note: Currently deferred (task 001) but defined for future use.

    Attributes:
        message: Description of the authorization failure
        user_id: Optional user ID for logging
        resource: Optional resource being accessed
    """

    def __init__(
        self,
        message: str,
        user_id: int | None = None,
        resource: str | None = None,
    ):
        self.user_id = user_id
        self.resource = resource
        super().__init__(message)


class DecisionTimeoutError(GameEngineError):
    """
    Raised when a decision is not submitted within the timeout period.

    The game engine uses default decisions when this occurs,
    so this exception is informational rather than fatal.

    Attributes:
        game_id: Game where timeout occurred
        decision_type: Type of decision that timed out ('defensive' or 'offensive')
        timeout_seconds: How long we waited
    """

    def __init__(
        self,
        game_id: UUID | str,
        decision_type: str,
        timeout_seconds: int,
    ):
        self.game_id = game_id
        self.decision_type = decision_type
        self.timeout_seconds = timeout_seconds
        super().__init__(
            f"Decision timeout for game {game_id}: "
            f"{decision_type} decision not received within {timeout_seconds}s"
        )


class ExternalAPIError(GameEngineError):
    """
    Raised when an external API call fails.

    Base class for external service errors.

    Attributes:
        service: Name of the external service
        message: Error description
        status_code: Optional HTTP status code
    """

    def __init__(
        self,
        service: str,
        message: str,
        status_code: int | None = None,
    ):
        self.service = service
        self.status_code = status_code
        super().__init__(f"{service} API error: {message}")


class PlayerDataError(ExternalAPIError):
    """
    Raised when player data cannot be fetched from external API.

    Specialized error for player data lookups (SBA API, PD API).

    Attributes:
        player_id: ID of the player that couldn't be fetched
        service: Which API was called
    """

    def __init__(self, player_id: int, service: str = "SBA API"):
        self.player_id = player_id
        super().__init__(
            service=service,
            message=f"Failed to fetch player data for ID {player_id}",
        )


class DatabaseError(GameEngineError):
    """
    Raised when a database operation fails.

    Wraps SQLAlchemy exceptions with game context.

    Attributes:
        operation: What operation was attempted (e.g., 'save_play', 'create_substitution')
        original_error: The underlying database exception
    """

    def __init__(self, operation: str, original_error: Exception | None = None):
        self.operation = operation
        self.original_error = original_error
        message = f"Database error during {operation}"
        if original_error:
            message += f": {original_error}"
        super().__init__(message)


class LineupError(GameEngineError):
    """
    Raised when lineup validation or lookup fails.

    Attributes:
        team_id: Team with the lineup issue
        message: Description of the error
    """

    def __init__(self, team_id: int, message: str):
        self.team_id = team_id
        super().__init__(f"Lineup error for team {team_id}: {message}")
