"""Custom exceptions for state machine transitions.

This module provides custom exception classes for handling state machine
transition failures with user-friendly error messages and error codes.

Example usage:
    try:
        instance.transition_to_approved(user=user)
    except TransitionPermissionDenied as e:
        return Response({
            'error': e.user_message,
            'code': e.error_code
        }, status=403)
"""

from typing import Any

from django_fsm import TransitionNotAllowed


class TransitionPermissionDenied(TransitionNotAllowed):
    """
    Exception raised when a transition is not allowed due to permission issues.

    This exception provides additional context about why the transition failed,
    including a user-friendly message and error code for programmatic handling.

    Attributes:
        error_code: Machine-readable error code for programmatic handling
        user_message: Human-readable message to display to the user
        required_roles: List of roles that would have allowed the transition
        user_role: The user's current role
    """

    # Standard error codes
    ERROR_CODE_NO_USER = "NO_USER"
    ERROR_CODE_NOT_AUTHENTICATED = "NOT_AUTHENTICATED"
    ERROR_CODE_PERMISSION_DENIED_ROLE = "PERMISSION_DENIED_ROLE"
    ERROR_CODE_PERMISSION_DENIED_OWNERSHIP = "PERMISSION_DENIED_OWNERSHIP"
    ERROR_CODE_PERMISSION_DENIED_ASSIGNMENT = "PERMISSION_DENIED_ASSIGNMENT"
    ERROR_CODE_PERMISSION_DENIED_CUSTOM = "PERMISSION_DENIED_CUSTOM"

    def __init__(
        self,
        message: str = "Permission denied for this transition",
        error_code: str = "PERMISSION_DENIED",
        user_message: str | None = None,
        required_roles: list[str] | None = None,
        user_role: str | None = None,
        guard: Any | None = None,
    ):
        """
        Initialize permission denied exception.

        Args:
            message: Technical error message (for logging)
            error_code: Machine-readable error code
            user_message: Human-readable message for the user
            required_roles: List of roles that would have allowed the transition
            user_role: The user's current role
            guard: The guard that failed (for detailed error messages)
        """
        super().__init__(message)
        self.error_code = error_code
        self.user_message = user_message or message
        self.required_roles = required_roles or []
        self.user_role = user_role
        self.guard = guard

    def to_dict(self) -> dict[str, Any]:
        """
        Convert exception to dictionary for API responses.

        Returns:
            Dictionary with error details
        """
        return {
            "error": self.user_message,
            "error_code": self.error_code,
            "required_roles": self.required_roles,
            "user_role": self.user_role,
        }


class TransitionValidationError(TransitionNotAllowed):
    """
    Exception raised when a transition fails validation.

    This exception is raised when business logic conditions are not met,
    such as missing required fields or invalid state.

    Attributes:
        error_code: Machine-readable error code for programmatic handling
        user_message: Human-readable message to display to the user
        field_name: Name of the field that failed validation (if applicable)
        current_state: Current state of the object
    """

    # Standard error codes
    ERROR_CODE_INVALID_STATE = "INVALID_STATE_TRANSITION"
    ERROR_CODE_BLOCKED_STATE = "BLOCKED_STATE"
    ERROR_CODE_MISSING_FIELD = "MISSING_REQUIRED_FIELD"
    ERROR_CODE_EMPTY_FIELD = "EMPTY_REQUIRED_FIELD"
    ERROR_CODE_NO_ASSIGNMENT = "NO_ASSIGNMENT"
    ERROR_CODE_VALIDATION_FAILED = "VALIDATION_FAILED"

    def __init__(
        self,
        message: str = "Transition validation failed",
        error_code: str = "VALIDATION_FAILED",
        user_message: str | None = None,
        field_name: str | None = None,
        current_state: str | None = None,
        guard: Any | None = None,
    ):
        """
        Initialize validation error exception.

        Args:
            message: Technical error message (for logging)
            error_code: Machine-readable error code
            user_message: Human-readable message for the user
            field_name: Name of the field that failed validation
            current_state: Current state of the object
            guard: The guard that failed (for detailed error messages)
        """
        super().__init__(message)
        self.error_code = error_code
        self.user_message = user_message or message
        self.field_name = field_name
        self.current_state = current_state
        self.guard = guard

    def to_dict(self) -> dict[str, Any]:
        """
        Convert exception to dictionary for API responses.

        Returns:
            Dictionary with error details
        """
        result = {
            "error": self.user_message,
            "error_code": self.error_code,
        }
        if self.field_name:
            result["field"] = self.field_name
        if self.current_state:
            result["current_state"] = self.current_state
        return result


class TransitionNotAvailable(TransitionNotAllowed):
    """
    Exception raised when a transition is not available from the current state.

    This exception provides context about why the transition isn't available,
    including the current state and available transitions.

    Attributes:
        error_code: Machine-readable error code
        user_message: Human-readable message for the user
        current_state: Current state of the object
        requested_transition: The transition that was requested
        available_transitions: List of transitions that are available
    """

    ERROR_CODE_TRANSITION_NOT_AVAILABLE = "TRANSITION_NOT_AVAILABLE"

    def __init__(
        self,
        message: str = "This transition is not available",
        error_code: str = "TRANSITION_NOT_AVAILABLE",
        user_message: str | None = None,
        current_state: str | None = None,
        requested_transition: str | None = None,
        available_transitions: list[str] | None = None,
    ):
        """
        Initialize transition not available exception.

        Args:
            message: Technical error message (for logging)
            error_code: Machine-readable error code
            user_message: Human-readable message for the user
            current_state: Current state of the object
            requested_transition: Name of the requested transition
            available_transitions: List of available transition names
        """
        super().__init__(message)
        self.error_code = error_code
        self.user_message = user_message or message
        self.current_state = current_state
        self.requested_transition = requested_transition
        self.available_transitions = available_transitions or []

    def to_dict(self) -> dict[str, Any]:
        """
        Convert exception to dictionary for API responses.

        Returns:
            Dictionary with error details
        """
        return {
            "error": self.user_message,
            "error_code": self.error_code,
            "current_state": self.current_state,
            "requested_transition": self.requested_transition,
            "available_transitions": self.available_transitions,
        }


# Error message templates for common scenarios
ERROR_MESSAGES = {
    "PERMISSION_DENIED_ROLE": (
        "You need {required_role} permissions to {action}. "
        "Please contact an administrator if you believe this is an error."
    ),
    "PERMISSION_DENIED_OWNERSHIP": ("You must be the owner of this item to perform this action."),
    "PERMISSION_DENIED_ASSIGNMENT": (
        "This item must be assigned to you before you can {action}. " "Please assign it to yourself first."
    ),
    "NO_ASSIGNMENT": ("This item must be assigned before this action can be performed."),
    "INVALID_STATE_TRANSITION": (
        "This action cannot be performed from the current state. "
        "The item is currently '{current_state}' and cannot be modified."
    ),
    "TRANSITION_NOT_AVAILABLE": ("This {item_type} has already been {state} and cannot be modified."),
    "MISSING_REQUIRED_FIELD": ("{field_name} is required to complete this action."),
    "EMPTY_REQUIRED_FIELD": ("{field_name} must not be empty."),
    "ESCALATED_REQUIRES_ADMIN": (
        "This submission has been escalated and requires admin review. "
        "Only administrators can approve or reject escalated items."
    ),
}


def get_permission_error_message(
    guard: Any,
    action: str = "perform this action",
    **kwargs: Any,
) -> str:
    """
    Generate a user-friendly error message based on guard type.

    Args:
        guard: The guard that failed
        action: Description of the action being attempted
        **kwargs: Additional context for message formatting

    Returns:
        User-friendly error message

    Example:
        message = get_permission_error_message(
            guard,
            action="approve submissions"
        )
        # "You need moderator permissions to approve submissions..."
    """
    from .guards import (
        ADMIN_ROLES,
        MODERATOR_ROLES,
        SUPERUSER_ROLES,
        AssignmentGuard,
        OwnershipGuard,
        PermissionGuard,
    )

    if hasattr(guard, "get_error_message"):
        return guard.get_error_message()

    if isinstance(guard, PermissionGuard):
        required_roles = guard.get_required_roles()
        if required_roles == SUPERUSER_ROLES:
            required_role = "superuser"
        elif required_roles == ADMIN_ROLES:
            required_role = "admin"
        elif required_roles == MODERATOR_ROLES:
            required_role = "moderator"
        else:
            required_role = ", ".join(required_roles)

        return ERROR_MESSAGES["PERMISSION_DENIED_ROLE"].format(
            required_role=required_role,
            action=action,
        )

    if isinstance(guard, OwnershipGuard):
        return ERROR_MESSAGES["PERMISSION_DENIED_OWNERSHIP"]

    if isinstance(guard, AssignmentGuard):
        return ERROR_MESSAGES["PERMISSION_DENIED_ASSIGNMENT"].format(action=action)

    return f"You don't have permission to {action}"


def get_state_error_message(
    current_state: str,
    item_type: str = "item",
    **kwargs: Any,
) -> str:
    """
    Generate a user-friendly error message for state-related errors.

    Args:
        current_state: Current state of the object
        item_type: Type of item (e.g., "submission", "report")
        **kwargs: Additional context for message formatting

    Returns:
        User-friendly error message

    Example:
        message = get_state_error_message(
            current_state="COMPLETED",
            item_type="submission"
        )
        # "This submission has already been COMPLETED and cannot be modified."
    """
    # Map states to user-friendly descriptions
    state_descriptions = {
        "COMPLETED": "completed",
        "CANCELLED": "cancelled",
        "APPROVED": "approved",
        "REJECTED": "rejected",
        "RESOLVED": "resolved",
        "DISMISSED": "dismissed",
        "ESCALATED": "escalated for review",
    }

    state_desc = state_descriptions.get(current_state, current_state.lower())

    return ERROR_MESSAGES["TRANSITION_NOT_AVAILABLE"].format(
        item_type=item_type,
        state=state_desc,
    )


def format_transition_error(
    exception: Exception,
    include_details: bool = False,
) -> dict[str, Any]:
    """
    Format a transition exception for API response.

    Args:
        exception: The exception to format
        include_details: Include detailed information (for debugging)

    Returns:
        Dictionary suitable for API response

    Example:
        try:
            instance.transition_to_approved(user=user)
        except TransitionNotAllowed as e:
            return Response(
                format_transition_error(e),
                status=403
            )
    """
    # Handle our custom exceptions
    if hasattr(exception, "to_dict"):
        result = exception.to_dict()
        if not include_details:
            # Remove technical details
            result.pop("user_role", None)
        return result

    # Handle standard TransitionNotAllowed
    if isinstance(exception, TransitionNotAllowed):
        return {
            "error": str(exception) or "This transition is not allowed",
            "error_code": "TRANSITION_NOT_ALLOWED",
        }

    # Handle other exceptions
    return {
        "error": str(exception) or "An error occurred",
        "error_code": "UNKNOWN_ERROR",
    }


def raise_permission_denied(
    guard: Any,
    user: Any = None,
    action: str = "perform this action",
) -> None:
    """
    Raise a TransitionPermissionDenied exception with proper context.

    Args:
        guard: The guard that failed
        user: The user who attempted the transition
        action: Description of the action being attempted

    Raises:
        TransitionPermissionDenied: Always raised with proper context
    """
    from .guards import PermissionGuard, get_user_role

    user_message = get_permission_error_message(guard, action=action)
    user_role = get_user_role(user) if user else None

    error_code = TransitionPermissionDenied.ERROR_CODE_PERMISSION_DENIED_ROLE
    required_roles: list[str] = []

    if isinstance(guard, PermissionGuard):
        required_roles = guard.get_required_roles()
        if guard.error_code:
            error_code = guard.error_code

    raise TransitionPermissionDenied(
        message=f"Permission denied: {user_message}",
        error_code=error_code,
        user_message=user_message,
        required_roles=required_roles,
        user_role=user_role,
        guard=guard,
    )


def raise_validation_error(
    guard: Any,
    current_state: str | None = None,
    field_name: str | None = None,
) -> None:
    """
    Raise a TransitionValidationError exception with proper context.

    Args:
        guard: The guard that failed
        current_state: Current state of the object
        field_name: Name of the field that failed validation

    Raises:
        TransitionValidationError: Always raised with proper context
    """
    from .guards import MetadataGuard, StateGuard

    error_code = TransitionValidationError.ERROR_CODE_VALIDATION_FAILED
    user_message = "Validation failed for this transition"

    if hasattr(guard, "get_error_message"):
        user_message = guard.get_error_message()

    if hasattr(guard, "error_code") and guard.error_code:
        error_code = guard.error_code

    if isinstance(guard, StateGuard):
        if guard.error_code == "BLOCKED_STATE":
            error_code = TransitionValidationError.ERROR_CODE_BLOCKED_STATE
        else:
            error_code = TransitionValidationError.ERROR_CODE_INVALID_STATE
        current_state = guard._current_state

    if isinstance(guard, MetadataGuard):
        field_name = guard._failed_field
        if guard.error_code == "EMPTY_FIELD":
            error_code = TransitionValidationError.ERROR_CODE_EMPTY_FIELD
        else:
            error_code = TransitionValidationError.ERROR_CODE_MISSING_FIELD

    raise TransitionValidationError(
        message=f"Validation error: {user_message}",
        error_code=error_code,
        user_message=user_message,
        field_name=field_name,
        current_state=current_state,
        guard=guard,
    )


__all__ = [
    # Exception classes
    "TransitionPermissionDenied",
    "TransitionValidationError",
    "TransitionNotAvailable",
    # Error message templates
    "ERROR_MESSAGES",
    # Helper functions
    "get_permission_error_message",
    "get_state_error_message",
    "format_transition_error",
    "raise_permission_denied",
    "raise_validation_error",
]
