"""
Tests for RenderingMixin
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from PyQt6.QtWidgets import QApplication
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtCore import Qt
from pyPhotoAlbum.mixins.rendering import RenderingMixin
from pyPhotoAlbum.mixins.viewport import ViewportMixin
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData


# Create a minimal test widget class that combines necessary mixins
class TestRenderingWidget(ViewportMixin, RenderingMixin, QOpenGLWidget):
    """Test widget combining rendering and viewport mixins with QOpenGLWidget"""

    def __init__(self):
        super().__init__()
        # Initialize attributes needed by rendering
        self.selected_elements = []
        self.rotation_mode = False

    def _get_page_positions(self):
        """Mock method to return page positions"""
        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return []

        positions = []
        y_offset = 50  # PAGE_MARGIN
        for page in main_window.project.pages:
            positions.append(("page", page, y_offset))
            # Calculate page height
            page_width_mm, page_height_mm = page.layout.size
            dpi = main_window.project.working_dpi
            page_height_px = page_height_mm * dpi / 25.4
            y_offset += page_height_px + 50  # PAGE_SPACING

        return positions


class TestRenderingInitialization:
    """Test rendering mixin setup"""

    def test_rendering_mixin_exists(self, qtbot):
        """Test that rendering mixin can be instantiated"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

        assert hasattr(widget, "paintGL")
        assert callable(widget.paintGL)
        assert hasattr(widget, "_draw_selection_handles")
        assert callable(widget._draw_selection_handles)


class TestPaintGL:
    """Test paintGL method"""

    def test_paintGL_no_project(self, qtbot):
        """Test paintGL with no project does nothing"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(800, 600)

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

        # Should not raise exception
        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                widget.paintGL()

    def test_paintGL_empty_project(self, qtbot):
        """Test paintGL with empty project does nothing"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(800, 600)

        # Mock window with empty project
        mock_window = Mock()
        mock_window.project = Project(name="Empty")
        mock_window.project.pages = []
        widget.window = Mock(return_value=mock_window)

        # Should not raise exception
        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                widget.paintGL()

    def test_paintGL_sets_initial_zoom(self, qtbot):
        """Test paintGL sets initial zoom on first render"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Mock window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        mock_window.update_scrollbars = Mock()

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

        widget.window = Mock(return_value=mock_window)

        # Ensure initial_zoom_set is False
        widget.initial_zoom_set = False

        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                with patch("pyPhotoAlbum.page_renderer.PageRenderer"):
                    widget.paintGL()

        # Should have set initial zoom
        assert widget.initial_zoom_set is True
        assert widget.zoom_level > 0
        assert widget.zoom_level <= 1.0

    def test_paintGL_renders_page(self, qtbot):
        """Test paintGL renders a page"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Mock window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        mock_window.update_scrollbars = Mock()

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

        widget.window = Mock(return_value=mock_window)
        widget.initial_zoom_set = True
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        # Mock PageRenderer
        mock_renderer = Mock()
        mock_renderer.begin_render = Mock()
        mock_renderer.end_render = Mock()

        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
                    widget.paintGL()

        # Should have created page renderers
        assert hasattr(widget, "_page_renderers")
        assert len(widget._page_renderers) == 1

        # Should have called renderer methods
        mock_renderer.begin_render.assert_called_once()
        mock_renderer.end_render.assert_called_once()

    def test_paintGL_renders_multiple_pages(self, qtbot):
        """Test paintGL renders multiple pages"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Mock window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        mock_window.update_scrollbars = Mock()

        # Create 3 A4 pages
        pages = [
            Page(layout=PageLayout(width=210, height=297), page_number=i)
            for i in range(1, 4)
        ]
        mock_window.project.pages = pages

        widget.window = Mock(return_value=mock_window)
        widget.initial_zoom_set = True
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        # Mock PageRenderer
        mock_renderer = Mock()
        mock_renderer.begin_render = Mock()
        mock_renderer.end_render = Mock()

        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
                    widget.paintGL()

        # Should have created page renderers for all pages
        assert hasattr(widget, "_page_renderers")
        assert len(widget._page_renderers) == 3

    def test_paintGL_updates_selected_elements_renderer(self, qtbot):
        """Test paintGL updates page renderer references for selected elements"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Mock window with project
        mock_window = Mock()
        mock_window.project = Project(name="Test")
        mock_window.project.working_dpi = 96
        mock_window.update_scrollbars = Mock()

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

        # Create a mock element
        mock_element = Mock()
        mock_element._parent_page = page
        widget.selected_elements = [mock_element]

        widget.window = Mock(return_value=mock_window)
        widget.initial_zoom_set = True
        widget.zoom_level = 1.0
        widget.pan_offset = [0, 0]

        # Mock PageRenderer
        mock_renderer = Mock()

        with patch("pyPhotoAlbum.mixins.rendering.glClear"):
            with patch("pyPhotoAlbum.mixins.rendering.glLoadIdentity"):
                with patch("pyPhotoAlbum.page_renderer.PageRenderer", return_value=mock_renderer):
                    with patch.object(widget, "_draw_selection_handles"):
                        widget.paintGL()

        # Element should have renderer reference
        assert hasattr(mock_element, "_page_renderer")
        assert mock_element._page_renderer == mock_renderer


class TestDrawSelectionHandles:
    """Test _draw_selection_handles method"""

    def test_draw_selection_handles_no_element(self, qtbot):
        """Test drawing handles with no element does nothing"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

        # Should not raise exception
        widget._draw_selection_handles(None)

    def test_draw_selection_handles_no_project(self, qtbot):
        """Test drawing handles with no project does nothing"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

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

        mock_element = Mock()
        mock_element._page_renderer = Mock()

        # Should not raise exception
        widget._draw_selection_handles(mock_element)

    def test_draw_selection_handles_no_renderer(self, qtbot):
        """Test drawing handles without renderer does nothing"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

        # 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 = [page]
        widget.window = Mock(return_value=mock_window)

        # Element without renderer
        mock_element = Mock(spec=[])  # No _page_renderer attribute

        # Should not raise exception
        widget._draw_selection_handles(mock_element)

    def test_draw_selection_handles_normal_mode(self, qtbot):
        """Test drawing handles in normal (non-rotation) mode"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.rotation_mode = False

        # 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 = [page]
        widget.window = Mock(return_value=mock_window)

        # Create element with renderer
        mock_element = Mock()
        mock_element.position = (100, 100)
        mock_element.size = (200, 150)

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(150, 150))
        mock_renderer.zoom = 1.0

        mock_element._page_renderer = mock_renderer

        # Mock OpenGL calls
        with patch("pyPhotoAlbum.mixins.rendering.glColor3f"):
            with patch("pyPhotoAlbum.mixins.rendering.glLineWidth"):
                with patch("pyPhotoAlbum.mixins.rendering.glBegin"):
                    with patch("pyPhotoAlbum.mixins.rendering.glVertex2f"):
                        with patch("pyPhotoAlbum.mixins.rendering.glEnd"):
                            widget._draw_selection_handles(mock_element)

        # Should have converted position
        mock_renderer.page_to_screen.assert_called_once_with(100, 100)

    def test_draw_selection_handles_rotation_mode(self, qtbot):
        """Test drawing handles in rotation mode"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.rotation_mode = True

        # 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 = [page]
        widget.window = Mock(return_value=mock_window)

        # Create element with renderer
        mock_element = Mock()
        mock_element.position = (100, 100)
        mock_element.size = (200, 150)

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(150, 150))
        mock_renderer.zoom = 1.0

        mock_element._page_renderer = mock_renderer

        # Mock OpenGL calls
        with patch("pyPhotoAlbum.mixins.rendering.glColor3f"):
            with patch("pyPhotoAlbum.mixins.rendering.glLineWidth"):
                with patch("pyPhotoAlbum.mixins.rendering.glBegin"):
                    with patch("pyPhotoAlbum.mixins.rendering.glVertex2f"):
                        with patch("pyPhotoAlbum.mixins.rendering.glEnd"):
                            widget._draw_selection_handles(mock_element)

        # Should have converted position
        mock_renderer.page_to_screen.assert_called_once_with(100, 100)


class TestRenderTextOverlays:
    """Test _render_text_overlays method"""

    def test_render_text_overlays_no_renderers(self, qtbot):
        """Test rendering text overlays with no page renderers"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

        # Should not raise exception
        widget._render_text_overlays()

    def test_render_text_overlays_empty_renderers(self, qtbot):
        """Test rendering text overlays with empty renderers list"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)

        widget._page_renderers = []

        # Should not raise exception
        widget._render_text_overlays()

    def test_render_text_overlays_no_text_elements(self, qtbot):
        """Test rendering text overlays with no text elements"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Mock page with no text elements
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = []

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Painter should have been created and ended
            mock_painter_class.assert_called_once_with(widget)
            mock_painter.end.assert_called_once()

    def test_render_text_overlays_with_text_element(self, qtbot):
        """Test rendering text overlays with text element"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create text element
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100,
            text_content="Test Text",
            font_settings={
                "family": "Arial",
                "size": 12,
                "color": (0, 0, 0)
            },
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Painter should have been used
            mock_painter_class.assert_called_once_with(widget)
            mock_painter.setFont.assert_called()
            mock_painter.setPen.assert_called()
            mock_painter.drawText.assert_called()
            mock_painter.end.assert_called_once()

    def test_render_text_overlays_with_rotated_text(self, qtbot):
        """Test rendering text overlays with rotated text"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create rotated text element
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100, rotation=45,
            text_content="Rotated Text",
            font_settings={
                "family": "Arial",
                "size": 14,
                "color": (0, 0, 0)
            },
            alignment="center"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Painter should have used save/restore for rotation
            mock_painter.save.assert_called()
            mock_painter.rotate.assert_called_with(45)
            mock_painter.restore.assert_called()

    def test_render_text_overlays_different_alignments(self, qtbot):
        """Test rendering text with different alignments"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        for alignment in ["left", "center", "right", "justify"]:
            # Create text element with alignment
            text_element = TextBoxData(
                x=50, y=50, width=200, height=100,
                text_content=f"{alignment} aligned",
                font_settings={
                    "family": "Arial",
                    "size": 12,
                    "color": (0, 0, 0)
                },
                alignment=alignment
            )

            # Mock page with text element
            page = Page(layout=PageLayout(width=210, height=297), page_number=1)
            page.layout.elements = [text_element]

            mock_renderer = Mock()
            mock_renderer.page_to_screen = Mock(return_value=(100, 100))
            mock_renderer.zoom = 1.0

            widget._page_renderers = [(mock_renderer, page)]

            # Mock QPainter
            with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
                mock_painter = Mock()
                mock_painter_class.return_value = mock_painter

                widget._render_text_overlays()

                # Should have drawn text
                mock_painter.drawText.assert_called()

    def test_render_text_overlays_normalized_color(self, qtbot):
        """Test rendering text with normalized color values (0-1 range)"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create text element with normalized colors
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100,
            text_content="Normalized Color",
            font_settings={
                "family": "Arial",
                "size": 12,
                "color": (0.5, 0.5, 0.5)  # Normalized (0-1 range)
            },
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Should have converted normalized colors to 0-255 range
            mock_painter.setPen.assert_called()

    def test_render_text_overlays_empty_text(self, qtbot):
        """Test rendering text element with no content"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create text element with no content
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100,
            text_content="",  # Empty
            font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)},
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Should not draw empty text
            mock_painter.drawText.assert_not_called()


class TestRenderGhostPage:
    """Test _render_ghost_page method"""

    def test_render_ghost_page(self, qtbot):
        """Test rendering a ghost page"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create mock ghost data
        ghost_data = Mock()
        ghost_data.page_size = (210, 297)
        ghost_data.render = Mock()
        ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))

        # Create mock renderer
        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0
        mock_renderer.begin_render = Mock()
        mock_renderer.end_render = Mock()

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_ghost_page(ghost_data, mock_renderer)

            # Should have called renderer methods
            mock_renderer.begin_render.assert_called_once()
            ghost_data.render.assert_called_once()
            mock_renderer.end_render.assert_called_once()

            # Should have drawn text overlay
            mock_painter.drawText.assert_called()
            # Check that "Click to Add Page" was drawn
            call_args = mock_painter.drawText.call_args
            assert "Click to Add Page" in str(call_args)

    def test_render_ghost_page_painter_cleanup(self, qtbot):
        """Test that ghost page rendering cleans up QPainter"""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create mock ghost data
        ghost_data = Mock()
        ghost_data.page_size = (210, 297)
        ghost_data.render = Mock()
        ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))

        # Create mock renderer
        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 1.0
        mock_renderer.begin_render = Mock()
        mock_renderer.end_render = Mock()

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_ghost_page(ghost_data, mock_renderer)

            # Painter should be ended (cleanup)
            mock_painter.end.assert_called_once()


class TestFontZoomIndependence:
    """Test that font size is independent of zoom level (bug fix verification)"""

    def test_font_size_not_scaled_by_zoom(self, qtbot):
        """Test that font size remains constant regardless of zoom level.

        This test verifies the fix for the bug where font size was multiplied
        by zoom level, causing text to scale differently than in PDF export.
        """
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        base_font_size = 24

        # Create text element with specific font size
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100,
            text_content="Test Text",
            font_settings={
                "family": "Arial",
                "size": base_font_size,
                "color": (0, 0, 0)
            },
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        # Test with different zoom levels
        for zoom_level in [0.5, 1.0, 2.0, 3.0]:
            mock_renderer = Mock()
            mock_renderer.page_to_screen = Mock(return_value=(100, 100))
            mock_renderer.zoom = zoom_level

            widget._page_renderers = [(mock_renderer, page)]

            # Mock QPainter and QFont
            with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
                with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
                    mock_painter = Mock()
                    mock_painter_class.return_value = mock_painter

                    widget._render_text_overlays()

                    # QFont should be created with base font size, NOT scaled by zoom
                    mock_qfont_class.assert_called()
                    call_args = mock_qfont_class.call_args
                    actual_font_size = call_args[0][1]  # Second positional arg is size

                    assert actual_font_size == base_font_size, (
                        f"Font size should be {base_font_size} at zoom {zoom_level}, "
                        f"got {actual_font_size}"
                    )

    def test_zoom_applied_via_painter_scale(self, qtbot):
        """Test that zoom is applied via painter.scale() transformation.

        This ensures text scales consistently with the page content using
        the painter's transformation matrix rather than font size modification.
        """
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create text element
        text_element = TextBoxData(
            x=50, y=50, width=200, height=100,
            text_content="Test Text",
            font_settings={
                "family": "Arial",
                "size": 12,
                "color": (0, 0, 0)
            },
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        zoom_level = 2.5
        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = zoom_level

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_text_overlays()

            # Painter should call scale() with zoom level
            mock_painter.scale.assert_called_with(zoom_level, zoom_level)

            # Painter should use save/restore for transformation
            mock_painter.save.assert_called()
            mock_painter.restore.assert_called()

    def test_text_rect_uses_page_coordinates(self, qtbot):
        """Test that text rect uses page coordinates (not screen coordinates).

        The text rect should be in page coordinates (w, h) since the painter
        applies the zoom transformation via scale().
        """
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        element_width = 200
        element_height = 100

        # Create text element
        text_element = TextBoxData(
            x=50, y=50, width=element_width, height=element_height,
            text_content="Test Text",
            font_settings={
                "family": "Arial",
                "size": 12,
                "color": (0, 0, 0)
            },
            alignment="left"
        )

        # Mock page with text element
        page = Page(layout=PageLayout(width=210, height=297), page_number=1)
        page.layout.elements = [text_element]

        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = 2.0  # Zoom that would double screen size

        widget._page_renderers = [(mock_renderer, page)]

        # Mock QPainter and QRectF
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            with patch("pyPhotoAlbum.mixins.rendering.QRectF") as mock_qrectf_class:
                mock_painter = Mock()
                mock_painter_class.return_value = mock_painter

                widget._render_text_overlays()

                # QRectF should be called with page coordinates (0, 0, w, h)
                # not screen coordinates (0, 0, w*zoom, h*zoom)
                mock_qrectf_class.assert_called()
                call_args = mock_qrectf_class.call_args
                rect_width = call_args[0][2]
                rect_height = call_args[0][3]

                assert rect_width == element_width, (
                    f"Rect width should be {element_width} (page coords), got {rect_width}"
                )
                assert rect_height == element_height, (
                    f"Rect height should be {element_height} (page coords), got {rect_height}"
                )

    def test_ghost_page_font_not_scaled_by_zoom(self, qtbot):
        """Test that ghost page font size is not scaled by zoom level."""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create mock ghost data
        ghost_data = Mock()
        ghost_data.page_size = (210, 297)
        ghost_data.render = Mock()
        ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))

        for zoom_level in [0.5, 1.0, 2.0]:
            # Create mock renderer with varying zoom
            mock_renderer = Mock()
            mock_renderer.page_to_screen = Mock(return_value=(100, 100))
            mock_renderer.zoom = zoom_level
            mock_renderer.begin_render = Mock()
            mock_renderer.end_render = Mock()

            # Mock QPainter and QFont
            with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
                with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
                    mock_painter = Mock()
                    mock_painter_class.return_value = mock_painter

                    widget._render_ghost_page(ghost_data, mock_renderer)

                    # QFont should be created with base font size 16, NOT scaled
                    mock_qfont_class.assert_called()
                    call_args = mock_qfont_class.call_args
                    actual_font_size = call_args[0][1]  # Second positional arg is size

                    assert actual_font_size == 16, (
                        f"Ghost page font size should be 16 at zoom {zoom_level}, "
                        f"got {actual_font_size}"
                    )

    def test_ghost_page_zoom_applied_via_scale(self, qtbot):
        """Test that ghost page zoom is applied via painter.scale()."""
        widget = TestRenderingWidget()
        qtbot.addWidget(widget)
        widget.resize(1000, 800)

        # Create mock ghost data
        ghost_data = Mock()
        ghost_data.page_size = (210, 297)
        ghost_data.render = Mock()
        ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))

        zoom_level = 1.5
        mock_renderer = Mock()
        mock_renderer.page_to_screen = Mock(return_value=(100, 100))
        mock_renderer.zoom = zoom_level
        mock_renderer.begin_render = Mock()
        mock_renderer.end_render = Mock()

        # Mock QPainter
        with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
            mock_painter = Mock()
            mock_painter_class.return_value = mock_painter

            widget._render_ghost_page(ghost_data, mock_renderer)

            # Painter should call scale() with zoom level
            mock_painter.scale.assert_called_with(zoom_level, zoom_level)
