"""Guard and condition extractors for permission-based transitions.

This module provides a comprehensive guard system for FSM state transitions,
including role-based permission checks, ownership verification, and business
logic conditions.

Example usage:
    # Create a permission guard requiring moderator role
    guard = PermissionGuard(requires_moderator=True)

    # Check if transition is allowed
    if guard(instance, user):
        instance.transition_to_approved(user=user)

    # Create a composite guard with ownership check
    composite = CompositeGuard([
        PermissionGuard(requires_moderator=True),
        OwnershipGuard()
    ], operator='OR')
"""

from collections.abc import Callable
from typing import Any, Optional

# Valid user roles in order of increasing privilege
VALID_ROLES = ["USER", "MODERATOR", "ADMIN", "SUPERUSER"]
MODERATOR_ROLES = ["MODERATOR", "ADMIN", "SUPERUSER"]
ADMIN_ROLES = ["ADMIN", "SUPERUSER"]
SUPERUSER_ROLES = ["SUPERUSER"]


class PermissionGuard:
    """
    Guard for checking permissions on state transitions.

    This guard checks the user's role field (USER, MODERATOR, ADMIN, SUPERUSER)
    as the primary authorization mechanism, with fallbacks to Django's built-in
    permission system for backward compatibility.

    Example:
        # Require moderator role
        guard = PermissionGuard(requires_moderator=True)

        # Require admin role
        guard = PermissionGuard(requires_admin=True)

        # Require superuser role
        guard = PermissionGuard(requires_superuser=True)

        # Custom check with role requirement
        guard = PermissionGuard(
            requires_moderator=True,
            custom_check=lambda instance, user: instance.priority != 'URGENT'
        )
    """

    # Error codes for programmatic handling
    ERROR_CODE_NO_USER = "NO_USER"
    ERROR_CODE_PERMISSION_DENIED_ROLE = "PERMISSION_DENIED_ROLE"
    ERROR_CODE_PERMISSION_DENIED_CUSTOM = "PERMISSION_DENIED_CUSTOM"

    def __init__(
        self,
        requires_moderator: bool = False,
        requires_admin: bool = False,
        requires_superuser: bool = False,
        required_roles: list[str] | None = None,
        custom_check: Callable | None = None,
        error_message: str | None = None,
    ):
        """
        Initialize permission guard.

        Args:
            requires_moderator: Whether moderator or above role is required
            requires_admin: Whether admin or above role is required
            requires_superuser: Whether superuser role is required
            required_roles: Explicit list of allowed roles (overrides other role flags)
            custom_check: Optional custom permission check function(instance, user) -> bool
            error_message: Custom error message to return on failure
        """
        self.requires_moderator = requires_moderator
        self.requires_admin = requires_admin
        self.requires_superuser = requires_superuser
        self.required_roles = required_roles
        self.custom_check = custom_check
        self._custom_error_message = error_message
        self._last_error_code: str | None = None

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if transition is allowed.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition

        Returns:
            True if transition is allowed
        """
        self._last_error_code = None

        if user is None:
            self._last_error_code = self.ERROR_CODE_NO_USER
            return False

        # If explicit roles are provided, check against them
        if self.required_roles:
            if not has_role(user, self.required_roles):
                self._last_error_code = self.ERROR_CODE_PERMISSION_DENIED_ROLE
                return False
        else:
            # Check superuser first (most restrictive)
            if self.requires_superuser:
                if not is_superuser_role(user):
                    self._last_error_code = self.ERROR_CODE_PERMISSION_DENIED_ROLE
                    return False

            # Check admin (includes superuser)
            elif self.requires_admin:
                if not is_admin_or_above(user):
                    self._last_error_code = self.ERROR_CODE_PERMISSION_DENIED_ROLE
                    return False

            # Check moderator (includes admin and superuser)
            elif self.requires_moderator and not is_moderator_or_above(user):
                self._last_error_code = self.ERROR_CODE_PERMISSION_DENIED_ROLE
                return False

        # Apply custom check if provided
        if self.custom_check and not self.custom_check(instance, user):
            self._last_error_code = self.ERROR_CODE_PERMISSION_DENIED_CUSTOM
            return False

        return True

    def get_error_message(self) -> str:
        """
        Return user-friendly error message.

        Returns:
            Error message describing permission requirement
        """
        if self._custom_error_message:
            return self._custom_error_message

        if self.required_roles:
            roles_display = ", ".join(self.required_roles)
            return f"This transition requires one of these roles: {roles_display}"
        elif self.requires_superuser:
            return "This transition requires superuser permissions"
        elif self.requires_admin:
            return "This transition requires admin or superuser permissions"
        elif self.requires_moderator:
            return "This transition requires moderator, admin, or superuser permissions"
        elif self.custom_check:
            return "This transition requires special permissions"
        return "This transition is not allowed"

    def get_required_roles(self) -> list[str]:
        """
        Return list of roles that would satisfy this guard.

        Returns:
            List of role strings that are allowed
        """
        if self.required_roles:
            return self.required_roles
        elif self.requires_superuser:
            return SUPERUSER_ROLES.copy()
        elif self.requires_admin:
            return ADMIN_ROLES.copy()
        elif self.requires_moderator:
            return MODERATOR_ROLES.copy()
        return VALID_ROLES.copy()


class OwnershipGuard:
    """
    Guard that checks if the user is the owner of the object.

    This guard checks common ownership fields like 'created_by', 'user',
    'submitted_by', 'reported_by' to determine if the user owns the object.

    Example:
        # Basic ownership check
        guard = OwnershipGuard()

        # Custom owner field
        guard = OwnershipGuard(owner_fields=['author', 'owner'])

        # Allow moderators to bypass ownership check
        guard = OwnershipGuard(allow_moderator_override=True)
    """

    # Default fields to check for ownership
    DEFAULT_OWNER_FIELDS = ["created_by", "user", "submitted_by", "reported_by", "uploaded_by"]

    # Error codes
    ERROR_CODE_NO_USER = "NO_USER"
    ERROR_CODE_NOT_OWNER = "NOT_OWNER"

    def __init__(
        self,
        owner_fields: list[str] | None = None,
        allow_moderator_override: bool = False,
        allow_admin_override: bool = False,
        error_message: str | None = None,
    ):
        """
        Initialize ownership guard.

        Args:
            owner_fields: List of field names to check for ownership
            allow_moderator_override: Allow moderators to bypass ownership check
            allow_admin_override: Allow admins to bypass ownership check
            error_message: Custom error message on failure
        """
        self.owner_fields = owner_fields or self.DEFAULT_OWNER_FIELDS.copy()
        self.allow_moderator_override = allow_moderator_override
        self.allow_admin_override = allow_admin_override
        self._custom_error_message = error_message
        self._last_error_code: str | None = None

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if user is the owner of the instance.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition

        Returns:
            True if user is the owner or has override permissions
        """
        self._last_error_code = None

        if user is None:
            self._last_error_code = self.ERROR_CODE_NO_USER
            return False

        # Check for moderator/admin override
        if self.allow_admin_override and is_admin_or_above(user):
            return True

        if self.allow_moderator_override and is_moderator_or_above(user):
            return True

        # Check ownership fields
        for field_name in self.owner_fields:
            owner = getattr(instance, field_name, None)
            if owner is not None:
                # Handle both direct user comparison and ID comparison
                if hasattr(owner, "pk"):
                    if owner.pk == user.pk:
                        return True
                elif owner == user:
                    return True

        self._last_error_code = self.ERROR_CODE_NOT_OWNER
        return False

    def get_error_message(self) -> str:
        """Return user-friendly error message."""
        if self._custom_error_message:
            return self._custom_error_message
        return "You must be the owner of this item to perform this action"


class AssignmentGuard:
    """
    Guard that checks if the user is assigned to the object.

    This guard checks common assignment fields like 'assigned_to', 'assigned_moderator',
    'handled_by' to determine if the user is assigned to handle the object.

    Example:
        # Basic assignment check
        guard = AssignmentGuard()

        # Require assignment before transition
        guard = AssignmentGuard(require_assignment=True)

        # Allow admins to bypass assignment check
        guard = AssignmentGuard(allow_admin_override=True)
    """

    # Default fields to check for assignment
    DEFAULT_ASSIGNMENT_FIELDS = ["assigned_to", "assigned_moderator", "handled_by"]

    # Error codes
    ERROR_CODE_NO_USER = "NO_USER"
    ERROR_CODE_NOT_ASSIGNED = "NOT_ASSIGNED"
    ERROR_CODE_NO_ASSIGNMENT = "NO_ASSIGNMENT"

    def __init__(
        self,
        assignment_fields: list[str] | None = None,
        require_assignment: bool = False,
        allow_admin_override: bool = False,
        error_message: str | None = None,
    ):
        """
        Initialize assignment guard.

        Args:
            assignment_fields: List of field names to check for assignment
            require_assignment: Require that someone is assigned before allowing transition
            allow_admin_override: Allow admins to bypass assignment check
            error_message: Custom error message on failure
        """
        self.assignment_fields = assignment_fields or self.DEFAULT_ASSIGNMENT_FIELDS.copy()
        self.require_assignment = require_assignment
        self.allow_admin_override = allow_admin_override
        self._custom_error_message = error_message
        self._last_error_code: str | None = None

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if user is assigned to the instance.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition

        Returns:
            True if user is assigned or has override permissions
        """
        self._last_error_code = None

        if user is None:
            self._last_error_code = self.ERROR_CODE_NO_USER
            return False

        # Check for admin override
        if self.allow_admin_override and is_admin_or_above(user):
            return True

        # Check assignment fields
        for field_name in self.assignment_fields:
            assigned_user = getattr(instance, field_name, None)
            if assigned_user is not None:
                # Handle both direct user comparison and ID comparison
                if hasattr(assigned_user, "pk"):
                    if assigned_user.pk == user.pk:
                        return True
                elif assigned_user == user:
                    return True
            elif self.require_assignment:
                # Field exists but is None - no assignment
                self._last_error_code = self.ERROR_CODE_NO_ASSIGNMENT
                return False

        self._last_error_code = self.ERROR_CODE_NOT_ASSIGNED
        return False

    def get_error_message(self) -> str:
        """Return user-friendly error message."""
        if self._custom_error_message:
            return self._custom_error_message
        if self._last_error_code == self.ERROR_CODE_NO_ASSIGNMENT:
            return "This item must be assigned before this action can be performed"
        return "You must be assigned to this item to perform this action"


class StateGuard:
    """
    Guard that checks if the object is in specific states.

    This guard validates that the object is in one of the allowed states
    before permitting a transition.

    Example:
        # Only allow transition from PENDING state
        guard = StateGuard(allowed_states=['PENDING'])

        # Block transition from final states
        guard = StateGuard(blocked_states=['COMPLETED', 'CANCELLED'])
    """

    # Error codes
    ERROR_CODE_INVALID_STATE = "INVALID_STATE"
    ERROR_CODE_BLOCKED_STATE = "BLOCKED_STATE"

    def __init__(
        self,
        allowed_states: list[str] | None = None,
        blocked_states: list[str] | None = None,
        state_field: str = "status",
        error_message: str | None = None,
    ):
        """
        Initialize state guard.

        Args:
            allowed_states: List of states that allow the transition
            blocked_states: List of states that block the transition
            state_field: Name of the field containing the current state
            error_message: Custom error message on failure
        """
        self.allowed_states = allowed_states
        self.blocked_states = blocked_states or []
        self.state_field = state_field
        self._custom_error_message = error_message
        self._last_error_code: str | None = None
        self._current_state: str | None = None

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if instance is in a valid state for the transition.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition (not used but required for interface)

        Returns:
            True if instance is in a valid state
        """
        self._last_error_code = None
        self._current_state = getattr(instance, self.state_field, None)

        # Check blocked states first
        if self._current_state in self.blocked_states:
            self._last_error_code = self.ERROR_CODE_BLOCKED_STATE
            return False

        # Check allowed states if specified
        if self.allowed_states is not None and self._current_state not in self.allowed_states:
            self._last_error_code = self.ERROR_CODE_INVALID_STATE
            return False

        return True

    def get_error_message(self) -> str:
        """Return user-friendly error message."""
        if self._custom_error_message:
            return self._custom_error_message
        if self._last_error_code == self.ERROR_CODE_BLOCKED_STATE:
            return f"This action cannot be performed from the '{self._current_state}' state"
        if self.allowed_states:
            states_display = ", ".join(self.allowed_states)
            return f"This action is only available from these states: {states_display}"
        return f"This action is not available from the current state: {self._current_state}"


class MetadataGuard:
    """
    Guard that validates metadata requirements.

    This guard checks for required metadata conditions before allowing
    transitions, such as requiring assignment, resolution notes, etc.

    Example:
        # Require resolution notes to be filled
        guard = MetadataGuard(required_fields=['resolution_notes'])

        # Require non-empty value
        guard = MetadataGuard(required_fields=['assigned_to'], check_not_empty=True)
    """

    # Error codes
    ERROR_CODE_MISSING_FIELD = "MISSING_FIELD"
    ERROR_CODE_EMPTY_FIELD = "EMPTY_FIELD"

    def __init__(
        self,
        required_fields: list[str] | None = None,
        check_not_empty: bool = True,
        error_message: str | None = None,
    ):
        """
        Initialize metadata guard.

        Args:
            required_fields: List of field names that must exist and optionally be non-empty
            check_not_empty: If True, require fields to have non-empty values
            error_message: Custom error message on failure
        """
        self.required_fields = required_fields or []
        self.check_not_empty = check_not_empty
        self._custom_error_message = error_message
        self._last_error_code: str | None = None
        self._failed_field: str | None = None

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if instance has required metadata fields.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition (not used but required for interface)

        Returns:
            True if all required fields are present and valid
        """
        self._last_error_code = None
        self._failed_field = None

        for field_name in self.required_fields:
            value = getattr(instance, field_name, None)

            if value is None:
                self._last_error_code = self.ERROR_CODE_MISSING_FIELD
                self._failed_field = field_name
                return False

            if self.check_not_empty:
                # Check for empty strings, empty lists, etc.
                if isinstance(value, str) and not value.strip():
                    self._last_error_code = self.ERROR_CODE_EMPTY_FIELD
                    self._failed_field = field_name
                    return False
                if isinstance(value, list | dict) and not value:
                    self._last_error_code = self.ERROR_CODE_EMPTY_FIELD
                    self._failed_field = field_name
                    return False

        return True

    def get_error_message(self) -> str:
        """Return user-friendly error message."""
        if self._custom_error_message:
            return self._custom_error_message
        if self._failed_field:
            field_display = self._failed_field.replace("_", " ").title()
            if self._last_error_code == self.ERROR_CODE_EMPTY_FIELD:
                return f"{field_display} must not be empty"
            return f"{field_display} is required for this action"
        return "Required information is missing"


class CompositeGuard:
    """
    Guard that combines multiple guards with AND/OR logic.

    This allows creating complex guard conditions by combining
    simpler guards.

    Example:
        # Require moderator AND ownership (both must pass)
        guard = CompositeGuard([
            PermissionGuard(requires_moderator=True),
            OwnershipGuard()
        ], operator='AND')

        # Require moderator OR ownership (either can pass)
        guard = CompositeGuard([
            PermissionGuard(requires_moderator=True),
            OwnershipGuard()
        ], operator='OR')
    """

    # Error codes
    ERROR_CODE_ALL_FAILED = "ALL_GUARDS_FAILED"
    ERROR_CODE_SOME_FAILED = "SOME_GUARDS_FAILED"

    def __init__(
        self,
        guards: list[Callable],
        operator: str = "AND",
        error_message: str | None = None,
    ):
        """
        Initialize composite guard.

        Args:
            guards: List of guard functions to combine
            operator: 'AND' requires all guards to pass, 'OR' requires at least one
            error_message: Custom error message on failure
        """
        self.guards = guards
        self.operator = operator.upper()
        self._custom_error_message = error_message
        self._last_error_code: str | None = None
        self._failed_guards: list[Callable] = []

    @property
    def error_code(self) -> str | None:
        """Return the error code from the last failed check."""
        return self._last_error_code

    def __call__(self, instance, user=None) -> bool:
        """
        Check if guards pass according to the operator.

        Args:
            instance: Model instance being transitioned
            user: User attempting the transition

        Returns:
            True if guards pass (all for AND, any for OR)
        """
        self._last_error_code = None
        self._failed_guards = []

        if self.operator == "AND":
            for guard in self.guards:
                if not guard(instance, user):
                    self._failed_guards.append(guard)
                    self._last_error_code = self.ERROR_CODE_SOME_FAILED
                    return False
            return True

        elif self.operator == "OR":
            for guard in self.guards:
                if guard(instance, user):
                    return True
                self._failed_guards.append(guard)
            self._last_error_code = self.ERROR_CODE_ALL_FAILED
            return False

        # Unknown operator, fail safe
        return False

    def get_error_message(self) -> str:
        """Return user-friendly error message."""
        if self._custom_error_message:
            return self._custom_error_message

        if self._failed_guards:
            # Try to get error message from first failed guard
            first_failed = self._failed_guards[0]
            if hasattr(first_failed, "get_error_message"):
                return first_failed.get_error_message()

        if self.operator == "OR":
            return "None of the required conditions were met"
        return "One or more required conditions were not met"


# Factory functions for creating guards


def create_ownership_guard(
    owner_fields: list[str] | None = None,
    allow_moderator_override: bool = False,
    allow_admin_override: bool = False,
) -> OwnershipGuard:
    """
    Create an ownership guard with configuration.

    Args:
        owner_fields: Fields to check for ownership
        allow_moderator_override: Allow moderators to bypass
        allow_admin_override: Allow admins to bypass

    Returns:
        Configured OwnershipGuard
    """
    return OwnershipGuard(
        owner_fields=owner_fields,
        allow_moderator_override=allow_moderator_override,
        allow_admin_override=allow_admin_override,
    )


def create_assignment_guard(
    assignment_fields: list[str] | None = None,
    require_assignment: bool = False,
    allow_admin_override: bool = False,
) -> AssignmentGuard:
    """
    Create an assignment guard with configuration.

    Args:
        assignment_fields: Fields to check for assignment
        require_assignment: Require assignment before transition
        allow_admin_override: Allow admins to bypass

    Returns:
        Configured AssignmentGuard
    """
    return AssignmentGuard(
        assignment_fields=assignment_fields,
        require_assignment=require_assignment,
        allow_admin_override=allow_admin_override,
    )


def create_composite_guard(
    guards: list[Callable],
    operator: str = "AND",
) -> CompositeGuard:
    """
    Create a composite guard combining multiple guards.

    Args:
        guards: List of guard functions
        operator: 'AND' or 'OR'

    Returns:
        Configured CompositeGuard
    """
    return CompositeGuard(guards=guards, operator=operator)


# Valid escalation levels and their role mappings
ESCALATION_LEVEL_ROLES = {
    "moderator": MODERATOR_ROLES,
    "admin": ADMIN_ROLES,
    "superuser": SUPERUSER_ROLES,
}


def extract_guards_from_metadata(metadata: dict[str, Any]) -> list[Callable]:
    """
    Convert RichChoice metadata to guard functions.

    This function extracts permission requirements, assignment requirements,
    and other business logic conditions from state metadata and creates
    appropriate guard functions.

    Supported metadata fields:
        - requires_moderator: Require moderator or above role
        - requires_admin_approval: Require admin or above role
        - escalation_level: 'moderator', 'admin', or 'superuser'
        - requires_assignment: Require item to be assigned to user
        - zero_tolerance: Apply stricter checks (superuser only)
        - requires_immediate_action: High priority flag
        - required_permissions: List of Django permissions required

    Args:
        metadata: State metadata dictionary from RichChoice

    Returns:
        List of guard functions

    Example:
        # Metadata with moderator requirement
        metadata = {'requires_moderator': True}
        guards = extract_guards_from_metadata(metadata)
        # Returns [PermissionGuard(requires_moderator=True)]

        # Metadata with assignment requirement
        metadata = {'requires_assignment': True, 'requires_moderator': True}
        guards = extract_guards_from_metadata(metadata)
        # Returns [PermissionGuard(...), AssignmentGuard(...)]
    """
    guards = []

    # Extract permission requirements
    requires_moderator = metadata.get("requires_moderator", False)
    requires_admin = metadata.get("requires_admin_approval", False)
    escalation_level = metadata.get("escalation_level")
    zero_tolerance = metadata.get("zero_tolerance", False)

    # Determine role requirement based on escalation level
    if escalation_level:
        escalation_level = escalation_level.lower()
        if escalation_level not in ESCALATION_LEVEL_ROLES:
            raise ValueError(
                f"Invalid escalation_level: {escalation_level}. "
                f"Must be one of: {', '.join(ESCALATION_LEVEL_ROLES.keys())}"
            )
        # Escalation level overrides other role requirements
        if escalation_level == "superuser":
            requires_admin = False
            requires_moderator = False
        elif escalation_level == "admin":
            requires_admin = True
            requires_moderator = False

    # Zero tolerance requires superuser
    if zero_tolerance:
        guard = PermissionGuard(
            requires_superuser=True, error_message="Zero tolerance violations require superuser permissions"
        )
        guards.append(guard)
    elif requires_moderator or requires_admin or escalation_level:
        guard = create_permission_guard(metadata)
        guards.append(guard)

    # Extract assignment requirements
    requires_assignment = metadata.get("requires_assignment", False)
    if requires_assignment:
        assignment_guard = AssignmentGuard(
            require_assignment=True,
            allow_admin_override=True,
            error_message="This item must be assigned to you before this action can be performed",
        )
        guards.append(assignment_guard)

    # Extract required permissions (Django permission strings)
    required_permissions = metadata.get("required_permissions", [])
    if required_permissions:
        # Create a custom check for each permission
        def check_permissions(instance, user, perms=required_permissions):
            return all(has_permission(user, perm) for perm in perms)

        perm_guard = PermissionGuard(
            custom_check=check_permissions,
            error_message=f"Missing required permissions: {', '.join(required_permissions)}",
        )
        guards.append(perm_guard)

    return guards


def create_permission_guard(metadata: dict[str, Any]) -> PermissionGuard:
    """
    Create a permission guard from RichChoice metadata.

    This function creates a configured PermissionGuard based on the
    permission requirements specified in the metadata.

    Supported metadata fields:
        - requires_moderator: Require moderator or above role
        - requires_admin_approval: Require admin or above role
        - escalation_level: 'moderator', 'admin', or 'superuser'

    Args:
        metadata: State metadata with permission requirements

    Returns:
        Configured PermissionGuard

    Example:
        # Create guard for moderator requirement
        guard = create_permission_guard({'requires_moderator': True})

        # Create guard for admin escalation
        guard = create_permission_guard({'escalation_level': 'admin'})
    """
    requires_moderator = metadata.get("requires_moderator", False)
    requires_admin = metadata.get("requires_admin_approval", False)
    requires_superuser = False

    # Handle escalation_level
    escalation_level = metadata.get("escalation_level")
    if escalation_level:
        escalation_level = escalation_level.lower()
        if escalation_level == "superuser":
            requires_superuser = True
            requires_admin = False
            requires_moderator = False
        elif escalation_level == "admin":
            requires_admin = True
            requires_moderator = False
        elif escalation_level == "moderator":
            requires_moderator = True
            requires_admin = False

    return PermissionGuard(
        requires_moderator=requires_moderator,
        requires_admin=requires_admin,
        requires_superuser=requires_superuser,
    )


def validate_guard_metadata(metadata: dict[str, Any]) -> tuple[bool, list[str]]:
    """
    Validate that metadata contains valid guard configuration.

    Args:
        metadata: State metadata dictionary

    Returns:
        Tuple of (is_valid, list of error messages)

    Example:
        is_valid, errors = validate_guard_metadata({'escalation_level': 'invalid'})
        # Returns (False, ['Invalid escalation_level: invalid...'])
    """
    errors = []

    # Validate escalation_level
    escalation_level = metadata.get("escalation_level")
    if escalation_level and escalation_level.lower() not in ESCALATION_LEVEL_ROLES:
        errors.append(
            f"Invalid escalation_level: {escalation_level}. "
            f"Must be one of: {', '.join(ESCALATION_LEVEL_ROLES.keys())}"
        )

    # Validate required_permissions is a list
    required_permissions = metadata.get("required_permissions")
    if required_permissions is not None:
        if not isinstance(required_permissions, list):
            errors.append("required_permissions must be a list")
        elif not all(isinstance(p, str) for p in required_permissions):
            errors.append("All items in required_permissions must be strings")

    # Validate boolean fields
    boolean_fields = [
        "requires_moderator",
        "requires_admin_approval",
        "requires_assignment",
        "zero_tolerance",
        "requires_immediate_action",
    ]
    for field in boolean_fields:
        value = metadata.get(field)
        if value is not None and not isinstance(value, bool):
            errors.append(f"{field} must be a boolean, got {type(value).__name__}")

    return len(errors) == 0, errors


class GuardRegistry:
    """Registry for storing and retrieving guard functions."""

    _instance: Optional["GuardRegistry"] = None
    _guards: dict[str, Callable]

    def __new__(cls):
        """Implement singleton pattern."""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._guards = {}
        return cls._instance

    def register_guard(self, name: str, guard: Callable) -> None:
        """
        Register a custom guard.

        Args:
            name: Name for the guard
            guard: Guard function
        """
        self._guards[name] = guard

    def get_guard(self, name: str) -> Callable | None:
        """
        Retrieve a guard by name.

        Args:
            name: Name of the guard

        Returns:
            Guard function or None
        """
        return self._guards.get(name)

    def apply_guards(
        self,
        instance: Any,
        guards: list[Callable],
        user: Any | None = None,
    ) -> tuple[bool, str | None]:
        """
        Apply multiple guards.

        Args:
            instance: Model instance
            guards: List of guard functions
            user: User attempting transition

        Returns:
            Tuple of (allowed, error_message)
        """
        for guard in guards:
            try:
                if not guard(instance, user):
                    error_msg = getattr(guard, "get_error_message", None)
                    if callable(error_msg):
                        return False, error_msg()
                    return False, "Transition not allowed"
            except Exception as e:
                return False, f"Guard check failed: {str(e)}"

        return True, None

    def clear_guards(self) -> None:
        """Clear all registered guards (for testing)."""
        self._guards.clear()


def create_condition_from_metadata(
    metadata: dict[str, Any],
) -> Callable | None:
    """
    Create FSM condition from metadata.

    Args:
        metadata: State metadata

    Returns:
        Condition function or None
    """
    guards = extract_guards_from_metadata(metadata)

    if not guards:
        return None

    def combined_condition(instance, user=None):
        """Combined condition from all guards."""
        return all(guard(instance, user) for guard in guards)

    return combined_condition


# Helper functions for permission checks


def get_user_role(user: Any) -> str | None:
    """
    Get the user's role from the role field.

    Args:
        user: User object

    Returns:
        Role string (USER, MODERATOR, ADMIN, SUPERUSER) or None if not available
    """
    if not user:
        return None

    # Primary check: role field (the main authorization mechanism)
    role = getattr(user, "role", None)
    if role and role in VALID_ROLES:
        return role

    return None


def has_role(user: Any, required_roles: list[str]) -> bool:
    """
    Check if user has one of the required roles.

    This is the primary role-checking function that should be used for
    authorization decisions. It checks the User.role field first, then
    falls back to Django's built-in permission system for backward compatibility.

    Args:
        user: User object
        required_roles: List of role strings that are allowed (e.g., ["MODERATOR", "ADMIN"])

    Returns:
        True if user has one of the required roles

    Example:
        # Check if user is moderator or above
        if has_role(user, ["MODERATOR", "ADMIN", "SUPERUSER"]):
            # Allow action
    """
    if not user or not hasattr(user, "is_authenticated"):
        return False

    if not user.is_authenticated:
        return False

    # Primary check: role field (the main authorization mechanism)
    user_role = get_user_role(user)
    if user_role and user_role in required_roles:
        return True

    # Fallback checks for backward compatibility with Django's permission system
    # Only apply if role field is not set
    if user_role is None:
        # Check for superuser (Django's is_superuser flag)
        if hasattr(user, "is_superuser") and user.is_superuser:  # noqa: SIM102
            if "SUPERUSER" in required_roles or "ADMIN" in required_roles:
                return True

        # Check for staff status (treat as moderator)
        if hasattr(user, "is_staff") and user.is_staff and "MODERATOR" in required_roles:
            return True

    return False


def is_moderator_or_above(user: Any) -> bool:
    """
    Check if user has moderator or higher permissions.

    This checks the User.role field for MODERATOR, ADMIN, or SUPERUSER roles.
    Falls back to Django's is_staff and is_superuser for backward compatibility.

    Args:
        user: User object

    Returns:
        True if user is moderator or above

    Example:
        if is_moderator_or_above(request.user):
            # Allow moderation action
    """
    return has_role(user, MODERATOR_ROLES)


def is_admin_or_above(user: Any) -> bool:
    """
    Check if user has admin or higher permissions.

    This checks the User.role field for ADMIN or SUPERUSER roles.
    Falls back to Django's is_superuser for backward compatibility.

    Args:
        user: User object

    Returns:
        True if user is admin or above

    Example:
        if is_admin_or_above(request.user):
            # Allow admin action
    """
    return has_role(user, ADMIN_ROLES)


def is_superuser_role(user: Any) -> bool:
    """
    Check if user has superuser role.

    This checks the User.role field for SUPERUSER role specifically.
    Falls back to Django's is_superuser for backward compatibility.

    Args:
        user: User object

    Returns:
        True if user has superuser role

    Example:
        if is_superuser_role(request.user):
            # Allow superuser-only action
    """
    return has_role(user, SUPERUSER_ROLES)


def has_permission(user: Any, permission: str) -> bool:
    """
    Check for custom Django permission.

    This is a fallback for checking Django's permission system when
    role-based checks are not sufficient.

    Args:
        user: User object
        permission: Permission string (e.g., 'app.permission_name')

    Returns:
        True if user has the permission

    Example:
        if has_permission(user, 'moderation.can_bulk_delete'):
            # Allow bulk delete
    """
    if not user or not hasattr(user, "is_authenticated"):
        return False

    if not user.is_authenticated:
        return False

    # Superuser role has all permissions
    if is_superuser_role(user):
        return True

    # Fallback to Django's is_superuser
    if hasattr(user, "is_superuser") and user.is_superuser:
        return True

    # Check specific permission
    if hasattr(user, "has_perm"):
        return user.has_perm(permission)

    return False


def create_guard_from_drf_permission(
    permission_class: type,
    error_message: str | None = None,
) -> Callable:
    """
    Create an FSM guard from a DRF permission class.

    This function allows DRF permission classes to be used as guards
    in FSM transition conditions, ensuring consistent authorization
    between API endpoints and state transitions.

    Args:
        permission_class: The DRF permission class to adapt
        error_message: Optional custom error message

    Returns:
        Guard function compatible with FSM transition conditions

    Example:
        from apps.moderation.permissions import IsModeratorOrAdmin

        guard = create_guard_from_drf_permission(IsModeratorOrAdmin)

        # Use in transition definition
        @transition(conditions=[guard])
        def approve(self, user=None):
            pass
    """
    # Check if the permission class has an as_guard method
    if hasattr(permission_class, "as_guard"):
        return permission_class.as_guard(error_message=error_message)

    # Otherwise, create a simple wrapper
    class DRFPermissionGuard:
        """Guard that wraps a DRF permission class."""

        def __init__(self, perm_class: type, err_msg: str | None = None):
            self.permission_class = perm_class
            self._custom_error_message = err_msg
            self._last_error_code: str | None = None

        @property
        def error_code(self) -> str | None:
            return self._last_error_code

        def __call__(self, instance: Any, user: Any = None) -> bool:
            self._last_error_code = None

            if user is None:
                self._last_error_code = "NO_USER"
                return False

            # Create a mock request object for DRF permission check
            class MockRequest:
                def __init__(self, u):
                    self.user = u
                    self.data = {}
                    self.method = "POST"

            mock_request = MockRequest(user)
            permission = self.permission_class()

            if not permission.has_permission(mock_request, None):
                self._last_error_code = "PERMISSION_DENIED"
                return False

            if hasattr(permission, "has_object_permission"):  # noqa: SIM102
                if not permission.has_object_permission(mock_request, None, instance):
                    self._last_error_code = "OBJECT_PERMISSION_DENIED"
                    return False

            return True

        def get_error_message(self) -> str:
            if self._custom_error_message:
                return self._custom_error_message
            return f"Permission denied by {self.permission_class.__name__}"

    return DRFPermissionGuard(permission_class, error_message)


# Global guard registry instance
guard_registry = GuardRegistry()


__all__ = [
    # Role constants
    "VALID_ROLES",
    "MODERATOR_ROLES",
    "ADMIN_ROLES",
    "SUPERUSER_ROLES",
    "ESCALATION_LEVEL_ROLES",
    # Permission guard classes
    "PermissionGuard",
    "OwnershipGuard",
    "AssignmentGuard",
    "StateGuard",
    "MetadataGuard",
    "CompositeGuard",
    # Guard extraction and creation
    "extract_guards_from_metadata",
    "create_permission_guard",
    "create_ownership_guard",
    "create_assignment_guard",
    "create_composite_guard",
    "validate_guard_metadata",
    "create_guard_from_drf_permission",
    # Registry
    "GuardRegistry",
    "guard_registry",
    # Condition creation
    "create_condition_from_metadata",
    # Role checking functions
    "get_user_role",
    "has_role",
    "is_moderator_or_above",
    "is_admin_or_above",
    "is_superuser_role",
    "has_permission",
]
