"""
Unit tests for PageSetupDialog with mocked Qt widgets

These tests mock Qt widgets to avoid dependencies on the display system
and test the dialog logic in isolation.
"""

import pytest
from unittest.mock import Mock, MagicMock, patch, call
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout


class TestPageSetupDialogWithMocks:
    """Test PageSetupDialog with fully mocked Qt widgets"""

    def test_dialog_stores_initialization_params(self):
        """Test dialog stores project and initial page index"""
        # We test that the dialog class properly stores init parameters
        # without actually creating Qt widgets
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        project = Project(name="Test")
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        project.pages = [page]

        # We can verify the class signature and that it would accept these params
        # This is a structural test rather than a full initialization test
        assert hasattr(PageSetupDialog, "__init__")

        # The actual widget creation tests are in test_page_setup_dialog.py
        # using qtbot which handles Qt properly

    def test_on_page_changed_logic_isolated(self):
        """Test _on_page_changed logic without Qt dependencies"""
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        # Setup project
        project = Project(name="Test")
        project.page_size_mm = (210, 297)

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

        # Mock the dialog instance
        with patch.object(PageSetupDialog, "__init__", lambda self, *args, **kwargs: None):
            dialog = PageSetupDialog(None, None, 0)

            # Manually set required attributes
            dialog.project = project
            dialog._cover_group = Mock()
            dialog.cover_checkbox = Mock()
            dialog.width_spinbox = Mock()
            dialog.height_spinbox = Mock()
            dialog.scope_non_manual = Mock()
            dialog.scope_all_pages = Mock()
            dialog.scope_page_only = Mock()

            # Mock the update spine info method
            dialog._update_spine_info = Mock()

            # Test with first page (index 0)
            dialog._on_page_changed(0)

            # Verify cover group was made visible (first page)
            dialog._cover_group.setVisible.assert_called_with(True)
            # Verify cover checkbox was updated
            dialog.cover_checkbox.setChecked.assert_called_once()
            # Verify spine info was updated
            dialog._update_spine_info.assert_called_once()

            # Reset mocks
            dialog._cover_group.reset_mock()
            dialog._update_spine_info.reset_mock()

            # Test with second page (index 1)
            dialog._on_page_changed(1)

            # Verify cover group was hidden (not first page)
            dialog._cover_group.setVisible.assert_called_with(False)
            # Verify spine info was NOT updated (not first page)
            dialog._update_spine_info.assert_not_called()

    def test_on_page_changed_invalid_indices(self):
        """Test _on_page_changed handles invalid indices"""
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        project = Project(name="Test")
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        project.pages = [page]

        with patch.object(PageSetupDialog, "__init__", lambda self, *args, **kwargs: None):
            dialog = PageSetupDialog(None, None, 0)
            dialog.project = project
            dialog._cover_group = Mock()

            # Test negative index - should return early
            dialog._on_page_changed(-1)
            dialog._cover_group.setVisible.assert_not_called()

            # Test out of bounds index - should return early
            dialog._on_page_changed(999)
            dialog._cover_group.setVisible.assert_not_called()

    def test_update_spine_info_calculation(self):
        """Test spine info calculation logic"""
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        project = Project(name="Test")
        project.page_size_mm = (210, 297)
        project.paper_thickness_mm = 0.1
        project.cover_bleed_mm = 3.0

        # Create 3 content pages (not covers)
        for i in range(3):
            page = Page(layout=PageLayout(width=210, height=297), page_number=i + 1)
            page.is_cover = False
            project.pages.append(page)

        with patch.object(PageSetupDialog, "__init__", lambda self, *args, **kwargs: None):
            dialog = PageSetupDialog(None, None, 0)
            dialog.project = project
            dialog.cover_checkbox = Mock()
            dialog.thickness_spinbox = Mock()
            dialog.bleed_spinbox = Mock()
            dialog.spine_info_label = Mock()

            # Test when cover is enabled
            dialog.cover_checkbox.isChecked.return_value = True
            dialog.thickness_spinbox.value.return_value = 0.1
            dialog.bleed_spinbox.value.return_value = 3.0

            dialog._update_spine_info()

            # Verify spine info was set (not empty)
            assert dialog.spine_info_label.setText.called
            call_args = dialog.spine_info_label.setText.call_args[0][0]
            assert "Cover Layout" in call_args
            assert "Spine" in call_args
            assert "Front" in call_args

            # Reset
            dialog.spine_info_label.reset_mock()

            # Test when cover is disabled
            dialog.cover_checkbox.isChecked.return_value = False
            dialog._update_spine_info()

            # Verify spine info was cleared
            dialog.spine_info_label.setText.assert_called_once_with("")

    def test_get_values_data_extraction(self):
        """Test get_values extracts all data correctly"""
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        project = Project(name="Test")
        project.page_size_mm = (210, 297)

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

        with patch.object(PageSetupDialog, "__init__", lambda self, *args, **kwargs: None):
            dialog = PageSetupDialog(None, None, 0)
            dialog.project = project

            # Mock all input widgets
            dialog.page_combo = Mock()
            dialog.page_combo.currentData.return_value = 0

            dialog.cover_checkbox = Mock()
            dialog.cover_checkbox.isChecked.return_value = True

            dialog.thickness_spinbox = Mock()
            dialog.thickness_spinbox.value.return_value = 0.15

            dialog.bleed_spinbox = Mock()
            dialog.bleed_spinbox.value.return_value = 5.0

            dialog.width_spinbox = Mock()
            dialog.width_spinbox.value.return_value = 200.0

            dialog.height_spinbox = Mock()
            dialog.height_spinbox.value.return_value = 280.0

            dialog.working_dpi_spinbox = Mock()
            dialog.working_dpi_spinbox.value.return_value = 150

            dialog.export_dpi_spinbox = Mock()
            dialog.export_dpi_spinbox.value.return_value = 600

            dialog._apply_scope_group = Mock()
            dialog._apply_scope_group.checkedId.return_value = 2

            # Get values
            values = dialog.get_values()

            # Verify all values were extracted
            assert values["selected_index"] == 0
            assert values["selected_page"] == page
            assert values["is_cover"] is True
            assert values["paper_thickness_mm"] == 0.15
            assert values["cover_bleed_mm"] == 5.0
            assert values["width_mm"] == 200.0
            assert values["height_mm"] == 280.0
            assert values["working_dpi"] == 150
            assert values["export_dpi"] == 600
            assert values["apply_scope"] == 2

    def test_cover_page_width_display(self):
        """Test cover page shows full width, not base width"""
        from pyPhotoAlbum.dialogs.page_setup_dialog import PageSetupDialog

        project = Project(name="Test")
        project.page_size_mm = (210, 297)

        # Create cover page with special width
        page = Page(layout=PageLayout(width=500, height=297), page_number=1)
        page.is_cover = True
        project.pages = [page]

        with patch.object(PageSetupDialog, "__init__", lambda self, *args, **kwargs: None):
            dialog = PageSetupDialog(None, None, 0)
            dialog.project = project
            dialog._cover_group = Mock()
            dialog.cover_checkbox = Mock()
            dialog.width_spinbox = Mock()
            dialog.height_spinbox = Mock()
            dialog.scope_non_manual = Mock()
            dialog.scope_all_pages = Mock()
            dialog.scope_page_only = Mock()
            dialog._update_spine_info = Mock()

            # Call _on_page_changed for cover page
            dialog._on_page_changed(0)

            # Verify width was set to full cover width (500), not base width
            dialog.width_spinbox.setValue.assert_called()
            width_call = dialog.width_spinbox.setValue.call_args[0][0]
            assert width_call == 500

            # Verify widgets were disabled for cover
            dialog.width_spinbox.setEnabled.assert_called_with(False)
            dialog.height_spinbox.setEnabled.assert_called_with(False)
            dialog.scope_non_manual.setEnabled.assert_called_with(False)
            dialog.scope_all_pages.setEnabled.assert_called_with(False)

    # Note: Additional widget state tests are covered in test_page_setup_dialog.py
    # using qtbot which properly handles Qt widget initialization


class TestDialogMixinMocked:
    """Test DialogMixin with mocked dialogs"""

    def test_create_dialog_flow(self):
        """Test create_dialog method flow"""
        from pyPhotoAlbum.mixins.dialog_mixin import DialogMixin

        class TestWindow(DialogMixin):
            pass

        window = TestWindow()

        # Mock dialog class
        mock_dialog_instance = Mock()
        mock_dialog_instance.exec.return_value = 1  # Accepted
        mock_dialog_instance.get_values.return_value = {"key": "value"}

        mock_dialog_class = Mock(return_value=mock_dialog_instance)

        # Call create_dialog
        result = window.create_dialog(mock_dialog_class, title="Test Title", extra_param="test")

        # Verify dialog was created with correct params
        mock_dialog_class.assert_called_once_with(parent=window, extra_param="test")

        # Verify title was set
        mock_dialog_instance.setWindowTitle.assert_called_once_with("Test Title")

        # Verify dialog was executed
        mock_dialog_instance.exec.assert_called_once()

        # Verify get_values was called
        mock_dialog_instance.get_values.assert_called_once()

        # Verify result
        assert result == {"key": "value"}

    def test_show_dialog_with_callback_flow(self):
        """Test show_dialog method with callback"""
        from pyPhotoAlbum.mixins.dialog_mixin import DialogMixin

        class TestWindow(DialogMixin):
            pass

        window = TestWindow()

        # Mock dialog
        mock_dialog_instance = Mock()
        mock_dialog_instance.exec.return_value = 1  # Accepted
        mock_dialog_instance.get_values.return_value = {"data": "test"}

        mock_dialog_class = Mock(return_value=mock_dialog_instance)

        # Mock callback
        callback = Mock()

        # Call show_dialog
        result = window.show_dialog(mock_dialog_class, on_accept=callback, param="value")

        # Verify callback was called with dialog values
        callback.assert_called_once_with({"data": "test"})

        # Verify result
        assert result is True

    def test_show_dialog_rejected_no_callback(self):
        """Test show_dialog when dialog is rejected"""
        from pyPhotoAlbum.mixins.dialog_mixin import DialogMixin

        class TestWindow(DialogMixin):
            pass

        window = TestWindow()

        # Mock rejected dialog
        mock_dialog_instance = Mock()
        mock_dialog_instance.exec.return_value = 0  # Rejected

        mock_dialog_class = Mock(return_value=mock_dialog_instance)
        callback = Mock()

        # Call show_dialog
        result = window.show_dialog(mock_dialog_class, on_accept=callback)

        # Verify callback was NOT called
        callback.assert_not_called()

        # Verify result
        assert result is False


class TestDialogActionDecoratorMocked:
    """Test @dialog_action decorator with mocks"""

    def test_decorator_creates_and_shows_dialog(self):
        """Test decorator creates dialog and passes values to function"""
        from pyPhotoAlbum.decorators import dialog_action
        from PyQt6.QtWidgets import QDialog

        # Mock dialog instance
        mock_dialog = Mock()
        mock_dialog.exec.return_value = QDialog.DialogCode.Accepted  # Accepted
        mock_dialog.get_values.return_value = {"test": "data"}

        # Mock dialog class
        mock_dialog_cls = Mock(return_value=mock_dialog)

        # Create decorated function
        @dialog_action(dialog_class=mock_dialog_cls, requires_pages=True)
        def test_function(self, values):
            return values["test"]

        # Mock instance with required attributes
        instance = Mock()
        instance.project = Mock()
        instance.project.pages = [Mock()]  # Has pages
        instance._get_most_visible_page_index = Mock(return_value=0)

        # Call decorated function
        result = test_function(instance)

        # Verify dialog was created
        mock_dialog_cls.assert_called_once()

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

        # Verify values were extracted
        mock_dialog.get_values.assert_called_once()

        # Verify original function received values
        assert result == "data"

    def test_decorator_returns_early_when_no_pages(self):
        """Test decorator returns early when pages required but not present"""
        from pyPhotoAlbum.decorators import dialog_action

        mock_dialog_cls = Mock()

        @dialog_action(dialog_class=mock_dialog_cls, requires_pages=True)
        def test_function(self, values):
            return "should not reach"

        # Mock instance with no pages
        instance = Mock()
        instance.project = Mock()
        instance.project.pages = []  # No pages

        # Call decorated function
        result = test_function(instance)

        # Verify dialog was NOT created
        mock_dialog_cls.assert_not_called()

        # Verify result is None
        assert result is None

    def test_decorator_works_without_pages_requirement(self):
        """Test decorator works when pages not required"""
        from pyPhotoAlbum.decorators import dialog_action

        mock_dialog = Mock()
        mock_dialog.exec.return_value = 1
        mock_dialog.get_values.return_value = {"key": "val"}

        mock_dialog_cls = Mock(return_value=mock_dialog)

        @dialog_action(dialog_class=mock_dialog_cls, requires_pages=False)
        def test_function(self, values):
            return values

        # Mock instance with no pages
        instance = Mock()
        instance.project = Mock()
        instance.project.pages = []  # No pages, but that's OK

        # Call decorated function
        result = test_function(instance)

        # Verify dialog WAS created (pages not required)
        mock_dialog_cls.assert_called_once()

        # Verify result
        assert result == {"key": "val"}


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
