"""
Integration tests for document layouter functionality.

This test file focuses on realistic scenarios using actual Line, Text, and other
concrete objects to test the complete integration of word spacing constraints
in multi-page layout scenarios.
"""

import pytest
import os
import logging

from pyWebLayout.layout.document_layouter import paragraph_layouter
from pyWebLayout.style.abstract_style import AbstractStyle
from pyWebLayout.style.fonts import Font
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.concrete.page import Page
from pyWebLayout.concrete.text import Line
from pyWebLayout.abstract.inline import Word

# Enable logging to see font loading messages
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def verify_bundled_font_available():
    """Verify that the bundled font is available for integration tests."""
    # Get the bundled font path
    current_dir = os.path.dirname(os.path.abspath(__file__))
    # Navigate up to pyWebLayout root, then to assets/fonts
    project_root = os.path.dirname(os.path.dirname(current_dir))
    bundled_font_path = os.path.join(
        project_root,
        'pyWebLayout',
        'assets',
        'fonts',
        'DejaVuSans.ttf')

    logger.info(f"Integration tests checking for bundled font at: {bundled_font_path}")

    if not os.path.exists(bundled_font_path):
        pytest.fail(
            f"INTEGRATION TEST FAILURE: Bundled font not found at {bundled_font_path}\n"
            f"Integration tests require the bundled font to ensure consistent behavior.\n"
            f"This likely means the font was not included in the package build."
        )

    logger.info(f"Bundled font found at: {bundled_font_path}")
    return bundled_font_path


class MockWord(Word):
    """A simple mock word that extends the real Word class."""

    def __init__(self, text, style=None):
        if style is None:
            # Integration tests MUST use the bundled font for consistency
            style = Font(font_size=16)
            # Verify the font loaded properly
            if style.font.path is None:
                logger.warning(
                    "Font loaded without explicit path - may be using PIL default")
        # Initialize the base Word with required parameters
        super().__init__(text, style)
        self._concrete_texts = []

    def add_concete(self, texts):
        """Add concrete text representations."""
        if isinstance(texts, list):
            self._concrete_texts.extend(texts)
        else:
            self._concrete_texts.append(texts)

    def possible_hyphenation(self):
        """Return possible hyphenation points."""
        if len(self.text) <= 6:
            return []

        # Simple hyphenation: split roughly in the middle
        mid = len(self.text) // 2
        return [(self.text[:mid] + "-", self.text[mid:])]


class MockParagraph:
    """A simple paragraph with words and styling."""

    def __init__(self, text_content, word_spacing_style=None):
        if word_spacing_style is None:
            word_spacing_style = AbstractStyle(
                word_spacing=4.0,
                word_spacing_min=2.0,
                word_spacing_max=8.0
            )

        self.style = word_spacing_style
        self.line_height = 25

        # Create words from text content
        self.words = []
        for word_text in text_content.split():
            word = MockWord(word_text)
            self.words.append(word)


class TestDocumentLayouterIntegration:
    """Integration tests using real components."""

    @classmethod
    def setup_class(cls):
        """Verify bundled font is available before running any tests."""
        verify_bundled_font_available()

    def test_single_page_layout_with_real_components(self):
        """Test layout on a single page using real Line and Text objects."""
        # Create a real page that can fit content
        page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
        page = Page(size=(500, 400), style=page_style)

        # Create a paragraph with realistic content
        paragraph = MockParagraph(
            "The quick brown fox jumps over the lazy dog and runs through the forest.",
            AbstractStyle(word_spacing=3.0, word_spacing_min=2.0, word_spacing_max=6.0)
        )

        # Layout the paragraph
        success, failed_word_index, remaining_pretext = paragraph_layouter(
            paragraph, page)

        # Verify successful layout
        assert success is True
        assert failed_word_index is None
        assert remaining_pretext is None

        # Verify lines were added to page
        assert len(page.children) > 0

        # Verify actual Line objects were created
        for child in page.children:
            assert isinstance(child, Line)

        print(f"✓ Single page test: {len(page.children)} lines created")

    def test_multi_page_scenario_with_page_overflow(self):
        """Test realistic multi-page scenario with actual page overflow."""
        # Create a very small real page that will definitely overflow
        small_page_style = PageStyle(border_width=5, padding=(5, 5, 5, 5))
        small_page = Page(size=(150, 80), style=small_page_style)

        # Create a long paragraph that will definitely overflow
        long_text = " ".join(
            [f"verylongword{i:02d}" for i in range(20)])  # 20 long words
        paragraph = MockParagraph(
            long_text,
            AbstractStyle(word_spacing=4.0, word_spacing_min=2.0, word_spacing_max=8.0)
        )

        # Layout the paragraph - should fail due to page overflow
        success, failed_word_index, remaining_pretext = paragraph_layouter(
            paragraph, small_page)

        # Either should fail due to overflow OR succeed with limited content
        if success:
            # If it succeeded, verify it fit some content
            assert len(small_page.children) > 0
            print(
                f"✓ Multi-page test: Content fit on small page, {len(small_page.children)} lines created")
        else:
            # If it failed, verify overflow handling
            assert failed_word_index is not None  # Should indicate where it failed
            assert failed_word_index < len(
                paragraph.words)  # Should be within word range
            print(
                f"✓ Multi-page test: Page overflow at word {failed_word_index}, {len(small_page.children)} lines fit")

    def test_word_spacing_constraints_in_real_lines(self):
        """Test that word spacing constraints are properly used in real Line objects."""
        # Create real page
        page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
        page = Page(size=(400, 300), style=page_style)

        # Create paragraph with specific spacing constraints
        paragraph = MockParagraph(
            "Testing word spacing constraints with realistic content.",
            AbstractStyle(word_spacing=5.0, word_spacing_min=3.0, word_spacing_max=10.0)
        )

        # Layout paragraph
        success, _, _ = paragraph_layouter(paragraph, page)
        assert success is True

        # Verify that Line objects were created with correct spacing
        assert len(page.children) > 0

        for line in page.children:
            assert isinstance(line, Line)
            # Verify spacing constraints were applied
            assert hasattr(line, '_spacing')
            min_spacing, max_spacing = line._spacing
            assert min_spacing == 3  # From our constraint
            assert max_spacing == 10  # From our constraint

        print(
            f"✓ Word spacing test: {len(page.children)} lines with constraints (3, 10)")

    def test_different_alignment_strategies_with_constraints(self):
        """Test different text alignment strategies with word spacing constraints."""
        alignments_to_test = [
            ("left", AbstractStyle(
                text_align="left", word_spacing_min=2.0, word_spacing_max=6.0)), ("justify", AbstractStyle(
                    text_align="justify", word_spacing_min=3.0, word_spacing_max=12.0)), ("center", AbstractStyle(
                        text_align="center", word_spacing_min=1.0, word_spacing_max=5.0))]

        for alignment_name, style in alignments_to_test:
            page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
            page = Page(size=(350, 200), style=page_style)
            paragraph = MockParagraph(
                "This sentence will test different alignment strategies with word spacing.", style)

            success, _, _ = paragraph_layouter(paragraph, page)
            assert success is True
            assert len(page.children) > 0

            # Verify alignment was applied to lines
            for line in page.children:
                assert isinstance(line, Line)
                # Check that the alignment handler was set correctly
                assert line._alignment_handler is not None

            print(f"✓ {alignment_name} alignment: {len(page.children)} lines created")

    def test_realistic_document_with_multiple_pages(self):
        """Test a realistic document that spans multiple pages."""
        # Create multiple real pages
        page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
        pages = [Page(size=(400, 300), style=page_style) for _ in range(3)]

        # Create a document with multiple paragraphs
        paragraphs = [
            MockParagraph(
                "This is the first paragraph of our document. It contains enough text to "
                "potentially span multiple lines and test the word spacing constraints properly.",
                AbstractStyle(word_spacing=3.0, word_spacing_min=2.0, word_spacing_max=8.0)
            ),
            MockParagraph(
                "Here is a second paragraph with different styling. This paragraph uses "
                "different word spacing constraints to test the flexibility of the system.",
                AbstractStyle(word_spacing=5.0, word_spacing_min=3.0, word_spacing_max=12.0)
            ),
            MockParagraph(
                "The third and final paragraph completes our test document. It should "
                "demonstrate that the layouter can handle multiple paragraphs with varying "
                "content lengths and styling requirements.",
                AbstractStyle(word_spacing=4.0, word_spacing_min=2.5, word_spacing_max=10.0)
            )
        ]

        # Layout paragraphs across pages
        current_page_index = 0

        for para_index, paragraph in enumerate(paragraphs):
            start_word = 0

            while start_word < len(paragraph.words):
                if current_page_index >= len(pages):
                    break  # Out of pages

                current_page = pages[current_page_index]
                success, failed_word_index, _ = paragraph_layouter(
                    paragraph, current_page, start_word
                )

                if success:
                    # Paragraph completed on this page
                    break
                else:
                    # Page full, move to next page
                    if failed_word_index is not None:
                        start_word = failed_word_index
                    current_page_index += 1

                    # If we're out of pages, stop
                    if current_page_index >= len(pages):
                        break

        # Verify pages have content
        total_lines = sum(len(page.children) for page in pages)
        pages_used = sum(1 for page in pages if len(page.children) > 0)

        assert total_lines > 0
        assert pages_used > 1  # Should use multiple pages

        print(f"✓ Multi-document test: {total_lines} lines across {pages_used} pages")

    def test_word_spacing_constraint_resolution_integration(self):
        """Test the complete integration from AbstractStyle to Line spacing."""
        page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
        _page = Page(size=(400, 600), style=page_style)

        # Test different constraint scenarios
        test_cases = [
            {
                "name": "explicit_constraints",
                "style": AbstractStyle(word_spacing=5.0, word_spacing_min=3.0, word_spacing_max=12.0),
                "expected_min": 3,
                "expected_max": 12
            },
            {
                "name": "default_constraints",
                "style": AbstractStyle(word_spacing=6.0),
                "expected_min": 6,  # Should use word_spacing as min
                "expected_max": 12  # Should use word_spacing * 2 as max
            },
            {
                "name": "no_word_spacing",
                "style": AbstractStyle(),
                "expected_min": 2,  # Default minimum
                "expected_max": 8   # Default based on font size (16 * 0.5)
            }
        ]

        for case in test_cases:
            # Create fresh real page for each test
            test_page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
            test_page = Page(size=(400, 600), style=test_page_style)
            paragraph = MockParagraph(
                "Testing constraint resolution with different scenarios.",
                case["style"]
            )

            success, _, _ = paragraph_layouter(paragraph, test_page)
            assert success is True
            assert len(test_page.children) > 0

            # Verify constraints were resolved correctly
            line = test_page.children[0]
            min_spacing, max_spacing = line._spacing

            assert min_spacing == case["expected_min"], f"Min constraint failed for {case['name']}"
            assert max_spacing == case["expected_max"], f"Max constraint failed for {case['name']}"

            print(f"✓ {case['name']}: constraints ({min_spacing}, {max_spacing})")

    def test_hyphenation_with_word_spacing_constraints(self):
        """Test that hyphenation works correctly with word spacing constraints."""
        # Create a narrow real page to force hyphenation
        narrow_page_style = PageStyle(border_width=20, padding=(10, 10, 10, 10))
        narrow_page = Page(size=(200, 300), style=narrow_page_style)

        # Create paragraph with long words that will need hyphenation
        paragraph = MockParagraph(
            "supercalifragilisticexpialidocious antidisestablishmentarianism",
            AbstractStyle(word_spacing=3.0, word_spacing_min=2.0, word_spacing_max=8.0)
        )

        success, failed_word_index, remaining_pretext = paragraph_layouter(
            paragraph, narrow_page)

        # Should succeed with hyphenation or handle overflow gracefully
        if success:
            assert len(narrow_page.children) > 0
            print(f"✓ Hyphenation test: {len(narrow_page.children)} lines created")
        else:
            # If it failed, it should be due to layout constraints, not errors
            assert failed_word_index is not None
            print(f"✓ Hyphenation test: Handled overflow at word {failed_word_index}")


if __name__ == "__main__":
    # Run integration tests
    test = TestDocumentLayouterIntegration()

    print("Running document layouter integration tests...")
    print("=" * 50)

    test.test_single_page_layout_with_real_components()
    test.test_multi_page_scenario_with_page_overflow()
    test.test_word_spacing_constraints_in_real_lines()
    test.test_different_alignment_strategies_with_constraints()
    test.test_realistic_document_with_multiple_pages()
    test.test_word_spacing_constraint_resolution_integration()
    test.test_hyphenation_with_word_spacing_constraints()

    print("=" * 50)
    print("✅ All integration tests completed successfully!")
