"""
Tests for TemplateOperationsMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QMainWindow, QDialog
from pyPhotoAlbum.mixins.base import ApplicationStateMixin
from pyPhotoAlbum.mixins.operations.template_ops import TemplateOperationsMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData
from pyPhotoAlbum.commands import CommandHistory


class TestTemplateOpsWindow(TemplateOperationsMixin, ApplicationStateMixin, QMainWindow):
    """Test window with template 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.page_size_mm = (210, 297)
        self._project.working_dpi = 96
        self._project.history = CommandHistory()
        self._template_manager = Mock()
        self._update_view_called = False
        self._status_message = None
        self._info_title = None
        self._info_message = None
        self._warning_title = None
        self._warning_message = None
        self._error_title = None
        self._error_message = None

    def update_view(self):
        self._update_view_called = True

    def show_status(self, message, timeout=0):
        self._status_message = message

    def show_info(self, title, message):
        self._info_title = title
        self._info_message = message

    def show_warning(self, title, message):
        self._warning_title = title
        self._warning_message = message

    def show_error(self, title, message):
        self._error_title = title
        self._error_message = message


class TestSavePageAsTemplate:
    """Test save_page_as_template method"""

    def test_save_template_no_current_page(self, qtbot):
        """Test returns early when no current page"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.project.pages = []

        window.save_page_as_template()

        # Should return early without showing dialogs
        assert not window._update_view_called

    def test_save_template_empty_page(self, qtbot):
        """Test shows warning when page has no elements"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

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

        window.save_page_as_template()

        # Should show warning about empty page
        assert window._warning_title == "Empty Page"
        assert "Cannot save an empty page" in window._warning_message

    @patch("pyPhotoAlbum.mixins.operations.template_ops.QInputDialog.getText")
    def test_save_template_user_cancels_name(self, mock_get_text, qtbot):
        """Test returns when user cancels name dialog"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        # Create page with elements
        layout = PageLayout(width=210, height=297)
        layout.elements = [ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)]
        page = Page(layout=layout, page_number=1)
        window.project.pages = [page]

        window.template_manager.list_templates.return_value = []

        # Mock user canceling the name dialog
        mock_get_text.return_value = ("", False)

        window.save_page_as_template()

        # Should return without saving
        assert not window.template_manager.save_template.called

    @patch("pyPhotoAlbum.mixins.operations.template_ops.QInputDialog.getText")
    def test_save_template_user_cancels_description(self, mock_get_text, qtbot):
        """Test continues with empty description when user cancels description dialog"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        # Create page with elements
        layout = PageLayout(width=210, height=297)
        layout.elements = [ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)]
        page = Page(layout=layout, page_number=1)
        window.project.pages = [page]

        window.template_manager.list_templates.return_value = []

        # Mock template creation
        mock_template = Mock()
        window.template_manager.create_template_from_page.return_value = mock_template

        # Mock dialogs: name OK, description canceled
        mock_get_text.side_effect = [
            ("My Template", True),  # Name dialog
            ("Some description", False)  # Description dialog canceled
        ]

        window.save_page_as_template()

        # Should still save with empty description
        window.template_manager.create_template_from_page.assert_called_once()
        call_args = window.template_manager.create_template_from_page.call_args
        assert call_args[0][0] == page
        assert call_args[0][1] == "My Template"
        assert call_args[0][2] == ""  # Empty description

        window.template_manager.save_template.assert_called_once_with(mock_template)
        assert window._info_title == "Template Saved"

    @patch("pyPhotoAlbum.mixins.operations.template_ops.QInputDialog.getText")
    def test_save_template_success(self, mock_get_text, qtbot):
        """Test successfully saving a template"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        # Create page with elements
        layout = PageLayout(width=210, height=297)
        layout.elements = [ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)]
        page = Page(layout=layout, page_number=1)
        window.project.pages = [page]

        window.template_manager.list_templates.return_value = ["Template_1", "Template_2"]

        # Mock template creation
        mock_template = Mock()
        window.template_manager.create_template_from_page.return_value = mock_template

        # Mock dialogs
        mock_get_text.side_effect = [
            ("My Template", True),  # Name dialog
            ("A description", True)  # Description dialog
        ]

        window.save_page_as_template()

        # Verify template creation with correct parameters
        window.template_manager.create_template_from_page.assert_called_once_with(
            page, "My Template", "A description"
        )

        # Verify template was saved
        window.template_manager.save_template.assert_called_once_with(mock_template)

        # Verify success message
        assert window._info_title == "Template Saved"
        assert "My Template" in window._info_message

    @patch("pyPhotoAlbum.mixins.operations.template_ops.QInputDialog.getText")
    def test_save_template_default_name_numbering(self, mock_get_text, qtbot):
        """Test default template name includes correct numbering"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        # Create page with elements
        layout = PageLayout(width=210, height=297)
        layout.elements = [ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)]
        page = Page(layout=layout, page_number=1)
        window.project.pages = [page]

        # Mock 3 existing templates
        window.template_manager.list_templates.return_value = ["Template_1", "Template_2", "Template_3"]

        mock_get_text.return_value = ("", False)  # User cancels

        window.save_page_as_template()

        # Check that the default name offered was "Template_4"
        call_args = mock_get_text.call_args
        assert call_args[1]["text"] == "Template_4"

    @patch("pyPhotoAlbum.mixins.operations.template_ops.QInputDialog.getText")
    def test_save_template_exception_handling(self, mock_get_text, qtbot):
        """Test handles exceptions during template save"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        # Create page with elements
        layout = PageLayout(width=210, height=297)
        layout.elements = [ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)]
        page = Page(layout=layout, page_number=1)
        window.project.pages = [page]

        window.template_manager.list_templates.return_value = []

        # Mock dialogs
        mock_get_text.side_effect = [
            ("My Template", True),
            ("Description", True)
        ]

        # Mock template creation to raise exception
        window.template_manager.create_template_from_page.side_effect = Exception("Template error")

        window.save_page_as_template()

        # Should show error message
        assert window._error_title == "Error"
        assert "Failed to save template" in window._error_message
        assert "Template error" in window._error_message


class TestNewPageFromTemplate:
    """Test new_page_from_template method"""

    def test_new_page_no_templates(self, qtbot):
        """Test shows info when no templates available"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.template_manager.list_templates.return_value = []

        window.new_page_from_template()

        assert window._info_title == "No Templates"
        assert "No templates available" in window._info_message

    def test_new_page_user_cancels_dialog(self, qtbot):
        """Test returns when user cancels dialog"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.template_manager.list_templates.return_value = ["Template_1"]

        # Patch QDialog.exec to return rejected
        with patch.object(QDialog, "exec", return_value=QDialog.DialogCode.Rejected):
            window.new_page_from_template()

        # Should not create page
        assert not window.template_manager.load_template.called

    def _mock_dialog_exec(self, template_name="Template_1", scale_id=1, margin=2.5):
        """Helper to mock dialog exec with specific values"""
        original_exec = QDialog.exec

        def mock_exec(self):
            # Find the widgets that were added to the dialog
            combo = self.findChild(Mock.__class__.__bases__[0], "")  # This won't work, need different approach
            # Set values on widgets before returning
            return QDialog.DialogCode.Accepted

        return mock_exec

    def test_new_page_stretch_mode(self, qtbot):
        """Test creates page with stretch scaling mode"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.template_manager.list_templates.return_value = ["Template_1"]

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

        # Mock template
        mock_template = Mock()
        window.template_manager.load_template.return_value = mock_template

        # Mock new page
        mock_new_page = Mock()
        window.template_manager.create_page_from_template.return_value = mock_new_page

        # Capture the dialog and manipulate it
        captured_dialog = None
        original_exec = QDialog.exec

        def mock_exec(dialog_self):
            nonlocal captured_dialog
            captured_dialog = dialog_self
            # Find and set widget values
            for child in dialog_self.children():
                # Template combo - first one should be selected by default (index 0)
                if hasattr(child, 'currentText'):
                    pass  # Already set to first item
                # Button groups - stretch radio should already be checked (it's default)
                # Margin spinbox - already set to 2.5 (default)
            return QDialog.DialogCode.Accepted

        with patch.object(QDialog, "exec", mock_exec):
            window.new_page_from_template()

        # Verify template was loaded
        window.template_manager.load_template.assert_called_once_with("Template_1")

        # Verify page creation with correct parameters (defaults)
        window.template_manager.create_page_from_template.assert_called_once()
        call_args = window.template_manager.create_page_from_template.call_args
        assert call_args[1]["page_number"] == 2
        assert call_args[1]["target_size_mm"] == (210, 297)
        assert call_args[1]["scale_mode"] == "stretch"  # Default checked
        assert call_args[1]["margin_percent"] == 2.5  # Default value

        # Verify page was added to project
        assert len(window.project.pages) == 2
        assert window._update_view_called

    def test_new_page_exception_handling(self, qtbot):
        """Test handles exceptions during page creation"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.template_manager.list_templates.return_value = ["Template_1"]
        window.project.pages = []

        # Mock template loading to raise exception
        window.template_manager.load_template.side_effect = Exception("Template load error")

        with patch.object(QDialog, "exec", return_value=QDialog.DialogCode.Accepted):
            window.new_page_from_template()

        # Should show error message
        assert window._error_title == "Error"
        assert "Failed to create page from template" in window._error_message
        assert "Template load error" in window._error_message


class TestApplyTemplateToPage:
    """Test apply_template_to_page method"""

    def test_apply_template_no_current_page(self, qtbot):
        """Test returns early when no current page"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

        window.project.pages = []

        window.apply_template_to_page()

        # Should return early without showing dialogs
        assert not window._update_view_called

    def test_apply_template_no_templates(self, qtbot):
        """Test shows info when no templates available"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

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

        window.template_manager.list_templates.return_value = []

        window.apply_template_to_page()

        assert window._info_title == "No Templates"
        assert "No templates available" in window._info_message

    def test_apply_template_user_cancels_dialog(self, qtbot):
        """Test returns when user cancels dialog"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

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

        window.template_manager.list_templates.return_value = ["Template_1"]

        # Mock dialog rejection
        with patch.object(QDialog, "exec", return_value=QDialog.DialogCode.Rejected):
            window.apply_template_to_page()

        # Should not apply template
        assert not window.template_manager.load_template.called
        assert not window._update_view_called

    def test_apply_template_replace_mode(self, qtbot):
        """Test applies template in replace mode"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

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

        window.template_manager.list_templates.return_value = ["Template_1"]

        # Mock template
        mock_template = Mock()
        window.template_manager.load_template.return_value = mock_template

        # Use default values: replace mode (checked by default), stretch mode (checked by default), margin=2.5
        with patch.object(QDialog, "exec", return_value=QDialog.DialogCode.Accepted):
            window.apply_template_to_page()

        # Verify template application with default values
        window.template_manager.apply_template_to_page.assert_called_once()
        call_args = window.template_manager.apply_template_to_page.call_args
        assert call_args[0][0] == mock_template
        assert call_args[0][1] == page
        assert call_args[1]["mode"] == "replace"  # Default
        assert call_args[1]["scale_mode"] == "stretch"  # Default
        assert call_args[1]["margin_percent"] == 2.5  # Default

        assert window._update_view_called
        assert "Template_1" in window._status_message

    def test_apply_template_exception_handling(self, qtbot):
        """Test handles exceptions during template application"""
        window = TestTemplateOpsWindow()
        qtbot.addWidget(window)

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

        window.template_manager.list_templates.return_value = ["Template_1"]

        # Mock template loading to raise exception
        window.template_manager.load_template.side_effect = Exception("Template error")

        with patch.object(QDialog, "exec", return_value=QDialog.DialogCode.Accepted):
            window.apply_template_to_page()

        # Should show error message
        assert window._error_title == "Error"
        assert "Failed to apply template" in window._error_message
        assert "Template error" in window._error_message
