"""
Tests for AssetDropMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtCore import QMimeData, QUrl, QPoint
from PyQt6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from pyPhotoAlbum.mixins.asset_drop import AssetDropMixin
from pyPhotoAlbum.mixins.asset_path import AssetPathMixin
from pyPhotoAlbum.mixins.viewport import ViewportMixin
from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData


# Create test widget combining necessary mixins
class TestAssetDropWidget(AssetDropMixin, AssetPathMixin, PageNavigationMixin, ViewportMixin, QOpenGLWidget):
    """Test widget combining asset drop, asset path, page navigation, and viewport mixins"""

    def _get_element_at(self, x, y):
        """Mock implementation for testing"""
        # Will be overridden in tests that need it
        return None

    def _get_project_folder(self):
        """Override to access project via window mock"""
        main_window = self.window()
        if hasattr(main_window, "project") and main_window.project:
            return getattr(main_window.project, "folder_path", None)
        return None


class TestAssetDropInitialization:
    """Test AssetDropMixin initialization"""

    def test_widget_accepts_drops(self, qtbot):
        """Test that widget is configured to accept drops"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        # Should accept drops (set in GLWidget.__init__)
        # This is a property of the widget, not the mixin
        assert hasattr(widget, "acceptDrops")


class TestDragEnterEvent:
    """Test dragEnterEvent method"""

    def test_accepts_image_urls(self, qtbot):
        """Test accepts drag events with image file URLs"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        # Create mime data with image file
        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])

        # Create drag enter event
        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()

        widget.dragEnterEvent(event)

        # Should accept the event
        assert event.acceptProposedAction.called

    def test_accepts_png_files(self, qtbot):
        """Test accepts PNG files"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.png")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()

        widget.dragEnterEvent(event)
        assert event.acceptProposedAction.called

    def test_rejects_non_image_files(self, qtbot):
        """Test rejects non-image files"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/document.pdf")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()
        event.ignore = Mock()

        widget.dragEnterEvent(event)

        # Should not accept PDF files
        assert not event.acceptProposedAction.called

    def test_rejects_empty_mime_data(self, qtbot):
        """Test rejects events with no URLs"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        mime_data = QMimeData()
        # No URLs set

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()

        widget.dragEnterEvent(event)

        assert not event.acceptProposedAction.called


class TestDragMoveEvent:
    """Test dragMoveEvent method"""

    def test_accepts_drag_move_with_image(self, qtbot):
        """Test accepts drag move events with image files"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()

        widget.dragMoveEvent(event)

        assert event.acceptProposedAction.called


class TestDropEvent:
    """Test dropEvent method"""

    @patch("pyPhotoAlbum.mixins.asset_drop.AddElementCommand")
    def test_drop_creates_image_element(self, mock_cmd_class, qtbot):
        """Test dropping image file creates ImageData element"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        # Mock update method
        widget.update = Mock()

        # Setup project with page
        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 = [page]

        # Mock asset manager
        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image.jpg")

        # Mock history
        mock_window.project.history = Mock()

        # Mock page renderer
        mock_renderer = Mock()
        mock_renderer.is_point_in_page = Mock(return_value=True)
        mock_renderer.screen_to_page = Mock(return_value=(100, 100))

        # Mock _get_page_at to return tuple
        widget._get_page_at = Mock(return_value=(page, 0, mock_renderer))

        widget._page_renderers = [(mock_renderer, page)]
        widget.window = Mock(return_value=mock_window)

        # Create drop event
        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should have called asset manager
        assert mock_window.project.asset_manager.import_asset.called
        # Should have created command
        assert mock_cmd_class.called
        # Should have executed command
        assert mock_window.project.history.execute.called
        assert widget.update.called

    def test_drop_outside_page_does_nothing(self, qtbot):
        """Test dropping outside any page does nothing"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        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]

        # Mock renderer that returns False (not in page)
        mock_renderer = Mock()
        mock_renderer.is_point_in_page = Mock(return_value=False)

        widget._page_renderers = [(mock_renderer, page)]
        widget.window = Mock(return_value=mock_window)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(5000, 5000))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should not create any elements
        assert len(page.layout.elements) == 0

    def test_drop_updates_existing_placeholder(self, qtbot, tmp_path):
        """Test dropping on existing placeholder updates it with image"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        widget.update = Mock()

        # Create a real test image file
        test_image = tmp_path / "test_image.jpg"
        test_image.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100)  # Minimal JPEG header

        # Setup project with page containing placeholder
        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)

        from pyPhotoAlbum.models import PlaceholderData

        placeholder = PlaceholderData(x=100, y=100, width=200, height=150)
        page.layout.elements.append(placeholder)

        mock_window.project.pages = [page]

        # Mock renderer
        mock_renderer = Mock()
        mock_renderer.is_point_in_page = Mock(return_value=True)
        mock_renderer.screen_to_page = Mock(return_value=(150, 150))

        widget._page_renderers = [(mock_renderer, page)]
        widget.window = Mock(return_value=mock_window)

        # Mock element selection to return the placeholder
        widget._get_element_at = Mock(return_value=placeholder)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile(str(test_image))])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should replace placeholder with ImageData
        assert len(page.layout.elements) == 1
        assert isinstance(page.layout.elements[0], ImageData)
        # Image path should now be in assets folder (imported)
        assert page.layout.elements[0].image_path.startswith("assets/")

    @patch("pyPhotoAlbum.mixins.asset_drop.AddElementCommand")
    def test_drop_multiple_files(self, mock_cmd_class, qtbot):
        """Test dropping first image from multiple files"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        widget.update = Mock()

        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 = [page]

        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image1.jpg")

        mock_window.project.history = Mock()

        mock_renderer = Mock()
        mock_renderer.is_point_in_page = Mock(return_value=True)
        mock_renderer.screen_to_page = Mock(return_value=(100, 100))

        widget._get_page_at = Mock(return_value=(page, 0, mock_renderer))
        widget._page_renderers = [(mock_renderer, page)]
        widget.window = Mock(return_value=mock_window)

        # Create drop event with multiple files (only first is used)
        mime_data = QMimeData()
        mime_data.setUrls(
            [
                QUrl.fromLocalFile("/path/to/image1.jpg"),
                QUrl.fromLocalFile("/path/to/image2.png"),
                QUrl.fromLocalFile("/path/to/image3.jpg"),
            ]
        )

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Only first image should be processed
        assert mock_window.project.asset_manager.import_asset.call_count == 1

    def test_drop_no_project_does_nothing(self, qtbot):
        """Test dropping when no project loaded does nothing"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        widget.update = Mock()

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

        # Mock _get_element_at to return None (no element hit)
        widget._get_element_at = Mock(return_value=None)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        # Should not crash
        widget.dropEvent(event)

        # Should still accept event and call update
        assert event.acceptProposedAction.called
        assert widget.update.called

    def test_drop_on_existing_image_updates_it(self, qtbot, tmp_path):
        """Test dropping on existing ImageData updates its image path"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.update = Mock()

        # Create a real test image file
        test_image = tmp_path / "new_image.jpg"
        test_image.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100)

        # Setup project with page containing existing ImageData
        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)

        existing_image = ImageData(image_path="assets/old_image.jpg", x=100, y=100, width=200, height=150)
        page.layout.elements.append(existing_image)
        mock_window.project.pages = [page]

        # Mock asset manager
        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(return_value="assets/new_image.jpg")

        widget.window = Mock(return_value=mock_window)
        widget._get_element_at = Mock(return_value=existing_image)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile(str(test_image))])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should update existing ImageData's path
        assert existing_image.image_path == "assets/new_image.jpg"
        assert mock_window.project.asset_manager.import_asset.called

    def test_drop_with_asset_import_failure(self, qtbot, tmp_path):
        """Test dropping handles asset import errors gracefully"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.update = Mock()

        test_image = tmp_path / "test.jpg"
        test_image.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100)

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

        existing_image = ImageData(image_path="assets/old.jpg", x=100, y=100, width=200, height=150)
        page.layout.elements.append(existing_image)
        mock_window.project.pages = [page]

        # Mock asset manager to raise exception
        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(side_effect=Exception("Import failed"))

        widget.window = Mock(return_value=mock_window)
        widget._get_element_at = Mock(return_value=existing_image)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile(str(test_image))])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        # Should not crash, should handle error gracefully
        widget.dropEvent(event)

        # Original path should remain unchanged
        assert existing_image.image_path == "assets/old.jpg"
        assert event.acceptProposedAction.called

    def test_drop_with_corrupted_image_uses_defaults(self, qtbot, tmp_path):
        """Test dropping corrupted image uses default dimensions"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]
        widget.update = Mock()

        # Create a corrupted/invalid image file
        corrupted_image = tmp_path / "corrupted.jpg"
        corrupted_image.write_bytes(b"not a valid image")

        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 = [page]

        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(return_value="assets/corrupted.jpg")
        mock_window.project.history = Mock()

        mock_renderer = Mock()
        mock_renderer.is_point_in_page = Mock(return_value=True)
        mock_renderer.screen_to_page = Mock(return_value=(100, 100))

        widget._get_page_at = Mock(return_value=(page, 0, mock_renderer))
        widget._page_renderers = [(mock_renderer, page)]
        widget.window = Mock(return_value=mock_window)
        widget._get_element_at = Mock(return_value=None)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile(str(corrupted_image))])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should use default dimensions (200, 150) from _calculate_image_dimensions
        # Check that AddElementCommand was called with an ImageData
        from pyPhotoAlbum.commands import AddElementCommand

        with patch("pyPhotoAlbum.mixins.asset_drop.AddElementCommand") as mock_cmd:
            # Re-run to check the call
            widget.dropEvent(event)
            assert mock_cmd.called


class TestDragMoveEventEdgeCases:
    """Test edge cases for dragMoveEvent"""

    def test_drag_move_rejects_no_urls(self, qtbot):
        """Test dragMoveEvent rejects events without URLs"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)

        mime_data = QMimeData()
        # No URLs set

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.acceptProposedAction = Mock()
        event.ignore = Mock()

        widget.dragMoveEvent(event)

        # Should ignore the event
        assert event.ignore.called
        assert not event.acceptProposedAction.called


class TestExtractImagePathEdgeCases:
    """Test edge cases for _extract_image_path"""

    def test_drop_ignores_non_image_urls(self, qtbot):
        """Test dropping non-image files is ignored"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.update = Mock()

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile("/path/to/document.pdf"), QUrl.fromLocalFile("/path/to/file.txt")])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.ignore = Mock()
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should ignore event since no valid image files
        assert event.ignore.called
        assert not event.acceptProposedAction.called

    def test_drop_ignores_empty_urls(self, qtbot):
        """Test dropping with no URLs is ignored"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.update = Mock()

        mime_data = QMimeData()
        # No URLs at all

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.ignore = Mock()
        event.acceptProposedAction = Mock()

        widget.dropEvent(event)

        # Should ignore event
        assert event.ignore.called
        assert not event.acceptProposedAction.called


class TestPlaceholderReplacementEdgeCases:
    """Test edge cases for placeholder replacement"""

    def test_replace_placeholder_with_no_pages(self, qtbot, tmp_path):
        """Test replacing placeholder when project has no pages"""
        widget = TestAssetDropWidget()
        qtbot.addWidget(widget)
        widget.update = Mock()

        test_image = tmp_path / "test.jpg"
        test_image.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100)

        # Setup project WITHOUT pages
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        mock_window.project.pages = []  # Empty pages list

        from pyPhotoAlbum.models import PlaceholderData

        placeholder = PlaceholderData(x=100, y=100, width=200, height=150)

        mock_window.project.asset_manager = Mock()
        mock_window.project.asset_manager.import_asset = Mock(return_value="assets/test.jpg")

        widget.window = Mock(return_value=mock_window)
        widget._get_element_at = Mock(return_value=placeholder)

        mime_data = QMimeData()
        mime_data.setUrls([QUrl.fromLocalFile(str(test_image))])

        event = Mock()
        event.mimeData = Mock(return_value=mime_data)
        event.position = Mock(return_value=QPoint(150, 150))
        event.acceptProposedAction = Mock()

        # Should not crash when trying to replace placeholder
        widget.dropEvent(event)

        # Event should still be accepted
        assert event.acceptProposedAction.called
