"""
Tests for PageLayout and GridLayout classes
"""

import pytest
from pyPhotoAlbum.page_layout import PageLayout, GridLayout
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
from pyPhotoAlbum.snapping import SnappingSystem


class TestPageLayoutInitialization:
    """Test PageLayout initialization"""

    def test_initialization_default(self):
        """Test PageLayout with default values"""
        layout = PageLayout()
        assert layout.size == (210, 297)
        assert layout.base_width == 210
        assert layout.is_facing_page is False
        assert layout.elements == []
        assert layout.grid_layout is None
        assert layout.background_color == (1.0, 1.0, 1.0)
        assert isinstance(layout.snapping_system, SnappingSystem)
        assert layout.show_snap_lines is True

    def test_initialization_custom_size(self):
        """Test PageLayout with custom dimensions"""
        layout = PageLayout(width=150, height=200)
        assert layout.size == (150, 200)
        assert layout.base_width == 150
        assert layout.is_facing_page is False

    def test_initialization_facing_page(self):
        """Test PageLayout as facing page (double width)"""
        layout = PageLayout(width=210, height=297, is_facing_page=True)
        assert layout.size == (420, 297)  # Width doubled
        assert layout.base_width == 210
        assert layout.is_facing_page is True


class TestElementManagement:
    """Test element add/remove operations"""

    def test_add_element(self):
        """Test adding element to layout"""
        layout = PageLayout()
        elem = ImageData(image_path="test.jpg")

        layout.add_element(elem)

        assert len(layout.elements) == 1
        assert layout.elements[0] == elem

    def test_add_multiple_elements(self):
        """Test adding multiple elements"""
        layout = PageLayout()
        elem1 = ImageData(image_path="test1.jpg")
        elem2 = PlaceholderData()
        elem3 = TextBoxData(text_content="Hello")

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

        assert len(layout.elements) == 3
        assert layout.elements[0] == elem1
        assert layout.elements[1] == elem2
        assert layout.elements[2] == elem3

    def test_remove_element(self):
        """Test removing element from layout"""
        layout = PageLayout()
        elem1 = ImageData(image_path="test1.jpg")
        elem2 = PlaceholderData()

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

        layout.remove_element(elem1)

        assert len(layout.elements) == 1
        assert layout.elements[0] == elem2

    def test_remove_element_not_in_list_raises_error(self):
        """Test removing non-existent element raises error"""
        layout = PageLayout()
        elem = ImageData(image_path="test.jpg")

        with pytest.raises(ValueError):
            layout.remove_element(elem)


class TestGridLayout:
    """Test setting grid layout"""

    def test_set_grid_layout(self):
        """Test setting a grid layout"""
        layout = PageLayout()
        grid = GridLayout(rows=2, columns=3)

        layout.set_grid_layout(grid)

        assert layout.grid_layout == grid
        assert layout.grid_layout.rows == 2
        assert layout.grid_layout.columns == 3


class TestPageLayoutSerialization:
    """Test PageLayout serialization"""

    def test_serialize_empty_layout(self):
        """Test serializing empty layout"""
        layout = PageLayout(width=210, height=297)
        data = layout.serialize()

        assert data["size"] == (210, 297)
        assert data["base_width"] == 210
        assert data["is_facing_page"] is False
        assert data["background_color"] == (1.0, 1.0, 1.0)
        assert data["elements"] == []
        assert data["grid_layout"] is None
        assert "snapping_system" in data
        assert data["show_snap_lines"] is True

    def test_serialize_with_elements(self):
        """Test serializing layout with elements"""
        layout = PageLayout()
        elem1 = ImageData(image_path="test1.jpg", x=10, y=20)
        elem2 = PlaceholderData(x=30, y=40)

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

        data = layout.serialize()

        assert len(data["elements"]) == 2
        assert data["elements"][0]["type"] == "image"
        assert data["elements"][1]["type"] == "placeholder"

    def test_serialize_with_grid_layout(self):
        """Test serializing layout with grid"""
        layout = PageLayout()
        grid = GridLayout(rows=3, columns=2, spacing=15.0)
        layout.set_grid_layout(grid)

        data = layout.serialize()

        assert data["grid_layout"] is not None
        assert data["grid_layout"]["rows"] == 3
        assert data["grid_layout"]["columns"] == 2
        assert data["grid_layout"]["spacing"] == 15.0

    def test_serialize_facing_page(self):
        """Test serializing facing page layout"""
        layout = PageLayout(width=210, height=297, is_facing_page=True)
        data = layout.serialize()

        assert data["size"] == (420, 297)
        assert data["base_width"] == 210
        assert data["is_facing_page"] is True


class TestPageLayoutDeserialization:
    """Test PageLayout deserialization"""

    def test_deserialize_basic_layout(self):
        """Test deserializing basic layout"""
        layout = PageLayout()
        data = {
            "size": (150, 200),
            "base_width": 150,
            "is_facing_page": False,
            "background_color": (0.9, 0.9, 0.9),
            "elements": [],
            "grid_layout": None,
            "snapping_system": {},
            "show_snap_lines": False,
        }

        layout.deserialize(data)

        assert layout.size == (150, 200)
        assert layout.base_width == 150
        assert layout.is_facing_page is False
        assert layout.background_color == (0.9, 0.9, 0.9)
        assert layout.show_snap_lines is False

    def test_deserialize_with_elements(self):
        """Test deserializing layout with elements"""
        layout = PageLayout()
        data = {
            "size": (210, 297),
            "elements": [
                {
                    "type": "image",
                    "image_path": "test1.jpg",
                    "position": (10, 20),
                    "size": (100, 100),
                    "rotation": 0,
                    "z_index": 0,
                    "crop_info": (0, 0, 1, 1),
                },
                {
                    "type": "placeholder",
                    "placeholder_type": "image",
                    "default_content": "",
                    "position": (30, 40),
                    "size": (80, 80),
                    "rotation": 0,
                    "z_index": 1,
                },
                {
                    "type": "textbox",
                    "text_content": "Hello",
                    "font_settings": {"family": "Arial", "size": 12, "color": (0, 0, 0)},
                    "alignment": "left",
                    "position": (50, 60),
                    "size": (120, 40),
                    "rotation": 0,
                    "z_index": 2,
                },
            ],
        }

        layout.deserialize(data)

        assert len(layout.elements) == 3
        assert isinstance(layout.elements[0], ImageData)
        assert isinstance(layout.elements[1], PlaceholderData)
        assert isinstance(layout.elements[2], TextBoxData)

    def test_deserialize_elements_sorted_by_z_index(self):
        """Test elements are sorted by z_index during deserialization"""
        layout = PageLayout()
        data = {
            "size": (210, 297),
            "elements": [
                {
                    "type": "image",
                    "image_path": "test1.jpg",
                    "position": (10, 20),
                    "size": (100, 100),
                    "rotation": 0,
                    "z_index": 5,  # Higher z_index
                    "crop_info": (0, 0, 1, 1),
                },
                {
                    "type": "placeholder",
                    "placeholder_type": "image",
                    "default_content": "",
                    "position": (30, 40),
                    "size": (80, 80),
                    "rotation": 0,
                    "z_index": 1,  # Lower z_index - should be first
                },
            ],
        }

        layout.deserialize(data)

        assert len(layout.elements) == 2
        # Lower z_index should come first in list
        assert layout.elements[0].z_index == 1
        assert layout.elements[1].z_index == 5

    def test_deserialize_with_grid_layout(self):
        """Test deserializing layout with grid"""
        layout = PageLayout()
        data = {
            "size": (210, 297),
            "elements": [],
            "grid_layout": {"rows": 2, "columns": 3, "spacing": 12.5, "merged_cells": [(0, 0), (1, 1)]},
        }

        layout.deserialize(data)

        assert layout.grid_layout is not None
        assert layout.grid_layout.rows == 2
        assert layout.grid_layout.columns == 3
        assert layout.grid_layout.spacing == 12.5
        assert layout.grid_layout.merged_cells == [(0, 0), (1, 1)]

    def test_deserialize_unknown_element_type_skipped(self):
        """Test that unknown element types are skipped"""
        layout = PageLayout()
        data = {
            "size": (210, 297),
            "elements": [
                {"type": "unknown_type", "position": (10, 20)},
                {
                    "type": "image",
                    "image_path": "test.jpg",
                    "position": (30, 40),
                    "size": (100, 100),
                    "rotation": 0,
                    "z_index": 0,
                    "crop_info": (0, 0, 1, 1),
                },
            ],
        }

        layout.deserialize(data)

        # Only valid element should be deserialized
        assert len(layout.elements) == 1
        assert isinstance(layout.elements[0], ImageData)

    def test_deserialize_with_defaults(self):
        """Test deserialization with missing fields uses defaults"""
        layout = PageLayout()
        data = {}

        layout.deserialize(data)

        assert layout.size == (210, 297)
        assert layout.background_color == (1.0, 1.0, 1.0)
        assert layout.show_snap_lines is True


class TestSerializationRoundtrip:
    """Test serialization/deserialization roundtrips"""

    def test_serialize_deserialize_roundtrip(self):
        """Test that serialize/deserialize are inverse operations"""
        original = PageLayout(width=200, height=280, is_facing_page=False)
        original.background_color = (0.95, 0.95, 0.95)
        original.show_snap_lines = False

        elem1 = ImageData(image_path="test.jpg", x=10, y=20, width=100, height=80)
        elem2 = PlaceholderData(x=30, y=40, width=50, height=50)
        original.add_element(elem1)
        original.add_element(elem2)

        grid = GridLayout(rows=2, columns=3, spacing=10.0)
        original.set_grid_layout(grid)

        # Serialize and deserialize
        data = original.serialize()
        restored = PageLayout()
        restored.deserialize(data)

        # Verify restoration
        assert restored.size == original.size
        assert restored.base_width == original.base_width
        assert restored.is_facing_page == original.is_facing_page
        assert restored.background_color == original.background_color
        assert restored.show_snap_lines == original.show_snap_lines
        assert len(restored.elements) == len(original.elements)
        assert restored.grid_layout is not None
        assert restored.grid_layout.rows == original.grid_layout.rows


class TestGridLayoutClass:
    """Test GridLayout class"""

    def test_initialization_default(self):
        """Test GridLayout initialization with defaults"""
        grid = GridLayout()
        assert grid.rows == 1
        assert grid.columns == 1
        assert grid.spacing == 10.0
        assert grid.merged_cells == []

    def test_initialization_custom(self):
        """Test GridLayout initialization with custom values"""
        grid = GridLayout(rows=3, columns=4, spacing=15.5)
        assert grid.rows == 3
        assert grid.columns == 4
        assert grid.spacing == 15.5

    def test_merge_cells(self):
        """Test merging cells in grid"""
        grid = GridLayout(rows=3, columns=3)

        grid.merge_cells(0, 0)
        grid.merge_cells(1, 1)

        assert len(grid.merged_cells) == 2
        assert (0, 0) in grid.merged_cells
        assert (1, 1) in grid.merged_cells

    def test_get_cell_position(self):
        """Test calculating cell position"""
        grid = GridLayout(rows=2, columns=2, spacing=10.0)

        # Cell (0, 0) - top left
        x, y = grid.get_cell_position(0, 0, page_width=800, page_height=600)
        assert x == 10.0  # spacing
        assert y == 10.0  # spacing

        # Cell (0, 1) - top right
        x, y = grid.get_cell_position(0, 1, page_width=800, page_height=600)
        # spacing + col * (cell_width + spacing)
        # cell_width = (800 - 30) / 2 = 385
        # x = 10 + 1 * (385 + 10) = 405
        assert x == 405.0

    def test_get_cell_size(self):
        """Test calculating cell size"""
        grid = GridLayout(rows=2, columns=3, spacing=10.0)

        width, height = grid.get_cell_size(page_width=900, page_height=600)

        # width = (900 - 10 * 4) / 3 = 860 / 3 ≈ 286.67
        # height = (600 - 10 * 3) / 2 = 570 / 2 = 285
        assert width == pytest.approx(286.666, rel=0.01)
        assert height == 285.0

    def test_grid_serialization(self):
        """Test GridLayout serialization"""
        grid = GridLayout(rows=3, columns=2, spacing=12.0)
        grid.merge_cells(0, 1)
        grid.merge_cells(2, 0)

        data = grid.serialize()

        assert data["rows"] == 3
        assert data["columns"] == 2
        assert data["spacing"] == 12.0
        assert data["merged_cells"] == [(0, 1), (2, 0)]

    def test_grid_deserialization(self):
        """Test GridLayout deserialization"""
        grid = GridLayout()
        data = {"rows": 4, "columns": 5, "spacing": 8.5, "merged_cells": [(1, 2), (3, 3)]}

        grid.deserialize(data)

        assert grid.rows == 4
        assert grid.columns == 5
        assert grid.spacing == 8.5
        assert grid.merged_cells == [(1, 2), (3, 3)]

    def test_grid_deserialization_with_defaults(self):
        """Test GridLayout deserialization with missing fields"""
        grid = GridLayout()
        data = {}

        grid.deserialize(data)

        assert grid.rows == 1
        assert grid.columns == 1
        assert grid.spacing == 10.0
        assert grid.merged_cells == []

    def test_grid_serialize_deserialize_roundtrip(self):
        """Test GridLayout serialize/deserialize roundtrip"""
        original = GridLayout(rows=3, columns=4, spacing=11.5)
        original.merge_cells(0, 0)
        original.merge_cells(1, 2)
        original.merge_cells(2, 3)

        data = original.serialize()
        restored = GridLayout()
        restored.deserialize(data)

        assert restored.rows == original.rows
        assert restored.columns == original.columns
        assert restored.spacing == original.spacing
        assert restored.merged_cells == original.merged_cells
