"""
Integration tests for the refactored UndoableInteractionMixin.
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from pyPhotoAlbum.mixins.interaction_undo import UndoableInteractionMixin
from pyPhotoAlbum.models import BaseLayoutElement


class MockWidget(UndoableInteractionMixin):
    """Mock widget that uses the UndoableInteractionMixin."""

    def __init__(self):
        # Simulate QWidget initialization
        self._mock_window = Mock()
        self._mock_window.project = Mock()
        self._mock_window.project.history = Mock()

        super().__init__()

    def window(self):
        """Mock window() method."""
        return self._mock_window


class TestUndoableInteractionMixinRefactored:
    """Tests for refactored UndoableInteractionMixin."""

    def test_initialization(self):
        """Test that mixin initializes correctly."""
        widget = MockWidget()

        assert hasattr(widget, "_command_factory")
        assert hasattr(widget, "_interaction_state")

    def test_begin_move(self):
        """Test beginning a move interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)

        assert widget._interaction_state.element == element
        assert widget._interaction_state.interaction_type == "move"
        assert widget._interaction_state.position == (0.0, 0.0)

    def test_begin_resize(self):
        """Test beginning a resize interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)
        element.size = (100.0, 100.0)

        widget._begin_resize(element)

        assert widget._interaction_state.element == element
        assert widget._interaction_state.interaction_type == "resize"
        assert widget._interaction_state.position == (0.0, 0.0)
        assert widget._interaction_state.size == (100.0, 100.0)

    def test_begin_rotate(self):
        """Test beginning a rotate interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.rotation = 0.0

        widget._begin_rotate(element)

        assert widget._interaction_state.element == element
        assert widget._interaction_state.interaction_type == "rotate"
        assert widget._interaction_state.rotation == 0.0

    def test_begin_image_pan(self):
        """Test beginning an image pan interaction."""
        from pyPhotoAlbum.models import ImageData

        widget = MockWidget()
        element = Mock(spec=ImageData)
        element.crop_info = (0.0, 0.0, 1.0, 1.0)

        widget._begin_image_pan(element)

        assert widget._interaction_state.element == element
        assert widget._interaction_state.interaction_type == "image_pan"
        assert widget._interaction_state.crop_info == (0.0, 0.0, 1.0, 1.0)

    def test_begin_image_pan_non_image_element(self):
        """Test that image pan doesn't start for non-ImageData elements."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)

        widget._begin_image_pan(element)

        # Should not set interaction state
        assert widget._interaction_state.element is None

    def test_end_interaction_creates_move_command(self):
        """Test that ending a move interaction creates a command."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)

        # Simulate movement
        element.position = (10.0, 10.0)

        widget._end_interaction()

        # Verify command was executed
        widget._mock_window.project.history.execute.assert_called_once()

    def test_end_interaction_creates_resize_command(self):
        """Test that ending a resize interaction creates a command."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)
        element.size = (100.0, 100.0)

        widget._begin_resize(element)

        # Simulate resize
        element.size = (200.0, 200.0)

        widget._end_interaction()

        # Verify command was executed
        widget._mock_window.project.history.execute.assert_called_once()

    def test_end_interaction_creates_rotate_command(self):
        """Test that ending a rotate interaction creates a command."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.rotation = 0.0
        element.position = (0.0, 0.0)  # Required by RotateElementCommand
        element.size = (100.0, 100.0)  # Required by RotateElementCommand

        widget._begin_rotate(element)

        # Simulate rotation
        element.rotation = 45.0

        widget._end_interaction()

        # Verify command was executed
        widget._mock_window.project.history.execute.assert_called_once()

    def test_end_interaction_no_command_for_insignificant_change(self):
        """Test that no command is created for insignificant changes."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)

        # Insignificant movement
        element.position = (0.05, 0.05)

        widget._end_interaction()

        # Verify no command was executed
        widget._mock_window.project.history.execute.assert_not_called()

    def test_end_interaction_clears_state(self):
        """Test that ending interaction clears state."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)
        widget._end_interaction()

        assert widget._interaction_state.element is None
        assert widget._interaction_state.interaction_type is None

    def test_end_interaction_without_begin(self):
        """Test that ending interaction without beginning is safe."""
        widget = MockWidget()

        widget._end_interaction()

        # Should not crash or execute commands
        widget._mock_window.project.history.execute.assert_not_called()

    def test_cancel_interaction(self):
        """Test canceling an interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)
        widget._cancel_interaction()

        assert widget._interaction_state.element is None
        assert widget._interaction_state.interaction_type is None

    def test_clear_interaction_state(self):
        """Test clearing interaction state directly."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)
        widget._clear_interaction_state()

        assert widget._interaction_state.element is None

    def test_end_interaction_without_project(self):
        """Test that ending interaction without project is safe."""
        widget = MockWidget()
        # Remove the project attribute entirely
        delattr(widget._mock_window, "project")

        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        widget._begin_move(element)
        element.position = (10.0, 10.0)

        widget._end_interaction()

        # Should clear state without crashing
        assert widget._interaction_state.element is None


class TestMixinIntegrationWithFactory:
    """Integration tests between mixin and factory."""

    def test_move_interaction_complete_flow(self):
        """Test complete flow of a move interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        # Begin
        widget._begin_move(element)
        assert widget._interaction_state.is_valid()

        # Modify
        element.position = (50.0, 75.0)

        # End
        widget._end_interaction()

        # Verify
        widget._mock_window.project.history.execute.assert_called_once()
        assert not widget._interaction_state.is_valid()

    def test_resize_interaction_complete_flow(self):
        """Test complete flow of a resize interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)
        element.size = (100.0, 100.0)

        # Begin
        widget._begin_resize(element)

        # Modify
        element.position = (10.0, 10.0)
        element.size = (200.0, 150.0)

        # End
        widget._end_interaction()

        # Verify
        widget._mock_window.project.history.execute.assert_called_once()

    def test_rotate_interaction_complete_flow(self):
        """Test complete flow of a rotate interaction."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.rotation = 0.0
        element.position = (0.0, 0.0)  # Required by RotateElementCommand
        element.size = (100.0, 100.0)  # Required by RotateElementCommand

        # Begin
        widget._begin_rotate(element)

        # Modify
        element.rotation = 90.0

        # End
        widget._end_interaction()

        # Verify
        widget._mock_window.project.history.execute.assert_called_once()

    def test_multiple_interactions_in_sequence(self):
        """Test multiple interactions in sequence."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)
        element.size = (100.0, 100.0)
        element.rotation = 0.0

        # First interaction: move
        widget._begin_move(element)
        element.position = (10.0, 10.0)
        widget._end_interaction()

        # Second interaction: resize
        widget._begin_resize(element)
        element.size = (200.0, 200.0)
        widget._end_interaction()

        # Third interaction: rotate
        widget._begin_rotate(element)
        element.rotation = 45.0
        widget._end_interaction()

        # Should have created 3 commands
        assert widget._mock_window.project.history.execute.call_count == 3

    def test_interaction_with_cancel(self):
        """Test interaction flow with cancellation."""
        widget = MockWidget()
        element = Mock(spec=BaseLayoutElement)
        element.position = (0.0, 0.0)

        # Begin
        widget._begin_move(element)
        element.position = (50.0, 50.0)

        # Cancel instead of end
        widget._cancel_interaction()

        # No command should be created
        widget._mock_window.project.history.execute.assert_not_called()
