"""
Tests for ElementOperationsMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch, mock_open
from PyQt6.QtWidgets import QMainWindow, QFileDialog
from pyPhotoAlbum.mixins.operations.element_ops import ElementOperationsMixin
from pyPhotoAlbum.mixins.asset_path import AssetPathMixin
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.commands import CommandHistory


# Create test window with ElementOperationsMixin
class TestElementWindow(ElementOperationsMixin, AssetPathMixin, QMainWindow):
    """Test window with element operations mixin"""

    def __init__(self):
        super().__init__()

        # Mock GL widget
        self.gl_widget = Mock()

        # Mock project
        self._project = Mock()
        self._project.history = CommandHistory()
        self._project.asset_manager = Mock()
        self._project.folder_path = "/tmp/test_project"

        # Track method calls
        self._update_view_called = False
        self._status_message = None
        self._error_message = None
        self._require_page_called = False
        self._current_page_index = 0

    @property
    def project(self):
        return self._project

    def require_page(self):
        """Track require_page calls"""
        self._require_page_called = True
        return self._current_page is not None if hasattr(self, "_current_page") else False

    def get_current_page(self):
        """Return mock current page"""
        if hasattr(self, "_current_page"):
            return self._current_page
        return None

    def get_current_page_index(self):
        """Return current page index"""
        return self._current_page_index

    def update_view(self):
        """Track update_view calls"""
        self._update_view_called = True

    def show_status(self, message, timeout=0):
        """Track status messages"""
        self._status_message = message

    def show_error(self, title, message):
        """Track error messages"""
        self._error_message = message


class TestAddImage:
    """Test add_image method"""

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    @patch("pyPhotoAlbum.mixins.operations.element_ops.get_image_dimensions")
    def test_add_image_success(self, mock_get_dims, mock_file_dialog, qtbot):
        """Test successfully adding an image"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        # Setup page
        layout = PageLayout()
        layout.size = (210, 297)  # A4 size
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Mock file dialog
        mock_file_dialog.return_value = ("/path/to/image.jpg", "Image Files (*.jpg)")

        # Mock get_image_dimensions (returns scaled dimensions)
        mock_get_dims.return_value = (300, 225)  # 800x600 scaled to max 300

        # Mock asset manager
        window.project.asset_manager.import_asset.return_value = "assets/image.jpg"

        window.add_image()

        # Should have called asset manager
        assert window.project.asset_manager.import_asset.called

        # Should have created command
        assert window.project.history.can_undo()

        # Should update view
        assert window._update_view_called
        assert "added image" in window._status_message.lower()

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    def test_add_image_cancelled(self, mock_file_dialog, qtbot):
        """Test cancelling image selection"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.size = (210, 297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Mock file dialog returning empty (cancelled)
        mock_file_dialog.return_value = ("", "")

        window.add_image()

        # Should not add anything
        assert not window._update_view_called

    def test_add_image_no_page(self, qtbot):
        """Test adding image with no current page"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.add_image()

        # Should check for page and return early
        assert window._require_page_called
        assert not window._update_view_called

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    @patch("pyPhotoAlbum.mixins.operations.element_ops.get_image_dimensions")
    def test_add_image_scales_large_image(self, mock_get_dims, mock_file_dialog, qtbot):
        """Test that large images are scaled down"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.size = (210, 297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        mock_file_dialog.return_value = ("/path/to/large.jpg", "Image Files (*.jpg)")

        # Mock get_image_dimensions returning scaled dimensions (3000x2000 -> 300x200)
        mock_get_dims.return_value = (300, 200)

        window.project.asset_manager.import_asset.return_value = "assets/large.jpg"

        window.add_image()

        # Image should be added (scaled down by get_image_dimensions)
        assert window._update_view_called

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    @patch("pyPhotoAlbum.mixins.operations.element_ops.get_image_dimensions")
    def test_add_image_fallback_dimensions(self, mock_get_dims, mock_file_dialog, qtbot):
        """Test fallback dimensions when get_image_dimensions returns None"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.size = (210, 297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        mock_file_dialog.return_value = ("/path/to/broken.jpg", "Image Files (*.jpg)")

        # Mock get_image_dimensions returning None (image unreadable)
        mock_get_dims.return_value = None

        window.project.asset_manager.import_asset.return_value = "assets/broken.jpg"

        window.add_image()

        # Should still add image with fallback dimensions (200x150)
        assert window._update_view_called
        assert window.project.history.can_undo()


class TestAddText:
    """Test add_text method"""

    def test_add_text_success(self, qtbot):
        """Test successfully adding a text box"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        # Setup page
        layout = PageLayout()
        layout.size = (210, 297)  # A4 size
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Mock layout.add_element
        layout.add_element = Mock()

        window.add_text()

        # Should have added text element
        assert layout.add_element.called
        args = layout.add_element.call_args[0]
        text_element = args[0]

        assert isinstance(text_element, TextBoxData)
        assert text_element.text_content == "New Text"
        assert text_element.size == (200, 50)

        # Should be centered
        expected_x = (210 - 200) / 2
        expected_y = (297 - 50) / 2
        assert text_element.position == (expected_x, expected_y)

        assert window._update_view_called

    def test_add_text_no_page(self, qtbot):
        """Test adding text with no current page"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.add_text()

        # Should check for page and return early
        assert window._require_page_called
        assert not window._update_view_called


class TestAddPlaceholder:
    """Test add_placeholder method"""

    def test_add_placeholder_success(self, qtbot):
        """Test successfully adding a placeholder"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        # Setup page
        layout = PageLayout()
        layout.size = (210, 297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Mock layout.add_element
        layout.add_element = Mock()

        window.add_placeholder()

        # Should have added placeholder element
        assert layout.add_element.called
        args = layout.add_element.call_args[0]
        placeholder_element = args[0]

        assert isinstance(placeholder_element, PlaceholderData)
        assert placeholder_element.placeholder_type == "image"
        assert placeholder_element.size == (200, 150)

        # Should be centered
        expected_x = (210 - 200) / 2
        expected_y = (297 - 150) / 2
        assert placeholder_element.position == (expected_x, expected_y)

        assert window._update_view_called

    def test_add_placeholder_no_page(self, qtbot):
        """Test adding placeholder with no current page"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        window._current_page = None

        window.add_placeholder()

        # Should check for page and return early
        assert window._require_page_called
        assert not window._update_view_called


class TestElementOperationsIntegration:
    """Test integration between element operations"""

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    @patch("pyPhotoAlbum.mixins.operations.element_ops.get_image_dimensions")
    def test_add_multiple_elements(self, mock_get_dims, mock_file_dialog, qtbot):
        """Test adding multiple different element types"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.size = (210, 297)
        layout.add_element = Mock()
        page = Mock()
        page.layout = layout
        window._current_page = page

        # Add text
        window.add_text()
        assert layout.add_element.call_count == 1

        # Add placeholder
        window.add_placeholder()
        assert layout.add_element.call_count == 2

        # Add image
        mock_file_dialog.return_value = ("/test.jpg", "Image Files")
        mock_get_dims.return_value = (100, 100)
        window.project.asset_manager.import_asset.return_value = "assets/test.jpg"

        window.add_image()

        # Should have added all three elements
        assert window._update_view_called

    @patch("pyPhotoAlbum.mixins.operations.element_ops.QFileDialog.getOpenFileName")
    @patch("pyPhotoAlbum.mixins.operations.element_ops.get_image_dimensions")
    def test_add_image_with_undo(self, mock_get_dims, mock_file_dialog, qtbot):
        """Test that adding image can be undone"""
        window = TestElementWindow()
        qtbot.addWidget(window)

        layout = PageLayout()
        layout.size = (210, 297)
        page = Mock()
        page.layout = layout
        window._current_page = page

        mock_file_dialog.return_value = ("/test.jpg", "Image Files")
        mock_get_dims.return_value = (200, 150)
        window.project.asset_manager.import_asset.return_value = "assets/test.jpg"

        # Should have no commands initially
        assert not window.project.history.can_undo()

        window.add_image()

        # Should have created a command
        assert window.project.history.can_undo()

        # Can undo
        initial_count = len(layout.elements)
        window.project.history.undo()
        assert len(layout.elements) < initial_count or layout.elements == []

        # Can redo
        window.project.history.redo()
        assert len(layout.elements) >= initial_count
