"""
Unit tests for z-order operations in pyPhotoAlbum
"""

import pytest
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.commands import ChangeZOrderCommand, CommandHistory


class TestZOrderBasics:
    """Tests for basic z-order functionality"""

    def test_list_order_is_render_order(self):
        """Test that list order determines render order"""
        layout = PageLayout(width=210, height=297)

        # Add elements in order
        elem1 = ImageData(x=10, y=10, width=50, height=50)
        elem2 = TextBoxData(x=20, y=20, width=50, height=50)
        elem3 = PlaceholderData(x=30, y=30, width=50, height=50)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Verify order
        assert layout.elements[0] is elem1
        assert layout.elements[1] is elem2
        assert layout.elements[2] is elem3

    def test_element_at_end_renders_on_top(self):
        """Test that element at end of list renders on top"""
        layout = PageLayout(width=210, height=297)

        elem1 = ImageData(x=10, y=10)
        elem2 = ImageData(x=20, y=20)

        layout.add_element(elem1)
        layout.add_element(elem2)

        # elem2 should be last (on top)
        assert layout.elements[-1] is elem2
        assert layout.elements.index(elem2) > layout.elements.index(elem1)


class TestChangeZOrderCommand:
    """Tests for ChangeZOrderCommand"""

    def test_move_element_forward(self):
        """Test moving an element forward one position"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Move elem1 forward (swap with elem2)
        cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=1)
        cmd.execute()

        assert layout.elements.index(elem1) == 1
        assert layout.elements.index(elem2) == 0
        assert layout.elements.index(elem3) == 2

    def test_move_element_backward(self):
        """Test moving an element backward one position"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Move elem2 backward (swap with elem1)
        cmd = ChangeZOrderCommand(layout, elem2, old_index=1, new_index=0)
        cmd.execute()

        assert layout.elements.index(elem2) == 0
        assert layout.elements.index(elem1) == 1
        assert layout.elements.index(elem3) == 2

    def test_move_to_front(self):
        """Test moving an element to the front (end of list)"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Move elem1 to front
        cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
        cmd.execute()

        assert layout.elements[-1] is elem1
        assert layout.elements.index(elem1) == 2

    def test_move_to_back(self):
        """Test moving an element to the back (start of list)"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Move elem3 to back
        cmd = ChangeZOrderCommand(layout, elem3, old_index=2, new_index=0)
        cmd.execute()

        assert layout.elements[0] is elem3
        assert layout.elements.index(elem3) == 0

    def test_undo_redo(self):
        """Test undo/redo functionality"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        original_order = list(layout.elements)

        # Move elem1 forward
        cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=1)
        cmd.execute()

        assert layout.elements.index(elem1) == 1

        # Undo
        cmd.undo()
        assert layout.elements == original_order

        # Redo
        cmd.redo()
        assert layout.elements.index(elem1) == 1

    def test_command_with_history(self):
        """Test ChangeZOrderCommand with CommandHistory"""
        layout = PageLayout()
        history = CommandHistory()

        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Execute command through history
        cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
        history.execute(cmd)

        assert layout.elements.index(elem1) == 2
        assert history.can_undo()

        # Undo through history
        history.undo()
        assert layout.elements.index(elem1) == 0
        assert history.can_redo()

        # Redo through history
        history.redo()
        assert layout.elements.index(elem1) == 2


class TestZOrderSerialization:
    """Tests for z-order serialization and deserialization"""

    def test_serialize_preserves_order(self):
        """Test that serialization preserves element order"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10, z_index=0)
        elem2 = TextBoxData(x=20, y=20, z_index=1)
        elem3 = PlaceholderData(x=30, y=30, z_index=2)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Serialize
        data = layout.serialize()

        # Elements should be in order
        assert len(data["elements"]) == 3
        assert data["elements"][0]["type"] == "image"
        assert data["elements"][1]["type"] == "textbox"
        assert data["elements"][2]["type"] == "placeholder"

    def test_deserialize_sorts_by_zindex(self):
        """Test that deserialization sorts by z_index for backward compatibility"""
        layout = PageLayout()

        # Create data with z_index values out of order
        data = {
            "size": (210, 297),
            "base_width": 210,
            "is_facing_page": False,
            "background_color": (1.0, 1.0, 1.0),
            "elements": [
                {
                    "type": "image",
                    "position": (10, 10),
                    "size": (50, 50),
                    "rotation": 0,
                    "z_index": 2,
                    "image_path": "",
                    "crop_info": (0, 0, 1, 1),
                },
                {
                    "type": "textbox",
                    "position": (20, 20),
                    "size": (50, 50),
                    "rotation": 0,
                    "z_index": 0,
                    "text_content": "",
                    "font_settings": {},
                    "alignment": "left",
                },
                {
                    "type": "placeholder",
                    "position": (30, 30),
                    "size": (50, 50),
                    "rotation": 0,
                    "z_index": 1,
                    "placeholder_type": "image",
                    "default_content": "",
                },
            ],
        }

        layout.deserialize(data)

        # Elements should be sorted by z_index
        assert len(layout.elements) == 3
        assert isinstance(layout.elements[0], TextBoxData)  # z_index=0
        assert isinstance(layout.elements[1], PlaceholderData)  # z_index=1
        assert isinstance(layout.elements[2], ImageData)  # z_index=2

    def test_roundtrip_maintains_order(self):
        """Test that serialize/deserialize maintains element order"""
        layout1 = PageLayout()
        elem1 = ImageData(x=10, y=10, z_index=0)
        elem2 = TextBoxData(x=20, y=20, z_index=1)
        elem3 = PlaceholderData(x=30, y=30, z_index=2)

        layout1.add_element(elem1)
        layout1.add_element(elem2)
        layout1.add_element(elem3)

        # Serialize and deserialize
        data = layout1.serialize()
        layout2 = PageLayout()
        layout2.deserialize(data)

        # Order should be maintained
        assert len(layout2.elements) == 3
        assert isinstance(layout2.elements[0], ImageData)
        assert isinstance(layout2.elements[1], TextBoxData)
        assert isinstance(layout2.elements[2], PlaceholderData)


class TestZOrderEdgeCases:
    """Tests for z-order edge cases"""

    def test_single_element(self):
        """Test operations with single element"""
        layout = PageLayout()
        elem = ImageData(x=10, y=10)
        layout.add_element(elem)

        # Try to move forward (should stay at index 0)
        cmd = ChangeZOrderCommand(layout, elem, old_index=0, new_index=0)
        cmd.execute()

        assert layout.elements.index(elem) == 0

    def test_empty_list(self):
        """Test operations with empty list"""
        layout = PageLayout()
        assert len(layout.elements) == 0

    def test_move_to_same_position(self):
        """Test moving element to its current position"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)

        layout.add_element(elem1)
        layout.add_element(elem2)

        # Move to same position
        cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=0)
        cmd.execute()

        assert layout.elements.index(elem1) == 0
        assert layout.elements.index(elem2) == 1

    def test_swap_adjacent_elements(self):
        """Test swapping two adjacent elements"""
        layout = PageLayout()
        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)

        layout.add_element(elem1)
        layout.add_element(elem2)

        # Swap by moving elem1 forward
        elements = layout.elements
        index1 = elements.index(elem1)
        index2 = elements.index(elem2)
        elements[index1], elements[index2] = elements[index2], elements[index1]

        assert layout.elements[0] is elem2
        assert layout.elements[1] is elem1

    def test_multiple_zorder_changes(self):
        """Test multiple z-order changes in sequence"""
        layout = PageLayout()
        history = CommandHistory()

        elem1 = ImageData(x=10, y=10)
        elem2 = TextBoxData(x=20, y=20)
        elem3 = PlaceholderData(x=30, y=30)

        layout.add_element(elem1)
        layout.add_element(elem2)
        layout.add_element(elem3)

        # Move elem1 to front
        cmd1 = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
        history.execute(cmd1)
        assert layout.elements.index(elem1) == 2

        # Move elem2 to front
        cmd2 = ChangeZOrderCommand(layout, elem2, old_index=0, new_index=2)
        history.execute(cmd2)
        assert layout.elements.index(elem2) == 2

        # Undo both
        history.undo()
        assert layout.elements.index(elem2) == 0

        history.undo()
        assert layout.elements.index(elem1) == 0


class TestZOrderCommandSerialization:
    """Tests for ChangeZOrderCommand serialization"""

    def test_serialize_command(self):
        """Test serializing a ChangeZOrderCommand"""
        layout = PageLayout()
        elem = ImageData(x=10, y=10)
        layout.add_element(elem)

        cmd = ChangeZOrderCommand(layout, elem, old_index=0, new_index=1)

        data = cmd.serialize()

        assert data["type"] == "change_zorder"
        assert data["old_index"] == 0
        assert data["new_index"] == 1
        assert "element" in data

    def test_deserialize_command(self):
        """Test deserializing a ChangeZOrderCommand"""
        data = {
            "type": "change_zorder",
            "element": {
                "type": "image",
                "position": (10, 10),
                "size": (50, 50),
                "rotation": 0,
                "z_index": 0,
                "image_path": "",
                "crop_info": (0, 0, 1, 1),
            },
            "old_index": 0,
            "new_index": 1,
        }

        cmd = ChangeZOrderCommand.deserialize(data, None)

        assert isinstance(cmd, ChangeZOrderCommand)
        assert cmd.old_index == 0
        assert cmd.new_index == 1
        assert isinstance(cmd.element, ImageData)
