"""
Test helper functions for state machine tests.

This module provides utility functions for testing state machine functionality:
- Transition assertion helpers
- State log verification helpers
- Guard testing utilities
"""

from collections.abc import Callable
from typing import Any

from django.contrib.contenttypes.models import ContentType


def assert_transition_allowed(instance: Any, method_name: str, user: Any | None = None) -> bool:
    """
    Assert that a transition is allowed.

    Args:
        instance: Model instance with FSM field
        method_name: Name of the transition method
        user: User attempting the transition

    Returns:
        True if transition is allowed

    Raises:
        AssertionError: If transition is not allowed

    Example:
        assert_transition_allowed(submission, 'transition_to_approved', moderator)
    """
    from django_fsm import can_proceed

    method = getattr(instance, method_name)
    result = can_proceed(method)
    assert result, f"Transition {method_name} should be allowed but was denied"
    return True


def assert_transition_denied(instance: Any, method_name: str, user: Any | None = None) -> bool:
    """
    Assert that a transition is denied.

    Args:
        instance: Model instance with FSM field
        method_name: Name of the transition method
        user: User attempting the transition

    Returns:
        True if transition is denied

    Raises:
        AssertionError: If transition is allowed

    Example:
        assert_transition_denied(submission, 'transition_to_approved', regular_user)
    """
    from django_fsm import can_proceed

    method = getattr(instance, method_name)
    result = can_proceed(method)
    assert not result, f"Transition {method_name} should be denied but was allowed"
    return True


def assert_state_log_created(instance: Any, expected_state: str, user: Any | None = None) -> Any:
    """
    Assert that a StateLog entry was created for a transition.

    Args:
        instance: Model instance that was transitioned
        expected_state: The expected final state in the log
        user: Expected user who made the transition (optional)

    Returns:
        The StateLog entry

    Raises:
        AssertionError: If StateLog entry not found or doesn't match

    Example:
        log = assert_state_log_created(submission, 'APPROVED', moderator)
    """
    from django_fsm_log.models import StateLog

    ct = ContentType.objects.get_for_model(instance)
    log = StateLog.objects.filter(content_type=ct, object_id=instance.id, state=expected_state).first()

    assert log is not None, f"StateLog for state '{expected_state}' not found"

    if user is not None:
        assert log.by == user, f"Expected log.by={user}, got {log.by}"

    return log


def assert_state_log_count(instance: Any, expected_count: int) -> list[Any]:
    """
    Assert the number of StateLog entries for an instance.

    Args:
        instance: Model instance to check logs for
        expected_count: Expected number of log entries

    Returns:
        List of StateLog entries

    Raises:
        AssertionError: If count doesn't match

    Example:
        logs = assert_state_log_count(submission, 2)
    """
    from django_fsm_log.models import StateLog

    ct = ContentType.objects.get_for_model(instance)
    logs = list(StateLog.objects.filter(content_type=ct, object_id=instance.id).order_by("timestamp"))

    actual_count = len(logs)
    assert actual_count == expected_count, f"Expected {expected_count} StateLog entries, got {actual_count}"

    return logs


def assert_state_transition_sequence(instance: Any, expected_states: list[str]) -> list[Any]:
    """
    Assert that state transitions occurred in a specific sequence.

    Args:
        instance: Model instance to check
        expected_states: List of expected states in order

    Returns:
        List of StateLog entries

    Raises:
        AssertionError: If sequence doesn't match

    Example:
        assert_state_transition_sequence(submission, ['ESCALATED', 'APPROVED'])
    """
    from django_fsm_log.models import StateLog

    ct = ContentType.objects.get_for_model(instance)
    logs = list(StateLog.objects.filter(content_type=ct, object_id=instance.id).order_by("timestamp"))

    actual_states = [log.state for log in logs]
    assert actual_states == expected_states, f"Expected state sequence {expected_states}, got {actual_states}"

    return logs


def assert_guard_passes(guard: Callable, instance: Any, user: Any | None = None, message: str = "") -> bool:
    """
    Assert that a guard function passes.

    Args:
        guard: Guard function or callable
        instance: Model instance to check
        user: User attempting the action
        message: Optional message on failure

    Returns:
        True if guard passes

    Raises:
        AssertionError: If guard fails

    Example:
        assert_guard_passes(permission_guard, instance, moderator)
    """
    result = guard(instance, user)
    fail_message = message or f"Guard should pass but returned {result}"
    assert result is True, fail_message
    return True


def assert_guard_fails(
    guard: Callable, instance: Any, user: Any | None = None, expected_error_code: str | None = None, message: str = ""
) -> bool:
    """
    Assert that a guard function fails.

    Args:
        guard: Guard function or callable
        instance: Model instance to check
        user: User attempting the action
        expected_error_code: Expected error code from guard
        message: Optional message on failure

    Returns:
        True if guard fails as expected

    Raises:
        AssertionError: If guard passes or wrong error code

    Example:
        assert_guard_fails(permission_guard, instance, regular_user, 'PERMISSION_DENIED')
    """
    result = guard(instance, user)
    fail_message = message or f"Guard should fail but returned {result}"
    assert result is False, fail_message

    if expected_error_code and hasattr(guard, "error_code"):
        assert (
            guard.error_code == expected_error_code
        ), f"Expected error code {expected_error_code}, got {guard.error_code}"

    return True


def transition_and_save(instance: Any, transition_method: str, user: Any | None = None, **kwargs) -> Any:
    """
    Execute a transition and save the instance.

    Args:
        instance: Model instance with FSM field
        transition_method: Name of the transition method
        user: User performing the transition
        **kwargs: Additional arguments for the transition

    Returns:
        The saved instance

    Example:
        submission = transition_and_save(submission, 'transition_to_approved', moderator)
    """
    method = getattr(instance, transition_method)
    method(user=user, **kwargs)
    instance.save()
    instance.refresh_from_db()
    return instance


def get_available_transitions(instance: Any) -> list[str]:
    """
    Get list of available transitions for an instance.

    Args:
        instance: Model instance with FSM field

    Returns:
        List of available transition method names

    Example:
        transitions = get_available_transitions(submission)
        # ['transition_to_approved', 'transition_to_rejected', 'transition_to_escalated']
    """

    # Get the state field name from the instance
    state_field = getattr(instance, "state_field_name", "status")

    # Build the function name dynamically
    func_name = f"get_available_{state_field}_transitions"
    if hasattr(instance, func_name):
        get_transitions = getattr(instance, func_name)
        return [t.name for t in get_transitions()]

    # Fallback: look for transition methods
    transitions = []
    for attr_name in dir(instance):
        if attr_name.startswith("transition_to_"):
            transitions.append(attr_name)

    return transitions


def create_transition_context(instance: Any, from_state: str, to_state: str, user: Any | None = None, **extra) -> dict:
    """
    Create a mock transition context dictionary.

    Args:
        instance: Model instance being transitioned
        from_state: Source state
        to_state: Target state
        user: User performing the transition
        **extra: Additional context data

    Returns:
        Dictionary matching TransitionContext structure

    Example:
        context = create_transition_context(submission, 'PENDING', 'APPROVED', moderator)
    """
    return {
        "instance": instance,
        "from_state": from_state,
        "to_state": to_state,
        "user": user,
        "model_class": type(instance),
        "transition_name": f"transition_to_{to_state.lower()}",
        **extra,
    }
