"""Integration tests for state machine model integration."""

from unittest.mock import Mock, patch

import pytest

from apps.core.choices.base import RichChoice
from apps.core.choices.registry import registry
from apps.core.state_machine.integration import (
    StateMachineModelMixin,
    apply_state_machine,
    generate_transition_methods_for_model,
    state_machine_model,
    validate_model_state_machine,
)
from apps.core.state_machine.registry import registry_instance


@pytest.fixture
def sample_choices():
    """Create sample choices for testing."""
    choices = [
        RichChoice(
            value="pending",
            label="Pending",
            metadata={"can_transition_to": ["approved", "rejected"]},
        ),
        RichChoice(
            value="approved",
            label="Approved",
            metadata={"is_final": True, "can_transition_to": []},
        ),
        RichChoice(
            value="rejected",
            label="Rejected",
            metadata={"is_final": True, "can_transition_to": []},
        ),
    ]
    registry.register("test_states", choices, domain="test")
    yield choices
    registry.clear_domain("test")
    registry_instance.clear_registry()


def test_apply_state_machine_valid(sample_choices):
    """Test applying state machine to model with valid metadata."""
    # Mock model class
    mock_model = type("MockModel", (), {})

    # Should not raise
    apply_state_machine(mock_model, "status", "test_states", "test")


def test_apply_state_machine_invalid():
    """Test applying state machine fails with invalid metadata."""
    choices = [
        RichChoice(
            value="pending",
            label="Pending",
            metadata={"can_transition_to": ["nonexistent"]},
        ),
    ]
    registry.register("invalid_states", choices, domain="test")

    mock_model = type("MockModel", (), {})

    with pytest.raises(ValueError) as exc_info:
        apply_state_machine(mock_model, "status", "invalid_states", "test")
    assert "validation failed" in str(exc_info.value).lower()

    registry.clear_domain("test")


def test_generate_transition_methods(sample_choices):
    """Test generating transition methods on model."""
    mock_model = type("MockModel", (), {})

    generate_transition_methods_for_model(mock_model, "status", "test_states", "test")

    # Check that transition methods were added
    # Method names may vary based on implementation
    assert hasattr(mock_model, "approve") or hasattr(mock_model, "transition_to_approved")


def test_state_machine_model_decorator(sample_choices):
    """Test state_machine_model decorator."""

    @state_machine_model(field_name="status", choice_group="test_states", domain="test")
    class TestModel:
        pass

    # Decorator should apply state machine
    # Check for transition methods
    assert hasattr(TestModel, "approve") or hasattr(TestModel, "transition_to_approved")


def test_state_machine_mixin_get_available_transitions():
    """Test StateMachineModelMixin.get_available_state_transitions."""

    class TestModel(StateMachineModelMixin):
        class _meta:
            @staticmethod
            def get_field(name):
                field = Mock()
                field.choice_group = "test_states"
                field.domain = "test"
                return field

        status = "pending"

    # Setup registry
    registry_instance.register_transition(
        choice_group="test_states",
        domain="test",
        source="pending",
        target="approved",
        method_name="approve",
    )

    instance = TestModel()
    transitions = instance.get_available_state_transitions("status")

    # Should return available transitions
    assert isinstance(transitions, list)


def test_state_machine_mixin_can_transition_to():
    """Test StateMachineModelMixin.can_transition_to."""

    class TestModel(StateMachineModelMixin):
        class _meta:
            @staticmethod
            def get_field(name):
                field = Mock()
                field.choice_group = "test_states"
                field.domain = "test"
                return field

        status = "pending"

        def approve(self):
            pass

    instance = TestModel()

    # Setup registry
    registry_instance.register_transition(
        choice_group="test_states",
        domain="test",
        source="pending",
        target="approved",
        method_name="approve",
    )

    # Mock can_proceed to return True
    with patch(
        "backend.apps.core.state_machine.integration.can_proceed",
        return_value=True,
    ):
        result = instance.can_transition_to("approved", "status")
        assert result is True


def test_state_machine_mixin_get_transition_method():
    """Test StateMachineModelMixin.get_transition_method."""

    class TestModel(StateMachineModelMixin):
        class _meta:
            @staticmethod
            def get_field(name):
                field = Mock()
                field.choice_group = "test_states"
                field.domain = "test"
                return field

        status = "pending"

        def approve(self):
            pass

    instance = TestModel()

    # Setup registry
    registry_instance.register_transition(
        choice_group="test_states",
        domain="test",
        source="pending",
        target="approved",
        method_name="approve",
    )

    method = instance.get_transition_method("approved", "status")
    assert method is not None
    assert callable(method)


def test_state_machine_mixin_execute_transition():
    """Test StateMachineModelMixin.execute_transition."""

    class TestModel(StateMachineModelMixin):
        class _meta:
            @staticmethod
            def get_field(name):
                field = Mock()
                field.choice_group = "test_states"
                field.domain = "test"
                return field

        status = "pending"

        def approve(self, user=None, **kwargs):
            self.status = "approved"

    instance = TestModel()

    # Setup registry
    registry_instance.register_transition(
        choice_group="test_states",
        domain="test",
        source="pending",
        target="approved",
        method_name="approve",
    )

    # Mock can_proceed
    with patch(
        "backend.apps.core.state_machine.integration.can_proceed",
        return_value=True,
    ):
        result = instance.execute_transition("approved", "status")
        assert result is True


def test_validate_model_state_machine_valid(sample_choices):
    """Test model validation with valid configuration."""

    class TestModel:
        class _meta:
            @staticmethod
            def get_field(name):
                field = Mock()
                field.choice_group = "test_states"
                field.domain = "test"
                return field

    result = validate_model_state_machine(TestModel, "status")
    assert result is True


def test_validate_model_state_machine_missing_field():
    """Test validation fails when field is missing."""

    class TestModel:
        class _meta:
            @staticmethod
            def get_field(name):
                raise Exception("Field not found")

    with pytest.raises(ValueError) as exc_info:
        validate_model_state_machine(TestModel, "status")
    assert "not found" in str(exc_info.value).lower()


def test_validate_model_state_machine_not_fsm_field():
    """Test validation fails when field is not FSM field."""

    class TestModel:
        class _meta:
            @staticmethod
            def get_field(name):
                return Mock(spec=[])  # Field without choice_group

    with pytest.raises(ValueError) as exc_info:
        validate_model_state_machine(TestModel, "status")
    assert "RichFSMField" in str(exc_info.value)
