"""
Tests for PageOperationsMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.base import ApplicationStateMixin
from pyPhotoAlbum.mixins.operations.page_ops import PageOperationsMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout


class TestPageOpsWindow(PageOperationsMixin, ApplicationStateMixin, QMainWindow):
    """Test window with page operations mixin"""

    def __init__(self):
        super().__init__()
        self._gl_widget = Mock()
        self._gl_widget.current_page_index = 0
        self._gl_widget.zoom_level = 1.0
        self._gl_widget.pan_offset = [0, 0]
        self._gl_widget._page_renderers = []
        self._gl_widget.width = Mock(return_value=800)
        self._gl_widget.height = Mock(return_value=600)
        self._project = Project(name="Test")
        self._project.working_dpi = 96
        self._project.page_size_mm = (210, 297)
        self._update_view_called = False
        self._status_message = None
        self._status_bar = Mock()

    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):
        pass


class TestGetMostVisiblePageIndex:
    """Test _get_most_visible_page_index method"""

    def test_no_renderers_returns_current_index(self, qtbot):
        """Test returns current_page_index when no renderers"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        window.gl_widget.current_page_index = 3
        window.gl_widget._page_renderers = []

        result = window._get_most_visible_page_index()
        assert result == 3

    def test_single_page_returns_zero(self, qtbot):
        """Test with single page returns index 0"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create a single page
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        window.project.pages = [page]

        # Create mock renderer
        mock_renderer = Mock()
        mock_renderer.screen_y = 100
        window.gl_widget._page_renderers = [(mock_renderer, page)]

        result = window._get_most_visible_page_index()
        assert result == 0

    def test_multiple_pages_finds_closest_to_center(self, qtbot):
        """Test finds page closest to viewport center"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)
        window.gl_widget.height = Mock(return_value=600)  # Viewport center at y=300

        # Create three pages
        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
        page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
        window.project.pages = [page1, page2, page3]

        # Calculate page height in pixels: 297mm * 96dpi / 25.4 = ~1122px
        # At zoom 1.0, half page height = ~561px
        # Viewport center is at y=300

        # Create renderers with different screen_y positions
        # Page 1: screen_y = 50, center at 50 + 561 = 611, distance = |611 - 300| = 311
        # Page 2: screen_y = -300, center at -300 + 561 = 261, distance = |261 - 300| = 39  <- closest!
        # Page 3: screen_y = 800, center at 800 + 561 = 1361, distance = |1361 - 300| = 1061
        renderer1 = Mock()
        renderer1.screen_y = 50
        renderer2 = Mock()
        renderer2.screen_y = -300  # This will put page center near viewport center
        renderer3 = Mock()
        renderer3.screen_y = 800

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]

        result = window._get_most_visible_page_index()
        # Page 2 (index 1) should be closest to viewport center
        assert result == 1

    def test_handles_page_not_in_project_list(self, qtbot):
        """Test handles case where page is not in project.pages"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        orphan_page = Page(layout=PageLayout(width=210, height=297), page_number=99)
        window.project.pages = [page1]

        renderer1 = Mock()
        renderer1.screen_y = 100
        renderer_orphan = Mock()
        renderer_orphan.screen_y = 50  # Closer to center

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer_orphan, orphan_page)]  # Not in project.pages
        window.gl_widget.current_page_index = 0

        result = window._get_most_visible_page_index()
        # Should fallback to valid page (page1) or current_page_index
        assert result == 0


class TestToggleDoubleSpread:
    """Test toggle_double_spread method"""

    def test_toggle_spread_no_pages(self, qtbot):
        """Test returns early when no pages"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        window.project.pages = []

        window.toggle_double_spread()

        # Should return early without error
        assert not window._update_view_called

    def test_toggle_spread_enables_double_spread(self, qtbot):
        """Test enables double spread on single page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create single page
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.is_double_spread = False
        window.project.pages = [page]

        # Mock renderer
        mock_renderer = Mock()
        mock_renderer.screen_y = 100
        window.gl_widget._page_renderers = [(mock_renderer, page)]

        window.toggle_double_spread()

        assert page.is_double_spread is True
        assert page.manually_sized is True
        assert page.layout.is_facing_page is True
        assert page.layout.size[0] == 420  # 210 * 2
        assert page.layout.size[1] == 297
        assert window._update_view_called
        assert "enabled" in window._status_message

    def test_toggle_spread_disables_double_spread(self, qtbot):
        """Test disables double spread on double page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create double spread page
        page = Page(layout=PageLayout(width=420, height=297), page_number=1)
        page.is_double_spread = True
        page.layout.base_width = 210
        page.layout.is_facing_page = True
        window.project.pages = [page]

        mock_renderer = Mock()
        mock_renderer.screen_y = 100
        window.gl_widget._page_renderers = [(mock_renderer, page)]

        window.toggle_double_spread()

        assert page.is_double_spread is False
        assert page.layout.is_facing_page is False
        assert page.layout.size[0] == 210  # Back to single width
        assert page.layout.size[1] == 297
        assert window._update_view_called
        assert "disabled" in window._status_message

    def test_toggle_spread_uses_most_visible_page(self, qtbot):
        """Test toggles the most visible page, not always first page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)
        window.gl_widget.height = Mock(return_value=600)  # Viewport center at y=300

        # Create three pages
        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page1.is_double_spread = False
        page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
        page2.is_double_spread = False
        page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
        page3.is_double_spread = False
        window.project.pages = [page1, page2, page3]

        # Set up renderers so page 2 is most visible (see calculation above)
        # Page 2 center should be closest to viewport center at y=300
        renderer1 = Mock()
        renderer1.screen_y = 50
        renderer2 = Mock()
        renderer2.screen_y = -300  # This will put page 2 center near viewport center
        renderer3 = Mock()
        renderer3.screen_y = 800

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]

        window.toggle_double_spread()

        # Only page 2 should be toggled
        assert page1.is_double_spread is False
        assert page2.is_double_spread is True  # Toggled
        assert page3.is_double_spread is False
        assert window._update_view_called

    def test_toggle_spread_invalid_index_uses_zero(self, qtbot):
        """Test uses index 0 when calculated index is invalid"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.is_double_spread = False
        window.project.pages = [page]

        # Mock _get_most_visible_page_index to return invalid index
        window._get_most_visible_page_index = Mock(return_value=999)

        window.toggle_double_spread()

        # Should fallback to first page (index 0)
        assert page.is_double_spread is True
        assert window._update_view_called

    def test_toggle_spread_calculates_base_width(self, qtbot):
        """Test correctly calculates base_width from facing page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create page with is_facing_page=True (which doubles the width automatically)
        # PageLayout(width=210, is_facing_page=True) creates size=(420, 297) and base_width=210
        page = Page(layout=PageLayout(width=210, height=297, is_facing_page=True), page_number=1)
        page.is_double_spread = False  # Not marked as double spread yet
        window.project.pages = [page]

        mock_renderer = Mock()
        mock_renderer.screen_y = 100
        window.gl_widget._page_renderers = [(mock_renderer, page)]

        # Now toggle it on
        window.toggle_double_spread()

        # Should enable double spread
        assert page.is_double_spread is True
        # base_width should remain 210 (was already set correctly)
        assert page.layout.base_width == 210
        # Width should still be doubled
        assert page.layout.size[0] == 420  # base_width * 2
        assert page.layout.is_facing_page is True


class TestAddPage:
    """Test add_page method"""

    def test_add_page_to_empty_project(self, qtbot):
        """Test adds first page to empty project"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        window.project.pages = []

        window.add_page()

        assert len(window.project.pages) == 1
        assert window.project.pages[0].page_number == 1
        assert window.project.pages[0].layout.size == (210, 297)
        assert window.project.pages[0].manually_sized is False
        assert window._update_view_called

    def test_add_page_to_existing_pages(self, qtbot):
        """Test adds page after the current page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        window.project.pages = [page1]

        # Mock _get_most_visible_page_index to return page 1 (index 0)
        mock_renderer = Mock()
        mock_renderer.screen_y = 100
        window.gl_widget._page_renderers = [(mock_renderer, page1)]

        window.add_page()

        assert len(window.project.pages) == 2
        # New page should be inserted after page 1
        assert window.project.pages[0].page_number == 1
        assert window.project.pages[1].page_number == 2
        assert window._update_view_called

    def test_add_page_inserts_after_current_page(self, qtbot):
        """Test adds page after the currently visible page, not at the end"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create three pages
        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
        page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
        window.project.pages = [page1, page2, page3]

        # Mock _get_most_visible_page_index to return page 2 (index 1)
        window.gl_widget.height = Mock(return_value=600)
        renderer1 = Mock()
        renderer1.screen_y = 50
        renderer2 = Mock()
        renderer2.screen_y = -300  # Page 2 is most visible
        renderer3 = Mock()
        renderer3.screen_y = 800

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]

        window.add_page()

        assert len(window.project.pages) == 4
        # Verify pages are in correct order (physical order in list)
        # After inserting after page2 (index 1), the new page is at index 2
        assert window.project.pages[0] == page1
        assert window.project.pages[1] == page2
        # window.project.pages[2] is the new page
        assert window.project.pages[3] == page3

        # Page numbers should be renumbered sequentially
        assert window.project.pages[0].page_number == 1
        assert window.project.pages[1].page_number == 2
        assert window.project.pages[2].page_number == 3  # New page
        assert window.project.pages[3].page_number == 4  # Old page 3, renumbered
        assert window._update_view_called

    def test_add_page_with_double_spreads(self, qtbot):
        """Test page numbering with double spreads"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        # Create pages: single, double spread, single
        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page1.is_double_spread = False
        page2 = Page(layout=PageLayout(width=420, height=297), page_number=2)
        page2.is_double_spread = True
        page2.layout.is_facing_page = True
        page3 = Page(layout=PageLayout(width=210, height=297), page_number=4)
        page3.is_double_spread = False
        window.project.pages = [page1, page2, page3]

        # Mock renderers - page 2 is most visible
        window.gl_widget.height = Mock(return_value=600)
        renderer1 = Mock()
        renderer1.screen_y = 800
        renderer2 = Mock()
        renderer2.screen_y = -300  # Page 2 (double spread) is most visible
        renderer3 = Mock()
        renderer3.screen_y = 1500

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]

        window.add_page()

        assert len(window.project.pages) == 4
        # Page numbers should account for double spread:
        # page1: 1 (single)
        # page2: 2-3 (double spread, counts as 2 pages)
        # new_page: 4 (single)
        # page3: 5 (was 4, renumbered)
        assert window.project.pages[0].page_number == 1
        assert window.project.pages[1].page_number == 2  # Double spread starts at 2
        assert window.project.pages[2].page_number == 4  # New page after double spread
        assert window.project.pages[3].page_number == 5  # Old page3 renumbered


class TestRemovePage:
    """Test remove_page method"""

    def test_remove_last_page(self, qtbot):
        """Test removes last page"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
        window.project.pages = [page1, page2]

        window.remove_page()

        assert len(window.project.pages) == 1
        assert window.project.pages[0].page_number == 1
        assert window._update_view_called

    def test_cannot_remove_only_page(self, qtbot):
        """Test cannot remove when only one page exists"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        window.project.pages = [page1]

        window.remove_page()

        # Should still have one page
        assert len(window.project.pages) == 1
        assert not window._update_view_called

    def test_remove_page_renumbers_remaining(self, qtbot):
        """Test remaining pages are renumbered after removal"""
        window = TestPageOpsWindow()
        qtbot.addWidget(window)

        page1 = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page2 = Page(layout=PageLayout(width=210, height=297), page_number=2)
        page3 = Page(layout=PageLayout(width=210, height=297), page_number=3)
        window.project.pages = [page1, page2, page3]

        # Mock renderers to make page3 the most visible (so it gets removed)
        window.gl_widget.height = Mock(return_value=600)
        renderer1 = Mock()
        renderer1.screen_y = 800
        renderer2 = Mock()
        renderer2.screen_y = 600
        renderer3 = Mock()
        renderer3.screen_y = -300  # Page 3 is most visible

        window.gl_widget._page_renderers = [(renderer1, page1), (renderer2, page2), (renderer3, page3)]

        window.remove_page()

        assert len(window.project.pages) == 2
        assert window.project.pages[0].page_number == 1
        assert window.project.pages[1].page_number == 2
