"""
Tests for ZOrderOperationsMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.operations.zorder_ops import ZOrderOperationsMixin
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 ZOrderOperationsMixin
class TestZOrderWindow(ZOrderOperationsMixin, QMainWindow):
    """Test window with z-order operations mixin"""

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

        # Mock GL widget
        self.gl_widget = Mock()
        self.gl_widget.selected_element = None
        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

    def get_current_page(self):
        """Return mock current page"""
        if hasattr(self, "_current_page"):
            return self._current_page
        return None

    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 TestBringToFront:
    """Test bring_to_front method"""

    def test_bring_to_front_success(self, qtbot):
        """Test bringing element to front"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        # Setup page with elements
        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element1 (at index 0, should move to index 2)
        window.gl_widget.selected_element = element1

        window.bring_to_front()

        # Element should now be at end
        assert layout.elements[-1] == element1
        assert layout.elements == [element2, element3, element1]
        assert window._update_view_called
        assert "front" in window._status_message.lower()

    def test_bring_to_front_already_at_front(self, qtbot):
        """Test bringing element that's already at front"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        layout.elements = [element1, element2]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element2 (already at front)
        window.gl_widget.selected_element = element2

        window.bring_to_front()

        # Order should not change
        assert layout.elements == [element1, element2]
        assert "already" in window._status_message.lower()

    def test_bring_to_front_no_selection(self, qtbot):
        """Test bring to front with no selection"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        page = Mock()
        page.layout = layout
        window._current_page = page

        window.gl_widget.selected_element = None

        window.bring_to_front()

        # Should do nothing
        assert not window._update_view_called

    def test_bring_to_front_no_page(self, qtbot):
        """Test bring to front with no current page"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        window.gl_widget.selected_element = ImageData(image_path="/test.jpg", x=0, y=0, width=100, height=100)
        window._current_page = None

        window.bring_to_front()

        # Should do nothing
        assert not window._update_view_called


class TestSendToBack:
    """Test send_to_back method"""

    def test_send_to_back_success(self, qtbot):
        """Test sending element to back"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element3 (at index 2, should move to index 0)
        window.gl_widget.selected_element = element3

        window.send_to_back()

        # Element should now be at start
        assert layout.elements[0] == element3
        assert layout.elements == [element3, element1, element2]
        assert window._update_view_called
        assert "back" in window._status_message.lower()

    def test_send_to_back_already_at_back(self, qtbot):
        """Test sending element that's already at back"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        layout.elements = [element1, element2]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element1 (already at back)
        window.gl_widget.selected_element = element1

        window.send_to_back()

        # Order should not change
        assert layout.elements == [element1, element2]
        assert "already" in window._status_message.lower()

    def test_send_to_back_no_selection(self, qtbot):
        """Test send to back with no selection"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        page = Mock()
        page.layout = layout
        window._current_page = page

        window.gl_widget.selected_element = None

        window.send_to_back()

        # Should do nothing
        assert not window._update_view_called


class TestBringForward:
    """Test bring_forward method"""

    def test_bring_forward_success(self, qtbot):
        """Test bringing element forward one position"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element1 (at index 0, should move to index 1)
        window.gl_widget.selected_element = element1

        window.bring_forward()

        # Element should move forward one position
        assert layout.elements == [element2, element1, element3]
        assert window._update_view_called
        assert "forward" in window._status_message.lower()

    def test_bring_forward_already_at_front(self, qtbot):
        """Test bringing forward element that's already at front"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        layout.elements = [element1, element2]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element2 (already at front)
        window.gl_widget.selected_element = element2

        window.bring_forward()

        # Order should not change
        assert layout.elements == [element1, element2]
        assert "already" in window._status_message.lower()


class TestSendBackward:
    """Test send_backward method"""

    def test_send_backward_success(self, qtbot):
        """Test sending element backward one position"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element3 (at index 2, should move to index 1)
        window.gl_widget.selected_element = element3

        window.send_backward()

        # Element should move backward one position
        assert layout.elements == [element1, element3, element2]
        assert window._update_view_called
        assert "backward" in window._status_message.lower()

    def test_send_backward_already_at_back(self, qtbot):
        """Test sending backward element that's already at back"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        layout.elements = [element1, element2]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element1 (already at back)
        window.gl_widget.selected_element = element1

        window.send_backward()

        # Order should not change
        assert layout.elements == [element1, element2]
        assert "already" in window._status_message.lower()


class TestSwapOrder:
    """Test swap_order method"""

    def test_swap_order_success(self, qtbot):
        """Test swapping z-order of two elements"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select element1 and element3
        window.gl_widget.selected_elements = {element1, element3}

        window.swap_order()

        # Elements should be swapped
        assert layout.elements == [element3, element2, element1]
        assert window._update_view_called
        assert "swapped" in window._status_message.lower()

    def test_swap_order_wrong_count(self, qtbot):
        """Test swap with wrong number of selections"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
        layout.elements = [element1]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select only one element
        window.gl_widget.selected_elements = {element1}

        window.swap_order()

        # Should show error message
        assert "exactly 2" in window._status_message.lower()
        assert not window._update_view_called

    def test_swap_order_no_page(self, qtbot):
        """Test swap with no current page"""
        window = TestZOrderWindow()
        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}
        window._current_page = None

        window.swap_order()

        # Should do nothing
        assert not window._update_view_called

    def test_swap_order_elements_not_on_page(self, qtbot):
        """Test swap with elements not on current page"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        page_element = ImageData(image_path="/page.jpg", x=0, y=0, width=100, height=100)
        layout.elements = [page_element]

        page = Mock()
        page.layout = layout
        window._current_page = page

        # Select elements not on the page
        other1 = ImageData(image_path="/other1.jpg", x=0, y=0, width=100, height=100)
        other2 = ImageData(image_path="/other2.jpg", x=100, y=100, width=100, height=100)
        window.gl_widget.selected_elements = {other1, other2}

        window.swap_order()

        # Should show error
        assert "not found" in window._status_message.lower()
        assert not window._update_view_called


class TestZOrderWithCommandPattern:
    """Test z-order operations with command pattern for undo/redo"""

    def test_bring_to_front_creates_command(self, qtbot):
        """Test that bring_to_front creates a command for undo"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        layout.elements = [element1, element2]

        page = Mock()
        page.layout = layout
        window._current_page = page

        window.gl_widget.selected_element = element1

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

        window.bring_to_front()

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

    def test_send_to_back_undo_redo(self, qtbot):
        """Test that send_to_back can be undone and redone"""
        window = TestZOrderWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        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)
        element3 = ImageData(image_path="/test3.jpg", x=200, y=200, width=100, height=100)
        layout.elements = [element1, element2, element3]

        page = Mock()
        page.layout = layout
        window._current_page = page

        window.gl_widget.selected_element = element3

        # Execute operation
        window.send_to_back()
        assert layout.elements[0] == element3

        # Undo
        window.project.history.undo()
        assert layout.elements == [element1, element2, element3]

        # Redo
        window.project.history.redo()
        assert layout.elements[0] == element3
