"""
Tests for SizeOperationsMixin
"""

import pytest
from unittest.mock import Mock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.operations.size_ops import SizeOperationsMixin
from pyPhotoAlbum.models import ImageData
from pyPhotoAlbum.commands import CommandHistory
from pyPhotoAlbum.page_layout import PageLayout


class TestSizeWindow(SizeOperationsMixin, QMainWindow):
    """Test window with size operations mixin"""

    def __init__(self):
        super().__init__()
        self.gl_widget = Mock()
        self.gl_widget.selected_elements = set()
        self.project = Mock()
        self.project.history = CommandHistory()
        self._update_view_called = False
        self._status_message = None
        self._warning_message = None
        self._require_selection_count = None

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

    def get_current_page(self):
        if hasattr(self, "_current_page"):
            return self._current_page
        return None

    def update_view(self):
        self._update_view_called = True

    def show_status(self, message, timeout=0):
        self._status_message = message

    def show_warning(self, title, message):
        self._warning_message = message


class TestMakeSameSize:
    """Test make_same_size method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_make_same_size_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

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

        window.gl_widget.selected_elements = {element1, element2}

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

        window.make_same_size()

        assert mock_manager.make_same_size.called
        assert window._update_view_called
        assert "same size" in window._status_message.lower()

    def test_make_same_size_insufficient_selection(self, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

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

        window.make_same_size()

        assert window._require_selection_count == 2
        assert not window._update_view_called


class TestMakeSameWidth:
    """Test make_same_width method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_make_same_width_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

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

        window.gl_widget.selected_elements = {element1, element2}

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

        window.make_same_width()

        assert mock_manager.make_same_width.called
        assert window._update_view_called
        assert "same width" in window._status_message.lower()


class TestMakeSameHeight:
    """Test make_same_height method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_make_same_height_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

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

        window.gl_widget.selected_elements = {element1, element2}

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

        window.make_same_height()

        assert mock_manager.make_same_height.called
        assert window._update_view_called
        assert "same height" in window._status_message.lower()


class TestFitToWidth:
    """Test fit_to_width method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_fit_to_width_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        # Setup page
        page = Mock()
        page.layout = Mock()
        page.layout.size = (210, 297)  # A4
        window._current_page = page

        mock_manager.fit_to_page_width.return_value = (element, (50, 50), (100, 100))

        window.fit_to_width()

        assert mock_manager.fit_to_page_width.called
        assert window._update_view_called
        assert "width" in window._status_message.lower()

    def test_fit_to_width_no_page(self, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        window._current_page = None

        window.fit_to_width()

        assert "page" in window._warning_message.lower()
        assert not window._update_view_called


class TestFitToHeight:
    """Test fit_to_height method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_fit_to_height_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        page = Mock()
        page.layout = Mock()
        page.layout.size = (210, 297)
        window._current_page = page

        mock_manager.fit_to_page_height.return_value = (element, (50, 50), (100, 100))

        window.fit_to_height()

        assert mock_manager.fit_to_page_height.called
        assert window._update_view_called
        assert "height" in window._status_message.lower()


class TestFitToPage:
    """Test fit_to_page method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_fit_to_page_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        page = Mock()
        page.layout = Mock()
        page.layout.size = (210, 297)
        window._current_page = page

        mock_manager.fit_to_page.return_value = (element, (50, 50), (100, 100))

        window.fit_to_page()

        assert mock_manager.fit_to_page.called
        assert window._update_view_called
        assert "fitted element to page" in window._status_message.lower()

    def test_fit_to_page_no_page(self, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        window._current_page = None

        window.fit_to_page()

        assert "page" in window._warning_message.lower()
        assert not window._update_view_called


class TestSizeCommandPattern:
    """Test size operations with command pattern"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_size_operation_creates_command(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

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

        window.gl_widget.selected_elements = {element1, element2}

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

        assert not window.project.history.can_undo()

        window.make_same_size()

        assert window.project.history.can_undo()

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_fit_operation_creates_command(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        window.gl_widget.selected_elements = {element}

        page = Mock()
        page.layout = Mock()
        page.layout.size = (210, 297)
        window._current_page = page

        mock_manager.fit_to_page.return_value = (element, (50, 50), (100, 100))

        assert not window.project.history.can_undo()

        window.fit_to_page()

        assert window.project.history.can_undo()


class TestExpandImage:
    """Test expand_image method"""

    @patch("pyPhotoAlbum.mixins.operations.size_ops.AlignmentManager")
    def test_expand_image_success(self, mock_manager, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        # Create an element to expand
        element = ImageData(image_path="/test.jpg", x=50, y=50, width=50, height=50)
        window.gl_widget.selected_elements = {element}

        # Create a mock page with other elements
        page = Mock()
        page.layout = Mock()
        page.layout.size = (210, 297)
        page.layout.snapping_system = Mock()
        page.layout.snapping_system.grid_spacing = 10.0

        # Mock other elements on the page
        other_element = ImageData(image_path="/other.jpg", x=150, y=50, width=50, height=50)
        page.layout.elements = [element, other_element]

        window._current_page = page

        # Mock the expand_to_bounds return value
        mock_manager.expand_to_bounds.return_value = (element, (50, 50), (50, 50))

        window.expand_image()

        # Verify that expand_to_bounds was called with correct parameters
        assert mock_manager.expand_to_bounds.called
        call_args = mock_manager.expand_to_bounds.call_args
        assert call_args[0][0] == element  # First arg is the element
        assert call_args[0][1] == (210, 297)  # Second arg is page size
        assert other_element in call_args[0][2]  # Third arg includes other elements
        assert element not in call_args[0][2]  # But not the selected element itself
        assert call_args[0][3] == 10.0  # Fourth arg is min_gap

        assert window._update_view_called
        assert "expanded image" in window._status_message.lower()
        assert "10" in window._status_message  # Gap size mentioned

    def test_expand_image_no_page(self, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        element = ImageData(image_path="/test.jpg", x=50, y=50, width=50, height=50)
        window.gl_widget.selected_elements = {element}
        window._current_page = None

        window.expand_image()

        assert "page" in window._warning_message.lower()
        assert not window._update_view_called

    def test_expand_image_insufficient_selection(self, qtbot):
        window = TestSizeWindow()
        qtbot.addWidget(window)

        window.gl_widget.selected_elements = set()

        window.expand_image()

        assert window._require_selection_count == 1
        assert not window._update_view_called
