"""
Tests for PDF export functionality
"""

import os
import tempfile
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData
from pyPhotoAlbum.pdf_exporter import PDFExporter


def test_pdf_exporter_basic():
    """Test basic PDF export with single page"""
    # Create a simple project
    project = Project("Test Project")
    project.page_size_mm = (210, 297)  # A4

    # Add a single page
    page = Page(page_number=1, is_double_spread=False)
    project.add_page(page)

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"
        assert os.path.exists(tmp_path), "PDF file was not created"
        assert os.path.getsize(tmp_path) > 0, "PDF file is empty"

        print(f"✓ Basic PDF export successful: {tmp_path}")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_exporter_double_spread():
    """Test PDF export with double-page spread"""
    project = Project("Test Spread Project")
    project.page_size_mm = (210, 297)  # A4

    # Add a double-page spread
    spread_page = Page(page_number=1, is_double_spread=True)
    project.add_page(spread_page)

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"
        assert os.path.exists(tmp_path), "PDF file was not created"

        print(f"✓ Double-spread PDF export successful: {tmp_path}")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_exporter_with_text():
    """Test PDF export with text boxes"""
    project = Project("Test Text Project")
    project.page_size_mm = (210, 297)

    # Create page with text box
    page = Page(page_number=1, is_double_spread=False)

    # Add a text box
    text_box = TextBoxData(
        text_content="Hello, World!",
        font_settings={"family": "Helvetica", "size": 24, "color": (0, 0, 0)},
        alignment="center",
        x=50,
        y=50,
        width=100,
        height=30,
    )
    page.layout.add_element(text_box)

    project.add_page(page)

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"
        assert os.path.exists(tmp_path), "PDF file was not created"

        print(f"✓ Text box PDF export successful: {tmp_path}")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_text_position_and_size():
    """
    Test that text in PDF is correctly positioned and sized relative to its text box.

    This test verifies:
    1. Font size is properly scaled (not used directly as PDF points)
    2. Text is positioned inside the text box (not above it)
    3. Text respects the top-alignment used in the UI
    """
    import pdfplumber

    project = Project("Test Text Position")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create page with text box
    page = Page(page_number=1, is_double_spread=False)

    # Create a text box with specific dimensions in pixels (at 96 DPI)
    # Text box: 200px wide x 100px tall, positioned at (100, 100)
    # Font size: 48 pixels (stored in same units as element size)
    text_box_x_px = 100
    text_box_y_px = 100
    text_box_width_px = 200
    text_box_height_px = 100
    font_size_px = 48  # Font size in same pixel units as element

    text_box = TextBoxData(
        text_content="Test",
        font_settings={"family": "Helvetica", "size": font_size_px, "color": (0, 0, 0)},
        alignment="left",
        x=text_box_x_px,
        y=text_box_y_px,
        width=text_box_width_px,
        height=text_box_height_px,
    )
    page.layout.add_element(text_box)
    project.add_page(page)

    # Calculate expected PDF values
    MM_TO_POINTS = 2.834645669
    dpi = project.working_dpi
    page_height_pt = 297 * MM_TO_POINTS  # ~842 points

    # Convert text box dimensions to points
    text_box_x_mm = text_box_x_px * 25.4 / dpi
    text_box_y_mm = text_box_y_px * 25.4 / dpi
    text_box_width_mm = text_box_width_px * 25.4 / dpi
    text_box_height_mm = text_box_height_px * 25.4 / dpi

    text_box_x_pt = text_box_x_mm * MM_TO_POINTS
    text_box_y_pt_bottom = page_height_pt - (text_box_y_mm * MM_TO_POINTS) - (text_box_height_mm * MM_TO_POINTS)
    text_box_y_pt_top = text_box_y_pt_bottom + (text_box_height_mm * MM_TO_POINTS)
    text_box_height_pt = text_box_height_mm * MM_TO_POINTS

    # Font size should also be converted from pixels to points
    expected_font_size_pt = font_size_px * 25.4 / dpi * MM_TO_POINTS

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"

        # Extract text position from PDF
        with pdfplumber.open(tmp_path) as pdf:
            page_pdf = pdf.pages[0]
            chars = page_pdf.chars

            assert len(chars) > 0, "No text found in PDF"

            # Get the first character's position and font size
            first_char = chars[0]
            text_x = first_char["x0"]
            text_y_baseline = first_char["y0"]  # This is the baseline y position
            actual_font_size = first_char["size"]

            print(f"\nText Position Analysis:")
            print(
                f"  Text box (in pixels at {dpi} DPI): x={text_box_x_px}, y={text_box_y_px}, "
                f"w={text_box_width_px}, h={text_box_height_px}"
            )
            print(
                f"  Text box (in PDF points): x={text_box_x_pt:.1f}, "
                f"y_bottom={text_box_y_pt_bottom:.1f}, y_top={text_box_y_pt_top:.1f}, "
                f"height={text_box_height_pt:.1f}"
            )
            print(f"  Font size (pixels): {font_size_px}")
            print(f"  Expected font size (points): {expected_font_size_pt:.1f}")
            print(f"  Actual font size (points): {actual_font_size:.1f}")
            print(f"  Actual text x: {text_x:.1f}")
            print(f"  Actual text y (baseline): {text_y_baseline:.1f}")

            # Verify font size is scaled correctly (tolerance of 1pt)
            font_size_diff = abs(actual_font_size - expected_font_size_pt)
            assert font_size_diff < 2.0, (
                f"Font size mismatch: expected ~{expected_font_size_pt:.1f}pt, "
                f"got {actual_font_size:.1f}pt (diff: {font_size_diff:.1f}pt). "
                f"Font size should be converted from pixels to points."
            )

            # Verify text X position is near the left edge of the text box
            x_diff = abs(text_x - text_box_x_pt)
            assert x_diff < 5.0, (
                f"Text X position mismatch: expected ~{text_box_x_pt:.1f}, " f"got {text_x:.1f} (diff: {x_diff:.1f}pt)"
            )

            # Verify text Y baseline is INSIDE the text box (not above it)
            # For top-aligned text, baseline should be within the box bounds
            # pdfplumber y-coordinates use PDF coordinate system: origin at bottom-left, y increases upward
            # So y0 is already the y-coordinate from the bottom of the page
            text_y_from_bottom = text_y_baseline

            # Text baseline should be between box bottom and box top
            # Allow some margin for ascender/descender
            margin = actual_font_size * 0.3  # 30% margin for font metrics

            assert text_y_from_bottom >= text_box_y_pt_bottom - margin, (
                f"Text is below the text box! "
                f"Text baseline y={text_y_from_bottom:.1f}, box bottom={text_box_y_pt_bottom:.1f}"
            )
            assert text_y_from_bottom <= text_box_y_pt_top + margin, (
                f"Text baseline is above the text box! "
                f"Text baseline y={text_y_from_bottom:.1f}, box top={text_box_y_pt_top:.1f}. "
                f"Text should be positioned inside the box, not above it."
            )

            print(f"  Text y (from bottom): {text_y_from_bottom:.1f}")
            print(f"  Text is inside box bounds: ✓")
            print(f"\n✓ Text position and size test passed!")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_text_wrapping():
    """
    Test that text wraps correctly within the text box boundaries.

    This test verifies:
    1. Long text is word-wrapped to fit within the box width
    2. Multiple lines are rendered correctly
    3. Text stays within the box boundaries
    """
    import pdfplumber

    project = Project("Test Text Wrapping")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create page with text box
    page = Page(page_number=1, is_double_spread=False)

    # Create a text box with long text that should wrap
    text_box_x_px = 100
    text_box_y_px = 100
    text_box_width_px = 200  # Narrow box to force wrapping
    text_box_height_px = 200  # Tall enough for multiple lines
    font_size_px = 24

    long_text = "This is a long piece of text that should wrap to multiple lines within the text box boundaries."

    text_box = TextBoxData(
        text_content=long_text,
        font_settings={"family": "Helvetica", "size": font_size_px, "color": (0, 0, 0)},
        alignment="left",
        x=text_box_x_px,
        y=text_box_y_px,
        width=text_box_width_px,
        height=text_box_height_px,
    )
    page.layout.add_element(text_box)
    project.add_page(page)

    # Calculate box boundaries in PDF points
    MM_TO_POINTS = 2.834645669
    dpi = project.working_dpi

    text_box_x_mm = text_box_x_px * 25.4 / dpi
    text_box_width_mm = text_box_width_px * 25.4 / dpi
    text_box_x_pt = text_box_x_mm * MM_TO_POINTS
    text_box_width_pt = text_box_width_mm * MM_TO_POINTS
    text_box_right_pt = text_box_x_pt + text_box_width_pt

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"

        # Extract text from PDF
        with pdfplumber.open(tmp_path) as pdf:
            page_pdf = pdf.pages[0]
            chars = page_pdf.chars

            assert len(chars) > 0, "No text found in PDF"

            # Get all unique Y positions (lines)
            y_positions = sorted(set(round(c["top"], 1) for c in chars))

            print(f"\nText Wrapping Analysis:")
            print(f"  Text box width: {text_box_width_pt:.1f}pt")
            print(f"  Text box x: {text_box_x_pt:.1f}pt to {text_box_right_pt:.1f}pt")
            print(f"  Number of lines: {len(y_positions)}")
            print(f"  Line Y positions: {y_positions[:5]}...")  # Show first 5

            # Verify text wrapped to multiple lines
            assert len(y_positions) > 1, f"Text should wrap to multiple lines but only found {len(y_positions)} line(s)"

            # Verify all characters are within box width (with small tolerance)
            tolerance = 5.0  # Small tolerance for rounding
            for char in chars:
                char_x = char["x0"]
                char_right = char["x1"]
                assert (
                    char_x >= text_box_x_pt - tolerance
                ), f"Character '{char['text']}' at x={char_x:.1f} is left of box start {text_box_x_pt:.1f}"
                assert (
                    char_right <= text_box_right_pt + tolerance
                ), f"Character '{char['text']}' ends at x={char_right:.1f} which exceeds box right {text_box_right_pt:.1f}"

            print(f"  All characters within box width: ✓")
            print(f"\n✓ Text wrapping test passed!")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_exporter_facing_pages_alignment():
    """Test that double spreads align to facing pages"""
    project = Project("Test Facing Pages")
    project.page_size_mm = (210, 297)

    # Add single page (page 1)
    page1 = Page(page_number=1, is_double_spread=False)
    project.add_page(page1)

    # Add double spread (should start on page 2, which requires blank insert)
    # Since page 1 is odd, a blank page should be inserted, making the spread pages 2-3
    spread = Page(page_number=2, is_double_spread=True)
    project.add_page(spread)

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, f"Export failed: {warnings}"
        assert os.path.exists(tmp_path), "PDF file was not created"

        print(f"✓ Facing pages alignment successful: {tmp_path}")
        print(f"  Expected: Page 1 (single), blank page, Pages 2-3 (spread)")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_exporter_missing_image():
    """Test PDF export with missing image (should warn but not fail)"""
    project = Project("Test Missing Image")
    project.page_size_mm = (210, 297)

    # Create page with image that doesn't exist
    page = Page(page_number=1, is_double_spread=False)

    # Add image with non-existent path
    image = ImageData(image_path="/nonexistent/path/to/image.jpg", x=50, y=50, width=100, height=100)
    page.layout.add_element(image)

    project.add_page(page)

    # Export to temporary file
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
        tmp_path = tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(tmp_path)

        assert success, "Export should succeed even with missing images"
        assert len(warnings) > 0, "Should have warnings for missing image"
        assert "not found" in warnings[0].lower(), "Warning should mention missing image"

        print(f"✓ Missing image handling successful: {tmp_path}")
        print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)


def test_pdf_exporter_spanning_image():
    """Test PDF export with image spanning across center line of double spread"""
    import tempfile
    from PIL import Image as PILImage

    project = Project("Test Spanning Image")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96  # Standard DPI

    # Create a test image (solid color for easy verification)
    test_img = PILImage.new("RGB", (400, 200), color="red")

    # Save test image to temporary file
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
        img_path = img_tmp.name
        test_img.save(img_path)

    try:
        # Create a double-page spread
        spread_page = Page(page_number=1, is_double_spread=True)

        # Calculate center position in pixels (for a 210mm page width at 96 DPI)
        # Spread width is 2 * 210mm = 420mm
        spread_width_px = 420 * 96 / 25.4  # ~1587 pixels
        center_px = spread_width_px / 2  # ~794 pixels

        # Add an image that spans across the center
        # Position it so it overlaps the center line
        image_width_px = 400
        image_x_px = center_px - 200  # Start 200px before center, end 200px after

        spanning_image = ImageData(image_path=img_path, x=image_x_px, y=100, width=image_width_px, height=200)
        spread_page.layout.add_element(spanning_image)

        project.add_page(spread_page)

        # Export to temporary PDF
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
            pdf_path = pdf_tmp.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)

            assert success, f"Export failed: {warnings}"
            assert os.path.exists(pdf_path), "PDF file was not created"

            print(f"✓ Spanning image export successful: {pdf_path}")
            print(f"  Image spans from {image_x_px:.1f}px to {image_x_px + image_width_px:.1f}px")
            print(f"  Center line at {center_px:.1f}px")
            if warnings:
                print(f"  Warnings: {warnings}")

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_multiple_spanning_elements():
    """Test PDF export with multiple images spanning the center line"""
    import tempfile
    from PIL import Image as PILImage

    project = Project("Test Multiple Spanning")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create test images
    test_img1 = PILImage.new("RGB", (300, 150), color="blue")
    test_img2 = PILImage.new("RGB", (250, 200), color="green")

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp1:
        img_path1 = img_tmp1.name
        test_img1.save(img_path1)

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp2:
        img_path2 = img_tmp2.name
        test_img2.save(img_path2)

    try:
        spread_page = Page(page_number=1, is_double_spread=True)

        # Calculate positions
        spread_width_px = 420 * 96 / 25.4
        center_px = spread_width_px / 2

        # First spanning image
        image1 = ImageData(
            image_path=img_path1, x=center_px - 150, y=50, width=300, height=150  # Centered on split line
        )

        # Second spanning image (different position)
        image2 = ImageData(image_path=img_path2, x=center_px - 100, y=250, width=250, height=200)

        spread_page.layout.add_element(image1)
        spread_page.layout.add_element(image2)

        project.add_page(spread_page)

        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
            pdf_path = pdf_tmp.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)

            assert success, f"Export failed: {warnings}"
            assert os.path.exists(pdf_path), "PDF file was not created"

            print(f"✓ Multiple spanning images export successful: {pdf_path}")
            if warnings:
                print(f"  Warnings: {warnings}")

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path1):
            os.remove(img_path1)
        if os.path.exists(img_path2):
            os.remove(img_path2)


def test_pdf_exporter_edge_case_barely_spanning():
    """Test image that barely crosses the threshold"""
    import tempfile
    from PIL import Image as PILImage

    project = Project("Test Edge Case")
    project.page_size_mm = (210, 297)
    project.working_dpi = 96

    test_img = PILImage.new("RGB", (100, 100), color="yellow")

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
        img_path = img_tmp.name
        test_img.save(img_path)

    try:
        spread_page = Page(page_number=1, is_double_spread=True)

        spread_width_px = 420 * 96 / 25.4
        center_px = spread_width_px / 2

        # Image that just barely crosses the center line
        image = ImageData(image_path=img_path, x=center_px - 5, y=100, width=100, height=100)  # Just 5px overlap

        spread_page.layout.add_element(image)
        project.add_page(spread_page)

        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
            pdf_path = pdf_tmp.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)

            assert success, f"Export failed: {warnings}"

            print(f"✓ Edge case (barely spanning) export successful: {pdf_path}")
            if warnings:
                print(f"  Warnings: {warnings}")

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_text_spanning():
    """Test text box spanning the center line"""
    project = Project("Test Spanning Text")
    project.page_size_mm = (210, 297)
    project.working_dpi = 96

    spread_page = Page(page_number=1, is_double_spread=True)

    spread_width_px = 420 * 96 / 25.4
    center_px = spread_width_px / 2

    # Text box spanning the center
    text_box = TextBoxData(
        text_content="Spanning Text",
        font_settings={"family": "Helvetica", "size": 24, "color": (0, 0, 0)},
        alignment="center",
        x=center_px - 100,
        y=100,
        width=200,
        height=50,
    )

    spread_page.layout.add_element(text_box)
    project.add_page(spread_page)

    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
        pdf_path = pdf_tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(pdf_path)

        assert success, f"Export failed: {warnings}"

        print(f"✓ Spanning text box export successful: {pdf_path}")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(pdf_path):
            os.remove(pdf_path)


def test_pdf_exporter_spanning_image_aspect_ratio():
    """Test that spanning images maintain correct aspect ratio and can be recombined"""
    import tempfile
    from PIL import Image as PILImage, ImageDraw

    project = Project("Test Aspect Ratio")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create a distinctive test image: red left half, blue right half, with a vertical line in center
    test_width, test_height = 800, 400
    test_img = PILImage.new("RGB", (test_width, test_height))
    draw = ImageDraw.Draw(test_img)

    # Fill left half red
    draw.rectangle([0, 0, test_width // 2, test_height], fill=(255, 0, 0))

    # Fill right half blue
    draw.rectangle([test_width // 2, 0, test_width, test_height], fill=(0, 0, 255))

    # Draw a black vertical line in the middle
    draw.line([test_width // 2, 0, test_width // 2, test_height], fill=(0, 0, 0), width=5)

    # Draw horizontal reference lines for visual verification
    for y in range(0, test_height, 50):
        draw.line([0, y, test_width, y], fill=(255, 255, 255), width=2)

    # Save test image to temporary file
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
        img_path = img_tmp.name
        test_img.save(img_path)

    try:
        # Create a double-page spread
        spread_page = Page(page_number=1, is_double_spread=True)

        # Calculate positions
        spread_width_px = 420 * 96 / 25.4  # ~1587 pixels
        center_px = spread_width_px / 2  # ~794 pixels

        # Create an image element that spans the center with a specific aspect ratio
        # Make it 600px wide and 300px tall (2:1 aspect ratio)
        image_width_px = 600
        image_height_px = 300
        image_x_px = center_px - 300  # Centered on the split line

        spanning_image = ImageData(
            image_path=img_path, x=image_x_px, y=100, width=image_width_px, height=image_height_px
        )
        spread_page.layout.add_element(spanning_image)

        project.add_page(spread_page)

        # Export to temporary PDF
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
            pdf_path = pdf_tmp.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)

            assert success, f"Export failed: {warnings}"
            assert os.path.exists(pdf_path), "PDF file was not created"

            # Verify the PDF was created and has expected properties
            # We can't easily extract and verify pixel-perfect image reconstruction without
            # additional dependencies, but we can verify the export succeeded
            file_size = os.path.getsize(pdf_path)
            assert file_size > 1000, "PDF file seems too small"

            print(f"✓ Spanning image aspect ratio test successful: {pdf_path}")
            print(f"  Original image: {test_width}x{test_height} (aspect {test_width/test_height:.2f}:1)")
            print(f"  Element size: {image_width_px}x{image_height_px} (aspect {image_width_px/image_height_px:.2f}:1)")
            print(f"  Split at: {center_px:.1f}px")
            print(f"  Left portion: {center_px - image_x_px:.1f}px wide")
            print(f"  Right portion: {image_width_px - (center_px - image_x_px):.1f}px wide")
            print(f"  PDF size: {file_size} bytes")

            if warnings:
                print(f"  Warnings: {warnings}")

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_varying_aspect_ratios():
    """Test spanning images with various aspect ratios"""
    import tempfile
    from PIL import Image as PILImage, ImageDraw

    project = Project("Test Varying Aspects")
    project.page_size_mm = (210, 297)
    project.working_dpi = 96

    # Test different aspect ratios
    test_configs = [
        ("Square", 400, 400),  # 1:1
        ("Landscape", 800, 400),  # 2:1
        ("Portrait", 400, 800),  # 1:2
        ("Wide", 1200, 400),  # 3:1
    ]

    spread_width_px = 420 * 96 / 25.4
    center_px = spread_width_px / 2

    for idx, (name, img_w, img_h) in enumerate(test_configs):
        # Create test image
        test_img = PILImage.new("RGB", (img_w, img_h))
        draw = ImageDraw.Draw(test_img)

        # Different colors for each test
        colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
        draw.rectangle([0, 0, img_w // 2, img_h], fill=colors[idx])
        draw.rectangle(
            [img_w // 2, 0, img_w, img_h], fill=(255 - colors[idx][0], 255 - colors[idx][1], 255 - colors[idx][2])
        )

        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
            img_path = img_tmp.name
            test_img.save(img_path)

        try:
            spread_page = Page(page_number=idx + 1, is_double_spread=True)

            # Position spanning element
            element_width_px = 500
            element_height_px = int(500 * img_h / img_w)  # Maintain aspect ratio

            spanning_image = ImageData(
                image_path=img_path,
                x=center_px - 250,
                y=100 + idx * 200,
                width=element_width_px,
                height=element_height_px,
            )
            spread_page.layout.add_element(spanning_image)

            project.add_page(spread_page)

        finally:
            if os.path.exists(img_path):
                os.remove(img_path)

    # Export all pages
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
        pdf_path = pdf_tmp.name

    try:
        exporter = PDFExporter(project)
        success, warnings = exporter.export(pdf_path)

        assert success, f"Export failed: {warnings}"
        assert os.path.exists(pdf_path), "PDF file was not created"

        print(f"✓ Varying aspect ratios test successful: {pdf_path}")
        print(f"  Tested {len(test_configs)} different aspect ratios")
        if warnings:
            print(f"  Warnings: {warnings}")

    finally:
        if os.path.exists(pdf_path):
            os.remove(pdf_path)


def test_pdf_exporter_rotated_image():
    """Test that PIL rotation is applied when exporting to PDF"""
    import tempfile
    from PIL import Image as PILImage, ImageDraw

    project = Project("Test Rotated Image")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create a distinctive test image that shows rotation clearly
    # Make it wider than tall (400x200) so we can verify rotation
    test_img = PILImage.new("RGB", (400, 200), color="white")
    draw = ImageDraw.Draw(test_img)

    # Draw a pattern that shows orientation
    # Red bar at top
    draw.rectangle([0, 0, 400, 50], fill=(255, 0, 0))
    # Blue bar at bottom
    draw.rectangle([0, 150, 400, 200], fill=(0, 0, 255))
    # Green vertical stripe on left
    draw.rectangle([0, 0, 50, 200], fill=(0, 255, 0))
    # Yellow vertical stripe on right
    draw.rectangle([350, 0, 400, 200], fill=(255, 255, 0))

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
        img_path = img_tmp.name
        test_img.save(img_path)

    try:
        # Create a page
        page = Page(page_number=1, is_double_spread=False)

        # Add image with 90-degree PIL rotation
        image = ImageData(
            image_path=img_path, x=50, y=50, width=200, height=400  # These dimensions are for the rotated version
        )
        image.pil_rotation_90 = 1  # 90 degree rotation
        image.image_dimensions = (400, 200)  # Original dimensions before rotation

        page.layout.add_element(image)
        project.add_page(page)

        # Export to PDF
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp:
            pdf_path = pdf_tmp.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)

            assert success, f"Export failed: {warnings}"
            assert os.path.exists(pdf_path), "PDF file was not created"

            print(f"✓ Rotated image export successful: {pdf_path}")
            print(f"  Original image: 400x200 (landscape)")
            print(f"  PIL rotation: 90 degrees")
            print(f"  Expected in PDF: rotated image (portrait orientation)")
            if warnings:
                print(f"  Warnings: {warnings}")

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_image_downsampling():
    """Test that export DPI controls image downsampling and reduces file size"""
    import tempfile
    from PIL import Image as PILImage

    project = Project("Test Downsampling")
    project.page_size_mm = (210, 297)  # A4
    project.working_dpi = 96

    # Create a large test image (4000x3000 - typical high-res camera)
    large_img = PILImage.new("RGB", (4000, 3000))
    # Add some pattern so it doesn't compress too much
    import random

    pixels = large_img.load()
    for i in range(0, 4000, 10):
        for j in range(0, 3000, 10):
            pixels[i, j] = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_tmp:
        img_path = img_tmp.name
        large_img.save(img_path)

    try:
        # Create a page with the large image
        page = Page(page_number=1, is_double_spread=False)

        # Add image at reasonable size (100mm x 75mm)
        image = ImageData(
            image_path=img_path,
            x=50,
            y=50,
            width=int(100 * 96 / 25.4),  # ~378 px
            height=int(75 * 96 / 25.4),  # ~283 px
        )
        page.layout.add_element(image)
        project.add_page(page)

        # Export with high DPI (300 - print quality)
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp1:
            pdf_path_300dpi = pdf_tmp1.name

        # Export with low DPI (150 - screen quality)
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_tmp2:
            pdf_path_150dpi = pdf_tmp2.name

        try:
            # Export at 300 DPI
            exporter_300 = PDFExporter(project, export_dpi=300)
            success1, warnings1 = exporter_300.export(pdf_path_300dpi)
            assert success1, f"300 DPI export failed: {warnings1}"

            # Export at 150 DPI
            exporter_150 = PDFExporter(project, export_dpi=150)
            success2, warnings2 = exporter_150.export(pdf_path_150dpi)
            assert success2, f"150 DPI export failed: {warnings2}"

            # Check file sizes
            size_300dpi = os.path.getsize(pdf_path_300dpi)
            size_150dpi = os.path.getsize(pdf_path_150dpi)

            print(f"✓ Image downsampling test successful:")
            print(f"  Original image: 4000x3000 pixels")
            print(f"  Element size: 100mm x 75mm")
            print(f"  PDF at 300 DPI: {size_300dpi:,} bytes")
            print(f"  PDF at 150 DPI: {size_150dpi:,} bytes")
            print(f"  Size reduction: {(1 - size_150dpi/size_300dpi)*100:.1f}%")

            # 150 DPI should be smaller than 300 DPI
            assert (
                size_150dpi < size_300dpi
            ), f"150 DPI file ({size_150dpi}) should be smaller than 300 DPI file ({size_300dpi})"

            # 150 DPI should be significantly smaller (at least 50% reduction)
            reduction_ratio = size_150dpi / size_300dpi
            assert reduction_ratio < 0.7, f"150 DPI should be at least 30% smaller, got {(1-reduction_ratio)*100:.1f}%"

        finally:
            if os.path.exists(pdf_path_300dpi):
                os.remove(pdf_path_300dpi)
            if os.path.exists(pdf_path_150dpi):
                os.remove(pdf_path_150dpi)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_bleed_expands_page_size():
    """Test that setting page_bleed_mm expands the PDF page dimensions"""
    import pdfplumber

    MM_TO_POINTS = 2.834645669
    page_width_mm = 210.0
    page_height_mm = 297.0
    bleed_mm = 3.0

    # Export WITHOUT bleed
    project_no_bleed = Project("No Bleed")
    project_no_bleed.page_size_mm = (page_width_mm, page_height_mm)
    project_no_bleed.page_bleed_mm = 0.0
    page_no_bleed = Page(page_number=1, is_double_spread=False)
    project_no_bleed.add_page(page_no_bleed)

    # Export WITH bleed
    project_bleed = Project("With Bleed")
    project_bleed.page_size_mm = (page_width_mm, page_height_mm)
    project_bleed.page_bleed_mm = bleed_mm
    page_bleed = Page(page_number=1, is_double_spread=False)
    project_bleed.add_page(page_bleed)

    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f1:
        path_no_bleed = f1.name
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f2:
        path_bleed = f2.name

    try:
        exporter_no_bleed = PDFExporter(project_no_bleed)
        success, _ = exporter_no_bleed.export(path_no_bleed)
        assert success

        exporter_bleed = PDFExporter(project_bleed)
        success, _ = exporter_bleed.export(path_bleed)
        assert success

        with pdfplumber.open(path_no_bleed) as pdf:
            w_no_bleed = pdf.pages[0].width
            h_no_bleed = pdf.pages[0].height

        with pdfplumber.open(path_bleed) as pdf:
            w_bleed = pdf.pages[0].width
            h_bleed = pdf.pages[0].height

        bleed_pt = bleed_mm * MM_TO_POINTS
        expected_w = page_width_mm * MM_TO_POINTS + 2 * bleed_pt
        expected_h = page_height_mm * MM_TO_POINTS + 2 * bleed_pt

        assert abs(w_no_bleed - page_width_mm * MM_TO_POINTS) < 1.0, (
            f"No-bleed page width mismatch: {w_no_bleed:.2f} vs {page_width_mm * MM_TO_POINTS:.2f}"
        )
        assert abs(w_bleed - expected_w) < 1.0, (
            f"Bleed page width mismatch: {w_bleed:.2f} vs {expected_w:.2f}"
        )
        assert abs(h_bleed - expected_h) < 1.0, (
            f"Bleed page height mismatch: {h_bleed:.2f} vs {expected_h:.2f}"
        )
        assert w_bleed > w_no_bleed, "Bleed page should be wider than non-bleed page"
        assert h_bleed > h_no_bleed, "Bleed page should be taller than non-bleed page"

    finally:
        if os.path.exists(path_no_bleed):
            os.remove(path_no_bleed)
        if os.path.exists(path_bleed):
            os.remove(path_bleed)


def test_pdf_exporter_spread_bleed_expands_and_offsets():
    """Test that double spread pages each get bleed on all 4 sides, same as single pages"""
    import pdfplumber
    from PIL import Image as PILImage

    MM_TO_POINTS = 2.834645669
    page_width_mm = 210.0
    page_height_mm = 297.0
    bleed_mm = 3.0
    bleed_pt = bleed_mm * MM_TO_POINTS
    dpi = 96

    img = PILImage.new("RGB", (200, 200), color="red")
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_f:
        img_path = img_f.name
        img.save(img_path)

    try:
        project = Project("Spread Bleed")
        project.page_size_mm = (page_width_mm, page_height_mm)
        project.working_dpi = dpi
        project.page_bleed_mm = bleed_mm

        spread = Page(page_number=1, is_double_spread=True)
        page_w_px = page_width_mm * dpi / 25.4
        page_h_px = page_height_mm * dpi / 25.4
        center_px = page_w_px

        # Full-page element on left page (x=0 to center)
        left_img = ImageData(image_path=img_path, x=0, y=0, width=page_w_px, height=page_h_px)
        # Full-page element on right page (x=center to 2*center)
        right_img = ImageData(image_path=img_path, x=center_px, y=0, width=page_w_px, height=page_h_px)
        spread.layout.add_element(left_img)
        spread.layout.add_element(right_img)
        project.add_page(spread)

        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_f:
            pdf_path = pdf_f.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)
            assert success, f"Export failed: {warnings}"

            expected_page_w = page_width_mm * MM_TO_POINTS + 2 * bleed_pt
            expected_page_h = page_height_mm * MM_TO_POINTS + 2 * bleed_pt
            expected_img_x1 = page_width_mm * MM_TO_POINTS + bleed_pt

            with pdfplumber.open(pdf_path) as pdf:
                spread_pages = [pg for pg in pdf.pages if pg.images]
                assert len(spread_pages) == 2, f"Expected 2 pages with images, got {len(spread_pages)}"

                for pg in spread_pages:
                    # Page size includes bleed on all sides
                    assert abs(pg.width - expected_page_w) < 1.0, (
                        f"Page width {pg.width:.2f} != expected {expected_page_w:.2f}"
                    )
                    assert abs(pg.height - expected_page_h) < 1.0, (
                        f"Page height {pg.height:.2f} != expected {expected_page_h:.2f}"
                    )
                    # Image starts at bleed_pt from left (3mm inner padding)
                    img_obj = pg.images[0]
                    assert abs(img_obj["x0"] - bleed_pt) < 1.0, (
                        f"Image x0={img_obj['x0']:.2f} != bleed_pt={bleed_pt:.2f}"
                    )
                    # Image ends bleed_pt from right edge (3mm outer padding)
                    assert abs(img_obj["x1"] - expected_img_x1) < 1.0, (
                        f"Image x1={img_obj['x1']:.2f} != {expected_img_x1:.2f}"
                    )

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)
    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


def test_pdf_exporter_bleed_offsets_content():
    """Test that bleed shifts image content by bleed_pt so it appears at correct position"""
    import pdfplumber
    from PIL import Image as PILImage

    MM_TO_POINTS = 2.834645669
    page_width_mm = 210.0
    page_height_mm = 297.0
    bleed_mm = 3.0
    bleed_pt = bleed_mm * MM_TO_POINTS

    # Create a solid-color test image
    test_img = PILImage.new("RGB", (200, 200), color="red")
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_f:
        img_path = img_f.name
        test_img.save(img_path)

    try:
        # Place image at page position (50mm, 50mm)
        element_x_mm = 50.0
        element_y_mm = 50.0
        element_w_mm = 40.0
        element_h_mm = 40.0
        dpi = 96
        element_x_px = element_x_mm * dpi / 25.4
        element_y_px = element_y_mm * dpi / 25.4
        element_w_px = element_w_mm * dpi / 25.4
        element_h_px = element_h_mm * dpi / 25.4

        project = Project("Bleed Offset")
        project.page_size_mm = (page_width_mm, page_height_mm)
        project.working_dpi = dpi
        project.page_bleed_mm = bleed_mm

        page = Page(page_number=1, is_double_spread=False)
        image = ImageData(image_path=img_path, x=element_x_px, y=element_y_px,
                          width=element_w_px, height=element_h_px)
        page.layout.add_element(image)
        project.add_page(page)

        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as pdf_f:
            pdf_path = pdf_f.name

        try:
            exporter = PDFExporter(project)
            success, warnings = exporter.export(pdf_path)
            assert success, f"Export failed: {warnings}"

            with pdfplumber.open(pdf_path) as pdf:
                pg = pdf.pages[0]
                images = pg.images
                assert len(images) > 0, "No images found in PDF"

                img_obj = images[0]
                # x0 should be element_x_pt + bleed_pt from left edge of expanded page
                expected_x0 = element_x_mm * MM_TO_POINTS + bleed_pt
                actual_x0 = img_obj["x0"]
                assert abs(actual_x0 - expected_x0) < 2.0, (
                    f"Image X offset mismatch: expected {expected_x0:.2f}, got {actual_x0:.2f}"
                )

        finally:
            if os.path.exists(pdf_path):
                os.remove(pdf_path)

    finally:
        if os.path.exists(img_path):
            os.remove(img_path)


if __name__ == "__main__":
    print("Running PDF export tests...\n")

    try:
        test_pdf_exporter_basic()
        test_pdf_exporter_double_spread()
        test_pdf_exporter_with_text()
        test_pdf_text_position_and_size()
        test_pdf_text_wrapping()
        test_pdf_exporter_facing_pages_alignment()
        test_pdf_exporter_missing_image()
        test_pdf_exporter_spanning_image()
        test_pdf_exporter_multiple_spanning_elements()
        test_pdf_exporter_edge_case_barely_spanning()
        test_pdf_exporter_text_spanning()
        test_pdf_exporter_spanning_image_aspect_ratio()
        test_pdf_exporter_varying_aspect_ratios()
        test_pdf_exporter_rotated_image()
        test_pdf_exporter_image_downsampling()
        test_pdf_exporter_bleed_expands_page_size()
        test_pdf_exporter_bleed_offsets_content()

        print("\n✓ All tests passed!")

    except AssertionError as e:
        print(f"\n✗ Test failed: {e}")
        raise
    except Exception as e:
        print(f"\n✗ Unexpected error: {e}")
        raise
