"""
Integration tests for GLWidget - verifying mixin composition
"""

import pytest
from unittest.mock import Mock, patch
from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtGui import QMouseEvent
from pyPhotoAlbum.gl_widget import GLWidget
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData


class TestGLWidgetInitialization:
    """Test GLWidget initialization and mixin integration"""

    def test_gl_widget_initializes(self, qtbot):
        """Test GLWidget can be instantiated with all mixins"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Verify mixin state is initialized
        assert hasattr(widget, "zoom_level")
        assert hasattr(widget, "pan_offset")
        assert hasattr(widget, "selected_elements")
        assert hasattr(widget, "drag_start_pos")
        assert hasattr(widget, "is_dragging")
        assert hasattr(widget, "is_panning")
        assert hasattr(widget, "rotation_mode")

    def test_gl_widget_accepts_drops(self, qtbot):
        """Test GLWidget is configured to accept drops"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        assert widget.acceptDrops() is True

    def test_gl_widget_tracks_mouse(self, qtbot):
        """Test GLWidget has mouse tracking enabled"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        assert widget.hasMouseTracking() is True


class TestGLWidgetMixinIntegration:
    """Test that mixins work together correctly"""

    def test_viewport_and_rendering_integration(self, qtbot):
        """Test viewport state affects rendering"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Set zoom level
        initial_zoom = widget.zoom_level
        widget.zoom_level = 2.0

        assert widget.zoom_level == 2.0
        assert widget.zoom_level != initial_zoom

        # Pan offset
        initial_pan = widget.pan_offset.copy()
        widget.pan_offset[0] += 100
        widget.pan_offset[1] += 50

        assert widget.pan_offset != initial_pan

    def test_selection_and_manipulation_integration(self, qtbot):
        """Test element selection works with manipulation"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Create an element
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)

        # Select it
        widget.selected_elements.add(element)

        # Verify selection
        assert element in widget.selected_elements
        assert widget.selected_element == element

        # Clear selection
        widget.selected_elements.clear()
        assert len(widget.selected_elements) == 0
        assert widget.selected_element is None

    def test_mouse_interaction_with_selection(self, qtbot):
        """Test mouse events trigger selection changes"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        widget.update = Mock()

        # Mock element at position
        test_element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100)
        widget._get_element_at = Mock(return_value=test_element)
        widget._get_page_at = Mock(return_value=(None, -1, None))
        widget._check_ghost_page_click = Mock(return_value=False)

        # Create mouse press event
        event = QMouseEvent(
            QMouseEvent.Type.MouseButtonPress,
            QPointF(75, 75),
            Qt.MouseButton.LeftButton,
            Qt.MouseButton.LeftButton,
            Qt.KeyboardModifier.NoModifier,
        )

        widget.mousePressEvent(event)

        # Should select the element
        assert test_element in widget.selected_elements

    def test_undo_integration_with_operations(self, qtbot):
        """Test undo/redo integration with element operations"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Create element
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_elements.add(element)

        # Begin operation (should be tracked for undo)
        widget._begin_move(element)
        assert widget._interaction_state.element is not None
        assert widget._interaction_state.interaction_type == "move"
        assert widget._interaction_state.position == (100, 100)

        # End operation
        widget._end_interaction()
        # Interaction state should be cleared after operation
        assert widget._interaction_state.element is None
        assert widget._interaction_state.interaction_type is None


class TestGLWidgetKeyEvents:
    """Test keyboard event handling"""

    def test_escape_clears_selection(self, qtbot):
        """Test Escape key clears selection and rotation mode"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        widget.update = Mock()

        # Set up selection and rotation mode
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_elements.add(element)
        widget.rotation_mode = True

        # Create key press event for Escape
        from PyQt6.QtGui import QKeyEvent

        event = QKeyEvent(QKeyEvent.Type.KeyPress, Qt.Key.Key_Escape, Qt.KeyboardModifier.NoModifier)

        widget.keyPressEvent(event)

        # Should clear selection and rotation mode
        assert widget.selected_element is None
        assert widget.rotation_mode is False
        assert widget.update.called

    def test_tab_toggles_rotation_mode(self, qtbot):
        """Test Tab key toggles rotation mode when element is selected"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        widget.update = Mock()

        # Set up mock window for status message
        mock_window = Mock()
        mock_window.show_status = Mock()
        widget.window = Mock(return_value=mock_window)

        # Select an element
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_elements.add(element)

        # Initially not in rotation mode
        assert widget.rotation_mode is False

        # Create key press event for Tab
        from PyQt6.QtGui import QKeyEvent

        event = QKeyEvent(QKeyEvent.Type.KeyPress, Qt.Key.Key_Tab, Qt.KeyboardModifier.NoModifier)

        widget.keyPressEvent(event)

        # Should toggle rotation mode
        assert widget.rotation_mode is True
        assert widget.update.called

        # Press Tab again
        widget.keyPressEvent(event)

        # Should toggle back
        assert widget.rotation_mode is False

    def test_delete_key_requires_main_window(self, qtbot):
        """Test Delete key calls main window's delete method"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Set up mock window
        mock_window = Mock()
        mock_window.delete_selected_element = Mock()
        widget.window = Mock(return_value=mock_window)

        # Select an element
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_elements.add(element)

        # Create key press event for Delete
        from PyQt6.QtGui import QKeyEvent

        event = QKeyEvent(QKeyEvent.Type.KeyPress, Qt.Key.Key_Delete, Qt.KeyboardModifier.NoModifier)

        widget.keyPressEvent(event)

        # Should call main window's delete method
        assert mock_window.delete_selected_element.called


class TestGLWidgetWithProject:
    """Test GLWidget with a full project setup"""

    def test_gl_widget_with_project(self, qtbot):
        """Test GLWidget can work with a project and pages"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Create a mock main window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test Project")
        mock_window.project.working_dpi = 96

        # Add a page
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        mock_window.project.pages.append(page)

        # Add an element to the page
        element = ImageData(image_path="/test.jpg", x=100, y=100, width=200, height=150)
        page.layout.add_element(element)

        widget.window = Mock(return_value=mock_window)

        # Verify we can access project through widget
        main_window = widget.window()
        assert hasattr(main_window, "project")
        assert main_window.project.name == "Test Project"
        assert len(main_window.project.pages) == 1
        assert len(main_window.project.pages[0].layout.elements) == 1

    def test_fit_to_screen_zoom_calculation(self, qtbot):
        """Test fit-to-screen zoom calculation with project"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Create mock window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        mock_window.project.pages.append(page)

        widget.window = Mock(return_value=mock_window)

        # Mock widget dimensions
        widget.width = Mock(return_value=800)
        widget.height = Mock(return_value=600)

        # Calculate fit-to-screen zoom
        zoom = widget._calculate_fit_to_screen_zoom()

        # Should return a valid zoom level
        assert isinstance(zoom, float)
        assert zoom > 0

    def test_gl_widget_without_project(self, qtbot):
        """Test GLWidget handles missing project gracefully"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Create mock window without project
        mock_window = Mock()
        del mock_window.project
        widget.window = Mock(return_value=mock_window)

        # Should not crash when calculating zoom
        zoom = widget._calculate_fit_to_screen_zoom()
        assert zoom == 1.0


class TestGLWidgetOpenGL:
    """Test OpenGL-specific functionality"""

    def test_gl_widget_has_opengl_format(self, qtbot):
        """Test GLWidget has OpenGL format configured"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Should have a format
        format = widget.format()
        assert format is not None

    def test_gl_widget_update_behavior(self, qtbot):
        """Test GLWidget update behavior is configured"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Should have NoPartialUpdate set
        from PyQt6.QtOpenGLWidgets import QOpenGLWidget

        assert widget.updateBehavior() == QOpenGLWidget.UpdateBehavior.NoPartialUpdate


class TestGLWidgetStateManagement:
    """Test state management across mixins"""

    def test_rotation_mode_state(self, qtbot):
        """Test rotation mode state is properly managed"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Initial state
        assert widget.rotation_mode is False

        # Toggle rotation mode
        widget.rotation_mode = True
        assert widget.rotation_mode is True

        # Toggle back
        widget.rotation_mode = False
        assert widget.rotation_mode is False

    def test_drag_state_management(self, qtbot):
        """Test drag state is properly managed"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Initial state
        assert widget.is_dragging is False
        assert widget.drag_start_pos is None

        # Start drag
        widget.is_dragging = True
        widget.drag_start_pos = (100, 100)

        assert widget.is_dragging is True
        assert widget.drag_start_pos == (100, 100)

        # End drag
        widget.is_dragging = False
        widget.drag_start_pos = None

        assert widget.is_dragging is False
        assert widget.drag_start_pos is None

    def test_pan_state_management(self, qtbot):
        """Test pan state is properly managed"""
        widget = GLWidget()
        qtbot.addWidget(widget)

        # Initial state
        assert widget.is_panning is False

        # Start panning
        widget.is_panning = True
        widget.drag_start_pos = (200, 200)

        assert widget.is_panning is True

        # End panning
        widget.is_panning = False
        widget.drag_start_pos = None

        assert widget.is_panning is False
