"""
Tests for ViewOperationsMixin
"""

import pytest
from unittest.mock import Mock, patch
from PyQt6.QtWidgets import QMainWindow, QDialog
from pyPhotoAlbum.mixins.operations.view_ops import ViewOperationsMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout


class TestViewWindow(ViewOperationsMixin, QMainWindow):
    """Test window with view operations mixin"""

    def __init__(self):
        super().__init__()
        self.gl_widget = Mock()
        self.gl_widget.zoom_level = 1.0
        self.gl_widget.current_page_index = 0
        self.gl_widget.width = Mock(return_value=800)
        self.gl_widget.height = Mock(return_value=600)
        self.project = Mock(spec=Project)
        self.project.working_dpi = 300
        # Add snapping attributes
        self.project.snap_to_grid = False
        self.project.snap_to_edges = True
        self.project.snap_to_guides = True
        self.project.show_snap_lines = True
        self.project.show_print_guides = False
        self.project.grid_size_mm = 10.0
        self.project.snap_threshold_mm = 5.0
        self._update_view_called = False
        self._status_message = None

    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


class TestZoomOperations:
    """Test zoom operations"""

    def test_zoom_in_success(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.gl_widget.zoom_level = 1.0

        window.zoom_in()

        assert window.gl_widget.zoom_level == 1.2
        assert window._update_view_called
        assert "120%" in window._status_message

    def test_zoom_in_max_limit(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.gl_widget.zoom_level = 4.5

        window.zoom_in()

        # 4.5 * 1.2 = 5.4, but clamped to 5.0
        assert window.gl_widget.zoom_level == 5.0
        assert window._update_view_called

    def test_zoom_out_success(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.gl_widget.zoom_level = 1.2

        window.zoom_out()

        assert window.gl_widget.zoom_level == 1.0
        assert window._update_view_called
        assert "100%" in window._status_message

    def test_zoom_out_min_limit(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.gl_widget.zoom_level = 0.11

        window.zoom_out()

        # 0.11 / 1.2 ≈ 0.092, but clamped to 0.1
        assert window.gl_widget.zoom_level == 0.1
        assert window._update_view_called

    def test_zoom_fit_success(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        # Setup page
        layout = PageLayout(width=210, height=297)  # A4 in mm
        page = Mock()
        page.layout = layout
        window.project.pages = [page]

        window.zoom_fit()

        # Should calculate zoom to fit page in widget
        assert window._update_view_called
        assert "zoom" in window._status_message.lower()
        # Zoom level should be set based on widget size
        assert 0.1 <= window.gl_widget.zoom_level <= 5.0

    def test_zoom_fit_no_pages(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.pages = []

        window.zoom_fit()

        # Should return early without error
        assert not window._update_view_called


class TestPrintGuidesToggle:
    """Test print guides toggle operation"""

    def test_toggle_print_guides_show(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.show_print_guides = False

        window.toggle_print_guides()

        assert window.project.show_print_guides is True
        assert "visible" in window._status_message.lower()
        assert window._update_view_called

    def test_toggle_print_guides_hide(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.show_print_guides = True

        window.toggle_print_guides()

        assert window.project.show_print_guides is False
        assert "hidden" in window._status_message.lower()
        assert window._update_view_called

    def test_toggle_print_guides_no_project(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project = None

        window.toggle_print_guides()

        assert not window._update_view_called


class TestSnappingToggles:
    """Test snapping toggle operations"""

    def test_toggle_grid_snap_enable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_grid = False

        window.toggle_grid_snap()

        assert window.project.snap_to_grid is True
        assert "enabled" in window._status_message.lower()
        assert window._update_view_called

    def test_toggle_grid_snap_disable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_grid = True

        window.toggle_grid_snap()

        assert window.project.snap_to_grid is False
        assert "disabled" in window._status_message.lower()
        assert window._update_view_called

    def test_toggle_grid_snap_no_page(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project = None

        window.toggle_grid_snap()

        assert not window._update_view_called

    def test_toggle_edge_snap_enable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_edges = False

        window.toggle_edge_snap()

        assert window.project.snap_to_edges is True
        assert "enabled" in window._status_message.lower()
        assert window._update_view_called

    def test_toggle_edge_snap_disable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_edges = True

        window.toggle_edge_snap()

        assert window.project.snap_to_edges is False
        assert "disabled" in window._status_message.lower()

    def test_toggle_guide_snap_enable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_guides = False

        window.toggle_guide_snap()

        assert window.project.snap_to_guides is True
        assert "enabled" in window._status_message.lower()

    def test_toggle_guide_snap_disable(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_guides = True

        window.toggle_guide_snap()

        assert window.project.snap_to_guides is False
        assert "disabled" in window._status_message.lower()

    def test_toggle_snap_lines_show(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.show_snap_lines = False

        window.toggle_snap_lines()

        assert window.project.show_snap_lines is True
        assert "visible" in window._status_message.lower()

    def test_toggle_snap_lines_hide(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.show_snap_lines = True

        window.toggle_snap_lines()

        assert window.project.show_snap_lines is False
        assert "hidden" in window._status_message.lower()


class TestGuideOperations:
    """Test guide add/clear operations"""

    def test_add_horizontal_guide(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        layout = PageLayout(width=210, height=297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        window.add_horizontal_guide()

        # Guide should be added at vertical center (297 / 2 = 148.5mm)
        assert "148.5" in window._status_message
        assert window._update_view_called

    def test_add_horizontal_guide_no_page(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.add_horizontal_guide()

        assert not window._update_view_called

    def test_add_vertical_guide(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        layout = PageLayout(width=210, height=297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        window.add_vertical_guide()

        # Guide should be added at horizontal center (210 / 2 = 105.0mm)
        assert "105.0" in window._status_message
        assert window._update_view_called

    def test_add_vertical_guide_no_page(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.add_vertical_guide()

        assert not window._update_view_called

    def test_clear_guides_with_guides(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        # Add some guides
        layout.snapping_system.add_guide(100, "vertical")
        layout.snapping_system.add_guide(150, "horizontal")
        page = Mock()
        page.layout = layout
        window._current_page = page

        window.clear_guides()

        assert len(layout.snapping_system.guides) == 0
        assert "2" in window._status_message  # Cleared 2 guides
        assert window._update_view_called

    def test_clear_guides_no_guides(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

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

        window.clear_guides()

        assert "0" in window._status_message
        assert window._update_view_called

    def test_clear_guides_no_page(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.clear_guides()

        assert not window._update_view_called


class TestGridSettingsDialog:
    """Test grid settings dialog"""

    def test_set_grid_size_no_page(self, qtbot):
        """Test set_grid_size with no project"""
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project = None

        window.set_grid_size()

        # Should return early without error
        assert not window._update_view_called

    def test_set_grid_size_with_page(self, qtbot):
        """Test set_grid_size creates dialog with current page"""
        window = TestViewWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.snapping_system.grid_size_mm = 10.0
        layout.snapping_system.snap_threshold_mm = 5.0
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Mock the full dialog workflow
        mock_dialog = Mock(spec=QDialog)
        mock_dialog.exec.return_value = QDialog.DialogCode.Rejected

        with patch("PyQt6.QtWidgets.QDialog", return_value=mock_dialog):
            window.set_grid_size()

            # Dialog should have been created and exec called
            mock_dialog.exec.assert_called_once()


class TestLayoutTabDelegation:
    """Test Layout tab methods delegate to main methods"""

    def test_layout_toggle_grid_snap_delegates(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_grid = False

        window.toggle_grid_snap()

        # Should toggle snap_to_grid
        assert window.project.snap_to_grid is True
        assert window._update_view_called

    def test_layout_toggle_edge_snap_delegates(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_edges = False

        window.toggle_edge_snap()

        assert window.project.snap_to_edges is True
        assert window._update_view_called

    def test_layout_toggle_guide_snap_delegates(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.snap_to_guides = False

        window.toggle_guide_snap()

        assert window.project.snap_to_guides is True
        assert window._update_view_called

    def test_layout_toggle_snap_lines_delegates(self, qtbot):
        window = TestViewWindow()
        qtbot.addWidget(window)

        window.project.show_snap_lines = False

        window.toggle_snap_lines()

        assert window.project.show_snap_lines is True
        assert window._update_view_called

    def test_layout_set_grid_size_delegates(self, qtbot):
        """Test grid size dialog works"""
        window = TestViewWindow()
        qtbot.addWidget(window)

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

        # Mock dialog to verify it runs
        mock_dialog = Mock(spec=QDialog)
        mock_dialog.exec.return_value = QDialog.DialogCode.Rejected

        with patch("PyQt6.QtWidgets.QDialog", return_value=mock_dialog):
            window.set_grid_size()

            # Verify dialog was shown
            mock_dialog.exec.assert_called_once()
