"""
Unit tests for interaction command builders.
"""

import pytest
from unittest.mock import Mock, MagicMock
from pyPhotoAlbum.mixins.interaction_command_builders import (
    MoveCommandBuilder,
    ResizeCommandBuilder,
    RotateCommandBuilder,
    ImagePanCommandBuilder,
)
from pyPhotoAlbum.mixins.interaction_validators import InteractionChangeDetector


class TestMoveCommandBuilder:
    """Tests for MoveCommandBuilder."""

    def test_can_build_with_significant_change(self):
        """Test that can_build returns True for significant position changes."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)

        start_state = {"position": (0.0, 0.0)}

        assert builder.can_build(element, start_state)

    def test_can_build_with_insignificant_change(self):
        """Test that can_build returns False for insignificant changes."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (0.05, 0.05)

        start_state = {"position": (0.0, 0.0)}

        assert not builder.can_build(element, start_state)

    def test_can_build_with_no_position(self):
        """Test that can_build returns False when no position in start_state."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)

        start_state = {}

        assert not builder.can_build(element, start_state)

    def test_build_creates_command(self):
        """Test that build creates a MoveElementCommand."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)

        start_state = {"position": (0.0, 0.0)}

        command = builder.build(element, start_state)

        assert command is not None
        assert command.element == element

    def test_build_returns_none_for_insignificant_change(self):
        """Test that build returns None for insignificant changes."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (0.05, 0.05)

        start_state = {"position": (0.0, 0.0)}

        command = builder.build(element, start_state)

        assert command is None


class TestResizeCommandBuilder:
    """Tests for ResizeCommandBuilder."""

    def test_can_build_with_size_change(self):
        """Test that can_build returns True when size changes."""
        builder = ResizeCommandBuilder()
        element = Mock()
        element.position = (0.0, 0.0)
        element.size = (200.0, 200.0)

        start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}

        assert builder.can_build(element, start_state)

    def test_can_build_with_position_change(self):
        """Test that can_build returns True when position changes."""
        builder = ResizeCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)
        element.size = (100.0, 100.0)

        start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}

        assert builder.can_build(element, start_state)

    def test_can_build_with_both_changes(self):
        """Test that can_build returns True when both position and size change."""
        builder = ResizeCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)
        element.size = (200.0, 200.0)

        start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}

        assert builder.can_build(element, start_state)

    def test_can_build_with_no_change(self):
        """Test that can_build returns False when nothing changes."""
        builder = ResizeCommandBuilder()
        element = Mock()
        element.position = (0.0, 0.0)
        element.size = (100.0, 100.0)

        start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}

        assert not builder.can_build(element, start_state)

    def test_build_creates_command(self):
        """Test that build creates a ResizeElementCommand."""
        builder = ResizeCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)
        element.size = (200.0, 200.0)

        start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}

        command = builder.build(element, start_state)

        assert command is not None
        assert command.element == element


class TestRotateCommandBuilder:
    """Tests for RotateCommandBuilder."""

    def test_can_build_with_significant_change(self):
        """Test that can_build returns True for significant rotation changes."""
        builder = RotateCommandBuilder()
        element = Mock()
        element.rotation = 45.0

        start_state = {"rotation": 0.0}

        assert builder.can_build(element, start_state)

    def test_can_build_with_insignificant_change(self):
        """Test that can_build returns False for insignificant changes."""
        builder = RotateCommandBuilder()
        element = Mock()
        element.rotation = 0.05

        start_state = {"rotation": 0.0}

        assert not builder.can_build(element, start_state)

    def test_build_creates_command(self):
        """Test that build creates a RotateElementCommand."""
        builder = RotateCommandBuilder()
        element = Mock()
        element.rotation = 45.0

        start_state = {"rotation": 0.0}

        command = builder.build(element, start_state)

        assert command is not None
        assert command.element == element


class TestImagePanCommandBuilder:
    """Tests for ImagePanCommandBuilder."""

    def test_can_build_with_image_data(self):
        """Test that can_build works with ImageData elements."""
        from pyPhotoAlbum.models import ImageData

        builder = ImagePanCommandBuilder()
        element = Mock(spec=ImageData)
        element.crop_info = (0.1, 0.1, 0.9, 0.9)

        start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}

        assert builder.can_build(element, start_state)

    def test_can_build_with_non_image_data(self):
        """Test that can_build returns False for non-ImageData elements."""
        builder = ImagePanCommandBuilder()
        element = Mock()
        element.crop_info = (0.1, 0.1, 0.9, 0.9)

        start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}

        assert not builder.can_build(element, start_state)

    def test_can_build_with_insignificant_change(self):
        """Test that can_build returns False for insignificant crop changes."""
        from pyPhotoAlbum.models import ImageData

        builder = ImagePanCommandBuilder()
        element = Mock(spec=ImageData)
        element.crop_info = (0.0001, 0.0001, 1.0, 1.0)

        start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}

        assert not builder.can_build(element, start_state)

    def test_build_creates_command(self):
        """Test that build creates an AdjustImageCropCommand."""
        from pyPhotoAlbum.models import ImageData

        builder = ImagePanCommandBuilder()
        element = Mock(spec=ImageData)
        element.crop_info = (0.1, 0.1, 0.9, 0.9)

        start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}

        command = builder.build(element, start_state)

        assert command is not None
        assert command.element == element


class TestCommandBuilderIntegration:
    """Integration tests for command builders."""

    def test_builders_use_custom_detector(self):
        """Test that builders can use custom change detectors."""
        detector = InteractionChangeDetector(threshold=10.0)
        builder = MoveCommandBuilder(change_detector=detector)

        element = Mock()
        element.position = (5.0, 5.0)

        start_state = {"position": (0.0, 0.0)}

        # With high threshold, this should not build
        assert not builder.can_build(element, start_state)

    def test_builder_logging(self, capsys):
        """Test that builders log command creation."""
        builder = MoveCommandBuilder()
        element = Mock()
        element.position = (10.0, 10.0)

        start_state = {"position": (0.0, 0.0)}

        builder.build(element, start_state)

        captured = capsys.readouterr()
        assert "Move command created" in captured.out
