"""
Tests for ElementSelectionMixin
"""

import pytest
from unittest.mock import Mock
from PyQt6.QtWidgets import QApplication
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from pyPhotoAlbum.mixins.element_selection import ElementSelectionMixin
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
from pyPhotoAlbum.project import Page
from pyPhotoAlbum.page_layout import PageLayout


@pytest.fixture
def mock_page_renderer():
    """Create a mock PageRenderer"""
    renderer = Mock()
    renderer.screen_x = 50
    renderer.screen_y = 50
    renderer.zoom = 1.0
    renderer.dpi = 96

    # Mock coordinate conversion methods
    def page_to_screen(x, y):
        return (renderer.screen_x + x * renderer.zoom, renderer.screen_y + y * renderer.zoom)

    def screen_to_page(x, y):
        return ((x - renderer.screen_x) / renderer.zoom, (y - renderer.screen_y) / renderer.zoom)

    def is_point_in_page(x, y):
        # Simple bounds check (assume 210mm x 297mm page at 96 DPI)
        page_width_px = 210 * 96 / 25.4
        page_height_px = 297 * 96 / 25.4
        return (
            renderer.screen_x <= x <= renderer.screen_x + page_width_px * renderer.zoom
            and renderer.screen_y <= y <= renderer.screen_y + page_height_px * renderer.zoom
        )

    renderer.page_to_screen = page_to_screen
    renderer.screen_to_page = screen_to_page
    renderer.is_point_in_page = is_point_in_page

    return renderer


# Create a minimal test widget class
class TestSelectionWidget(ElementSelectionMixin, QOpenGLWidget):
    """Test widget combining ElementSelectionMixin with QOpenGLWidget"""

    def __init__(self):
        super().__init__()
        self._page_renderers = []


class TestElementSelectionInitialization:
    """Test ElementSelectionMixin initialization"""

    def test_initialization_creates_empty_selection_set(self, qtbot):
        """Test that mixin initializes with empty selection set"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        assert hasattr(widget, "selected_elements")
        assert isinstance(widget.selected_elements, set)
        assert len(widget.selected_elements) == 0

    def test_selected_element_property_returns_none_when_empty(self, qtbot):
        """Test that selected_element property returns None when no selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        assert widget.selected_element is None

    def test_selected_element_property_returns_first_when_populated(self, qtbot):
        """Test that selected_element property returns first element"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem1 = ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)
        elem2 = PlaceholderData(x=50, y=50, width=80, height=80)

        widget.selected_elements = {elem1, elem2}

        # Should return one of them (sets are unordered, but there should be exactly one)
        result = widget.selected_element
        assert result is not None
        assert result in {elem1, elem2}


class TestElementSelectionProperty:
    """Test selected_element property setter/getter"""

    def test_set_selected_element_to_single_element(self, qtbot):
        """Test setting selected_element with single element"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)
        widget.selected_element = elem

        assert len(widget.selected_elements) == 1
        assert elem in widget.selected_elements
        assert widget.selected_element == elem

    def test_set_selected_element_to_none_clears_selection(self, qtbot):
        """Test setting selected_element to None clears selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=0, y=0, width=100, height=100)
        widget.selected_element = elem

        widget.selected_element = None

        assert len(widget.selected_elements) == 0
        assert widget.selected_element is None

    def test_set_selected_element_replaces_previous(self, qtbot):
        """Test setting selected_element replaces previous selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem1 = ImageData(image_path="test1.jpg", x=0, y=0, width=100, height=100)
        elem2 = PlaceholderData(x=50, y=50, width=80, height=80)

        widget.selected_element = elem1
        assert widget.selected_element == elem1

        widget.selected_element = elem2
        assert widget.selected_element == elem2
        assert len(widget.selected_elements) == 1
        assert elem1 not in widget.selected_elements


class TestGetElementAt:
    """Test _get_element_at method"""

    def test_get_element_at_no_renderers(self, qtbot):
        """Test _get_element_at returns None when no renderers"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)
        widget._page_renderers = []

        result = widget._get_element_at(100, 100)
        assert result is None

    def test_get_element_at_outside_page(self, qtbot, mock_page_renderer):
        """Test _get_element_at returns None when click is outside page"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        widget._page_renderers = [(mock_page_renderer, page)]

        # Click way outside page bounds
        result = widget._get_element_at(5000, 5000)
        assert result is None

    def test_get_element_at_finds_element(self, qtbot, mock_page_renderer):
        """Test _get_element_at finds element at position"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        page.layout.add_element(elem)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click in middle of element (screen coords: 50 + 150 = 200, 50 + 175 = 225)
        result = widget._get_element_at(200, 225)

        assert result is not None
        assert result == elem
        assert hasattr(result, "_page_renderer")
        assert hasattr(result, "_parent_page")

    def test_get_element_at_finds_topmost_element(self, qtbot, mock_page_renderer):
        """Test _get_element_at returns topmost element when overlapping"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)

        # Add overlapping elements (higher z-index = on top)
        elem1 = ImageData(image_path="bottom.jpg", x=100, y=100, width=200, height=200, z_index=0)
        elem2 = PlaceholderData(x=150, y=150, width=100, height=100, z_index=1)

        page.layout.add_element(elem1)
        page.layout.add_element(elem2)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click in overlapping region (screen: 50 + 175 = 225, 50 + 175 = 225)
        result = widget._get_element_at(225, 225)

        # Should return elem2 (topmost - last in list)
        assert result == elem2

    def test_get_element_at_handles_empty_page(self, qtbot, mock_page_renderer):
        """Test _get_element_at returns None for empty page"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        widget._page_renderers = [(mock_page_renderer, page)]

        # Click inside page but no elements
        result = widget._get_element_at(200, 200)
        assert result is None

    def test_get_element_at_element_at_edge(self, qtbot, mock_page_renderer):
        """Test _get_element_at detects element at exact edge"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        page.layout.add_element(elem)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click exactly at element edge (screen: 50 + 100 = 150, 50 + 100 = 150)
        result = widget._get_element_at(150, 150)
        assert result == elem

        # Click just outside element (screen: 50 + 301 = 351, 50 + 251 = 301)
        result = widget._get_element_at(351, 301)
        assert result is None

    def test_get_element_at_rotated_element(self, qtbot, mock_page_renderer):
        """Test _get_element_at handles rotated elements correctly"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        # Create element rotated 45 degrees
        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem.rotation = 45
        page.layout.add_element(elem)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click at center of rotated element (should still be inside)
        # Center is at (100 + 200/2, 100 + 150/2) = (200, 175) in page coords
        # Screen coords: (50 + 200, 50 + 175) = (250, 225)
        result = widget._get_element_at(250, 225)
        assert result == elem
        assert hasattr(result, "_page_renderer")
        assert hasattr(result, "_parent_page")

    def test_get_element_at_rotated_element_outside(self, qtbot, mock_page_renderer):
        """Test _get_element_at correctly rejects clicks outside rotated element"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        # Create element rotated 90 degrees
        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem.rotation = 90
        page.layout.add_element(elem)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click far outside element
        result = widget._get_element_at(500, 500)
        assert result is None

    def test_get_element_at_element_off_page(self, qtbot, mock_page_renderer):
        """Test _get_element_at can find element that has moved off the page"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        # Create element positioned completely off the page (negative coordinates)
        # Page is at screen coords 50,50 with size 210mm x 297mm
        elem = ImageData(image_path="test.jpg", x=-200, y=-150, width=100, height=100)
        page.layout.add_element(elem)

        widget._page_renderers = [(mock_page_renderer, page)]

        # Click on the off-page element
        # Element is at page coords (-200, -150) with size (100, 100)
        # Screen coords: (50 + (-200), 50 + (-150)) = (-150, -100)
        # Click in middle of element: (-150 + 50, -100 + 50) = (-100, -50)
        result = widget._get_element_at(-100, -50)

        # Should be able to select the element even though it's off the page
        assert result is not None
        assert result == elem
        assert hasattr(result, "_page_renderer")
        assert hasattr(result, "_parent_page")


class TestGetResizeHandleAt:
    """Test _get_resize_handle_at method"""

    def test_get_resize_handle_no_selection(self, qtbot):
        """Test _get_resize_handle_at returns None when no selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        result = widget._get_resize_handle_at(100, 100)
        assert result is None

    def test_get_resize_handle_no_project(self, qtbot):
        """Test _get_resize_handle_at returns None when no project"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_element = elem

        # Mock window without project
        mock_window = Mock()
        mock_window.project = None
        widget.window = Mock(return_value=mock_window)

        result = widget._get_resize_handle_at(100, 100)
        assert result is None

    def test_get_resize_handle_no_renderer(self, qtbot):
        """Test _get_resize_handle_at returns None when element has no renderer"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        widget.selected_element = elem

        # Mock window with project
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        result = widget._get_resize_handle_at(100, 100)
        assert result is None

    def test_get_resize_handle_detects_nw_corner(self, qtbot, mock_page_renderer):
        """Test _get_resize_handle_at detects northwest corner"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem._page_renderer = mock_page_renderer
        widget.selected_element = elem

        # Mock window with project
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        # Click on NW handle (screen: 50 + 100 = 150, 50 + 100 = 150)
        result = widget._get_resize_handle_at(150, 150)
        assert result == "nw"

    def test_get_resize_handle_detects_all_corners(self, qtbot, mock_page_renderer):
        """Test _get_resize_handle_at detects all four corners"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem._page_renderer = mock_page_renderer
        widget.selected_element = elem

        # Mock window
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        # NW corner (screen: 50 + 100 = 150, 50 + 100 = 150)
        assert widget._get_resize_handle_at(150, 150) == "nw"

        # NE corner (screen: 50 + 300 = 350, 50 + 100 = 150)
        assert widget._get_resize_handle_at(350, 150) == "ne"

        # SW corner (screen: 50 + 100 = 150, 50 + 250 = 300)
        assert widget._get_resize_handle_at(150, 300) == "sw"

        # SE corner (screen: 50 + 300 = 350, 50 + 250 = 300)
        assert widget._get_resize_handle_at(350, 300) == "se"

    def test_get_resize_handle_returns_none_for_center(self, qtbot, mock_page_renderer):
        """Test _get_resize_handle_at returns None for element center"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem._page_renderer = mock_page_renderer
        widget.selected_element = elem

        # Mock window
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        # Click in center of element (screen: 50 + 200 = 250, 50 + 175 = 225)
        result = widget._get_resize_handle_at(250, 225)
        assert result is None

    def test_get_resize_handle_rotated_element(self, qtbot, mock_page_renderer):
        """Test _get_resize_handle_at handles rotated elements"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem.rotation = 45
        elem._page_renderer = mock_page_renderer
        widget.selected_element = elem

        # Mock window
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        # The rotation code should still detect handles - test NW handle
        # For rotated element, the handle positions are transformed
        result = widget._get_resize_handle_at(150, 150)
        # Should detect a handle (exact handle depends on rotation transform)
        assert result is None or result in ["nw", "ne", "sw", "se"]

    def test_get_resize_handle_rotated_90_degrees(self, qtbot, mock_page_renderer):
        """Test _get_resize_handle_at handles 90-degree rotated elements"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem = ImageData(image_path="test.jpg", x=100, y=100, width=200, height=150)
        elem.rotation = 90
        elem._page_renderer = mock_page_renderer
        widget.selected_element = elem

        # Mock window
        from pyPhotoAlbum.project import Project, Page
        from pyPhotoAlbum.page_layout import PageLayout

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

        # Test clicking at various positions - rotation code should handle them
        # Just verify the method runs without crashing
        result = widget._get_resize_handle_at(200, 200)
        assert result is None or result in ["nw", "ne", "sw", "se"]


class TestMultiSelect:
    """Test multi-selection functionality"""

    def test_multi_select_add_elements(self, qtbot):
        """Test adding multiple elements to selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem1 = ImageData(image_path="test1.jpg", x=0, y=0, width=100, height=100)
        elem2 = PlaceholderData(x=50, y=50, width=80, height=80)

        widget.selected_elements.add(elem1)
        widget.selected_elements.add(elem2)

        assert len(widget.selected_elements) == 2
        assert elem1 in widget.selected_elements
        assert elem2 in widget.selected_elements

    def test_multi_select_remove_element(self, qtbot):
        """Test removing element from multi-selection"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem1 = ImageData(image_path="test1.jpg", x=0, y=0, width=100, height=100)
        elem2 = PlaceholderData(x=50, y=50, width=80, height=80)

        widget.selected_elements = {elem1, elem2}
        widget.selected_elements.remove(elem1)

        assert len(widget.selected_elements) == 1
        assert elem2 in widget.selected_elements
        assert elem1 not in widget.selected_elements

    def test_multi_select_clear_all(self, qtbot):
        """Test clearing all selections"""
        widget = TestSelectionWidget()
        qtbot.addWidget(widget)

        elem1 = ImageData(image_path="test1.jpg", x=0, y=0, width=100, height=100)
        elem2 = PlaceholderData(x=50, y=50, width=80, height=80)

        widget.selected_elements = {elem1, elem2}
        widget.selected_elements.clear()

        assert len(widget.selected_elements) == 0
