"""
Tests for AlignmentOperationsMixin
"""

import pytest
from unittest.mock import Mock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.operations.alignment_ops import AlignmentOperationsMixin
from pyPhotoAlbum.models import ImageData, TextBoxData
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.commands import CommandHistory


# Create test window with AlignmentOperationsMixin
class TestAlignmentWindow(AlignmentOperationsMixin, QMainWindow):
    """Test window with alignment operations mixin"""

    def __init__(self):
        super().__init__()

        # Mock GL widget
        self.gl_widget = Mock()
        self.gl_widget.selected_elements = set()

        # Mock project
        self.project = Mock()
        self.project.history = CommandHistory()

        # Track method calls
        self._update_view_called = False
        self._status_message = None
        self._require_selection_count = None

    def require_selection(self, min_count=1):
        """Track require_selection calls"""
        self._require_selection_count = min_count
        return len(self.gl_widget.selected_elements) >= min_count

    def update_view(self):
        """Track update_view calls"""
        self._update_view_called = True

    def show_status(self, message, timeout=0):
        """Track status messages"""
        self._status_message = message


class TestGetSelectedElementsList:
    """Test _get_selected_elements_list helper"""

    def test_get_selected_elements_list(self, qtbot):
        """Test getting selected elements as list"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        result = window._get_selected_elements_list()

        assert isinstance(result, list)
        assert len(result) == 2
        assert element1 in result
        assert element2 in result

    def test_get_selected_elements_list_empty(self, qtbot):
        """Test getting empty list when nothing selected"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        window.gl_widget.selected_elements = set()

        result = window._get_selected_elements_list()

        assert result == []


class TestAlignLeft:
    """Test align_left method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_left_success(self, mock_manager, qtbot):
        """Test aligning elements to the left"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        # Mock AlignmentManager to return changes
        mock_manager.align_left.return_value = [(element1, (100, 0)), (element2, (200, 100))]

        window.align_left()

        # Should call AlignmentManager
        assert mock_manager.align_left.called
        assert window._update_view_called
        assert "aligned" in window._status_message.lower()
        assert "left" in window._status_message.lower()

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_left_no_changes(self, mock_manager, qtbot):
        """Test align left when no changes needed"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=0, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        # Mock AlignmentManager to return no changes
        mock_manager.align_left.return_value = []

        window.align_left()

        # Should not update view or show status
        assert not window._update_view_called

    def test_align_left_insufficient_selection(self, qtbot):
        """Test align left with fewer than 2 elements"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        window.gl_widget.selected_elements = {element1}

        window.align_left()

        # Should check for minimum 2 elements
        assert window._require_selection_count == 2
        assert not window._update_view_called


class TestAlignRight:
    """Test align_right method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_right_success(self, mock_manager, qtbot):
        """Test aligning elements to the right"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=150, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_right.return_value = [(element1, (100, 0)), (element2, (200, 100))]

        window.align_right()

        assert mock_manager.align_right.called
        assert window._update_view_called
        assert "right" in window._status_message.lower()


class TestAlignTop:
    """Test align_top method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_top_success(self, mock_manager, qtbot):
        """Test aligning elements to the top"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=50, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_top.return_value = [(element1, (0, 50)), (element2, (100, 100))]

        window.align_top()

        assert mock_manager.align_top.called
        assert window._update_view_called
        assert "top" in window._status_message.lower()


class TestAlignBottom:
    """Test align_bottom method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_bottom_success(self, mock_manager, qtbot):
        """Test aligning elements to the bottom"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=50, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=150)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_bottom.return_value = [(element1, (0, 50)), (element2, (100, 100))]

        window.align_bottom()

        assert mock_manager.align_bottom.called
        assert window._update_view_called
        assert "bottom" in window._status_message.lower()


class TestAlignHorizontalCenter:
    """Test align_horizontal_center method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_horizontal_center_success(self, mock_manager, qtbot):
        """Test aligning elements to horizontal center"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_horizontal_center.return_value = [(element1, (0, 0)), (element2, (200, 100))]

        window.align_horizontal_center()

        assert mock_manager.align_horizontal_center.called
        assert window._update_view_called
        assert "horizontal center" in window._status_message.lower()


class TestAlignVerticalCenter:
    """Test align_vertical_center method"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_align_vertical_center_success(self, mock_manager, qtbot):
        """Test aligning elements to vertical center"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=100, y=200, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_vertical_center.return_value = [(element1, (0, 0)), (element2, (100, 200))]

        window.align_vertical_center()

        assert mock_manager.align_vertical_center.called
        assert window._update_view_called
        assert "vertical center" in window._status_message.lower()


class TestAlignmentCommandPattern:
    """Test alignment operations with command pattern for undo/redo"""

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_alignment_creates_command(self, mock_manager, qtbot):
        """Test that alignment creates a command for undo"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        mock_manager.align_left.return_value = [(element1, (100, 0)), (element2, (200, 100))]

        # Should have no commands initially
        assert not window.project.history.can_undo()

        window.align_left()

        # Should have created a command
        assert window.project.history.can_undo()

    @patch("pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager")
    def test_alignment_undo_redo(self, mock_manager, qtbot):
        """Test that alignment can be undone and redone"""
        window = TestAlignmentWindow()
        qtbot.addWidget(window)

        element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
        element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)

        window.gl_widget.selected_elements = {element1, element2}

        # Mock alignment to return changes (command will handle actual moves)
        mock_manager.align_top.return_value = [(element1, (100, 0)), (element2, (200, 100))]

        # Execute alignment - command created
        window.align_top()

        # Should have created a command
        assert window.project.history.can_undo()

        # Can redo after undo
        window.project.history.undo()
        assert window.project.history.can_redo()

        # Redo works
        window.project.history.redo()
        assert not window.project.history.can_redo()  # Nothing left to redo
