"""
Unit tests for abstract document elements.

Tests the Document, Chapter, Book, and MetadataType classes that handle
document structure and metadata management.
"""

import unittest
from pyWebLayout.abstract.document import Document, Chapter, Book, MetadataType
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, BlockType
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style import Font, FontWeight, FontStyle, TextDecoration


class TestMetadataType(unittest.TestCase):
    """Test cases for MetadataType enum."""

    def test_metadata_types(self):
        """Test that all expected metadata types exist."""
        expected_types = [
            'TITLE', 'AUTHOR', 'DESCRIPTION', 'KEYWORDS', 'LANGUAGE',
            'PUBLICATION_DATE', 'MODIFIED_DATE', 'PUBLISHER', 'IDENTIFIER',
            'COVER_IMAGE', 'CUSTOM'
        ]

        for type_name in expected_types:
            self.assertTrue(hasattr(MetadataType, type_name))

        # Test custom type has expected value
        self.assertEqual(MetadataType.CUSTOM.value, 100)


class TestDocument(unittest.TestCase):
    """Test cases for Document class."""

    def setUp(self):
        """Set up test fixtures."""
        self.doc = Document("Test Document", "en-US")
        self.font = Font()

    def test_document_creation(self):
        """Test document creation with basic parameters."""
        self.assertEqual(self.doc.get_title(), "Test Document")
        self.assertEqual(self.doc.get_metadata(MetadataType.LANGUAGE), "en-US")
        self.assertEqual(len(self.doc.blocks), 0)

    def test_document_creation_minimal(self):
        """Test document creation with minimal parameters."""
        doc = Document()
        self.assertIsNone(doc.get_title())
        self.assertEqual(doc.get_metadata(MetadataType.LANGUAGE), "en-US")

    def test_metadata_management(self):
        """Test setting and getting metadata."""
        # Set various metadata types
        self.doc.set_metadata(MetadataType.AUTHOR, "John Doe")
        self.doc.set_metadata(MetadataType.DESCRIPTION, "A test document")
        self.doc.set_metadata(MetadataType.KEYWORDS, ["test", "document"])

        # Test retrieval
        self.assertEqual(self.doc.get_metadata(MetadataType.AUTHOR), "John Doe")
        self.assertEqual(
            self.doc.get_metadata(
                MetadataType.DESCRIPTION),
            "A test document")
        self.assertEqual(
            self.doc.get_metadata(
                MetadataType.KEYWORDS), [
                "test", "document"])

        # Test non-existent metadata
        self.assertIsNone(self.doc.get_metadata(MetadataType.PUBLISHER))

    def test_title_convenience_methods(self):
        """Test title getter and setter convenience methods."""
        # Test setting title
        self.doc.set_title("New Title")
        self.assertEqual(self.doc.get_title(), "New Title")

        # Test that it's also in metadata
        self.assertEqual(self.doc.get_metadata(MetadataType.TITLE), "New Title")

    def test_block_management(self):
        """Test adding and managing blocks."""
        # Create some blocks
        para1 = Paragraph()
        para2 = Paragraph()
        heading = Heading(HeadingLevel.H1)

        # Add blocks
        self.doc.add_block(para1)
        self.doc.add_block(heading)
        self.doc.add_block(para2)

        # Test blocks list
        self.assertEqual(len(self.doc.blocks), 3)
        self.assertEqual(self.doc.blocks[0], para1)
        self.assertEqual(self.doc.blocks[1], heading)
        self.assertEqual(self.doc.blocks[2], para2)

    def test_anchor_management(self):
        """Test named anchor functionality."""
        heading = Heading(HeadingLevel.H1)
        para = Paragraph()

        # Add anchors
        self.doc.add_anchor("intro", heading)
        self.doc.add_anchor("content", para)

        # Test retrieval
        self.assertEqual(self.doc.get_anchor("intro"), heading)
        self.assertEqual(self.doc.get_anchor("content"), para)
        self.assertIsNone(self.doc.get_anchor("nonexistent"))

    def test_resource_management(self):
        """Test document resource management."""
        # Add various resources
        self.doc.add_resource("image1", {"type": "image", "path": "test.jpg"})
        self.doc.add_resource("style1", {"type": "css", "content": "body {}"})

        # Test retrieval
        image = self.doc.get_resource("image1")
        self.assertEqual(image["type"], "image")
        self.assertEqual(image["path"], "test.jpg")

        style = self.doc.get_resource("style1")
        self.assertEqual(style["type"], "css")

        # Test non-existent resource
        self.assertIsNone(self.doc.get_resource("nonexistent"))

    def test_stylesheet_management(self):
        """Test stylesheet addition."""
        # Add stylesheets
        css1 = {"href": "style.css", "type": "text/css"}
        css2 = {"href": "theme.css", "type": "text/css"}

        self.doc.add_stylesheet(css1)
        self.doc.add_stylesheet(css2)

        # Test that stylesheets are stored
        self.assertEqual(len(self.doc._stylesheets), 2)
        self.assertEqual(self.doc._stylesheets[0], css1)
        self.assertEqual(self.doc._stylesheets[1], css2)

    def test_script_management(self):
        """Test script addition."""
        # Add scripts
        script1 = "console.log('Hello');"
        script2 = "document.ready(function(){});"

        self.doc.add_script(script1)
        self.doc.add_script(script2)

        # Test that scripts are stored
        self.assertEqual(len(self.doc._scripts), 2)
        self.assertEqual(self.doc._scripts[0], script1)
        self.assertEqual(self.doc._scripts[1], script2)

    def test_find_blocks_by_type(self):
        """Test finding blocks by type."""
        # Create blocks of different types
        para1 = Paragraph()
        para2 = Paragraph()
        heading1 = Heading(HeadingLevel.H1)
        heading2 = Heading(HeadingLevel.H2)

        # Add blocks to document
        self.doc.add_block(para1)
        self.doc.add_block(heading1)
        self.doc.add_block(para2)
        self.doc.add_block(heading2)

        # Test finding paragraphs
        paragraphs = self.doc.find_blocks_by_type(BlockType.PARAGRAPH)
        self.assertEqual(len(paragraphs), 2)
        self.assertIn(para1, paragraphs)
        self.assertIn(para2, paragraphs)

        # Test finding headings
        headings = self.doc.find_blocks_by_type(BlockType.HEADING)
        self.assertEqual(len(headings), 2)
        self.assertIn(heading1, headings)
        self.assertIn(heading2, headings)

    def test_find_headings(self):
        """Test finding heading blocks specifically."""
        # Create mixed blocks
        para = Paragraph()
        h1 = Heading(HeadingLevel.H1)
        h2 = Heading(HeadingLevel.H2)

        # Add words to headings for title extraction
        word1 = Word("Chapter", self.font)
        word2 = Word("One", self.font)
        h1.add_word(word1)
        h1.add_word(word2)

        word3 = Word("Section", self.font)
        h2.add_word(word3)

        self.doc.add_block(para)
        self.doc.add_block(h1)
        self.doc.add_block(h2)

        # Test finding headings
        headings = self.doc.find_headings()
        self.assertEqual(len(headings), 2)
        self.assertIn(h1, headings)
        self.assertIn(h2, headings)
        self.assertNotIn(para, headings)

    def test_generate_table_of_contents(self):
        """Test table of contents generation."""
        # Create headings with content
        h1 = Heading(HeadingLevel.H1)
        h2 = Heading(HeadingLevel.H2)
        h3 = Heading(HeadingLevel.H3)

        # Add words to headings
        h1.add_word(Word("Introduction", self.font))
        h2.add_word(Word("Getting", self.font))
        h2.add_word(Word("Started", self.font))
        h3.add_word(Word("Installation", self.font))

        self.doc.add_block(h1)
        self.doc.add_block(h2)
        self.doc.add_block(h3)

        # Generate TOC
        toc = self.doc.generate_table_of_contents()

        # Test TOC structure
        self.assertEqual(len(toc), 3)

        # Test first entry
        level, title, block = toc[0]
        self.assertEqual(level, 1)  # H1
        self.assertEqual(title, "Introduction")
        self.assertEqual(block, h1)

        # Test second entry
        level, title, block = toc[1]
        self.assertEqual(level, 2)  # H2
        self.assertEqual(title, "Getting Started")
        self.assertEqual(block, h2)

        # Test third entry
        level, title, block = toc[2]
        self.assertEqual(level, 3)  # H3
        self.assertEqual(title, "Installation")
        self.assertEqual(block, h3)


class TestChapter(unittest.TestCase):
    """Test cases for Chapter class."""

    def setUp(self):
        """Set up test fixtures."""
        self.chapter = Chapter("Test Chapter", 1)

    def test_chapter_creation(self):
        """Test chapter creation."""
        self.assertEqual(self.chapter.title, "Test Chapter")
        self.assertEqual(self.chapter.level, 1)
        self.assertEqual(len(self.chapter.blocks), 0)

    def test_chapter_creation_minimal(self):
        """Test chapter creation with minimal parameters."""
        chapter = Chapter()
        self.assertIsNone(chapter.title)
        self.assertEqual(chapter.level, 1)

    def test_title_property(self):
        """Test title property getter and setter."""
        # Test setter
        self.chapter.title = "New Chapter Title"
        self.assertEqual(self.chapter.title, "New Chapter Title")

        # Test setting to None
        self.chapter.title = None
        self.assertIsNone(self.chapter.title)

    def test_level_property(self):
        """Test level property."""
        self.assertEqual(self.chapter.level, 1)

        # Level should be read-only (no setter test)
        # This is by design based on the class definition

    def test_block_management(self):
        """Test adding blocks to chapter."""
        para1 = Paragraph()
        para2 = Paragraph()
        heading = Heading(HeadingLevel.H2)

        # Add blocks
        self.chapter.add_block(para1)
        self.chapter.add_block(heading)
        self.chapter.add_block(para2)

        # Test blocks list
        self.assertEqual(len(self.chapter.blocks), 3)
        self.assertEqual(self.chapter.blocks[0], para1)
        self.assertEqual(self.chapter.blocks[1], heading)
        self.assertEqual(self.chapter.blocks[2], para2)

    def test_metadata_management(self):
        """Test chapter metadata."""
        # Set metadata
        self.chapter.set_metadata("author", "Jane Doe")
        self.chapter.set_metadata("word_count", 1500)
        self.chapter.set_metadata("tags", ["intro", "basics"])

        # Test retrieval
        self.assertEqual(self.chapter.get_metadata("author"), "Jane Doe")
        self.assertEqual(self.chapter.get_metadata("word_count"), 1500)
        self.assertEqual(self.chapter.get_metadata("tags"), ["intro", "basics"])

        # Test non-existent metadata
        self.assertIsNone(self.chapter.get_metadata("nonexistent"))


class TestBook(unittest.TestCase):
    """Test cases for Book class."""

    def setUp(self):
        """Set up test fixtures."""
        self.book = Book("Test Book", "Author Name", "en-US")

    def test_book_creation(self):
        """Test book creation with all parameters."""
        self.assertEqual(self.book.get_title(), "Test Book")
        self.assertEqual(self.book.get_author(), "Author Name")
        self.assertEqual(self.book.get_metadata(MetadataType.LANGUAGE), "en-US")
        self.assertEqual(len(self.book.chapters), 0)

    def test_book_creation_minimal(self):
        """Test book creation with minimal parameters."""
        book = Book()
        self.assertIsNone(book.get_title())
        self.assertIsNone(book.get_author())
        self.assertEqual(book.get_metadata(MetadataType.LANGUAGE), "en-US")

    def test_book_creation_partial(self):
        """Test book creation with partial parameters."""
        book = Book(title="Just Title")
        self.assertEqual(book.get_title(), "Just Title")
        self.assertIsNone(book.get_author())

    def test_author_convenience_methods(self):
        """Test author getter and setter convenience methods."""
        # Test setting author
        self.book.set_author("New Author")
        self.assertEqual(self.book.get_author(), "New Author")

        # Test that it's also in metadata
        self.assertEqual(self.book.get_metadata(MetadataType.AUTHOR), "New Author")

    def test_chapter_management(self):
        """Test adding and managing chapters."""
        # Create chapters
        ch1 = Chapter("Introduction", 1)
        ch2 = Chapter("Getting Started", 1)
        ch3 = Chapter("Advanced Topics", 1)

        # Add chapters
        self.book.add_chapter(ch1)
        self.book.add_chapter(ch2)
        self.book.add_chapter(ch3)

        # Test chapters list
        self.assertEqual(len(self.book.chapters), 3)
        self.assertEqual(self.book.chapters[0], ch1)
        self.assertEqual(self.book.chapters[1], ch2)
        self.assertEqual(self.book.chapters[2], ch3)

    def test_create_chapter(self):
        """Test creating chapters through the book."""
        # Create chapter with title and level
        ch1 = self.book.create_chapter("Chapter 1", 1)
        self.assertEqual(ch1.title, "Chapter 1")
        self.assertEqual(ch1.level, 1)
        self.assertEqual(len(self.book.chapters), 1)
        self.assertEqual(self.book.chapters[0], ch1)

        # Create chapter with minimal parameters
        ch2 = self.book.create_chapter()
        self.assertIsNone(ch2.title)
        self.assertEqual(ch2.level, 1)
        self.assertEqual(len(self.book.chapters), 2)

    def test_generate_book_toc(self):
        """Test table of contents generation for book."""
        # Create chapters with different levels
        ch1 = Chapter("Introduction", 1)
        ch2 = Chapter("Getting Started", 1)
        ch3 = Chapter("Basic Concepts", 2)
        ch4 = Chapter("Advanced Topics", 1)
        ch5 = Chapter("Best Practices", 2)

        # Add chapters to book
        self.book.add_chapter(ch1)
        self.book.add_chapter(ch2)
        self.book.add_chapter(ch3)
        self.book.add_chapter(ch4)
        self.book.add_chapter(ch5)

        # Generate TOC
        toc = self.book.generate_table_of_contents()

        # Test TOC structure
        self.assertEqual(len(toc), 5)

        # Test entries
        expected = [
            (1, "Introduction", ch1),
            (1, "Getting Started", ch2),
            (2, "Basic Concepts", ch3),
            (1, "Advanced Topics", ch4),
            (2, "Best Practices", ch5)
        ]

        for i, (exp_level, exp_title, exp_chapter) in enumerate(expected):
            level, title, chapter = toc[i]
            self.assertEqual(level, exp_level)
            self.assertEqual(title, exp_title)
            self.assertEqual(chapter, exp_chapter)

    def test_generate_book_toc_with_untitled_chapters(self):
        """Test TOC generation with chapters that have no title."""
        # Create chapters, some without titles
        ch1 = Chapter("Introduction", 1)
        ch2 = Chapter(None, 1)  # No title
        ch3 = Chapter("Conclusion", 1)

        self.book.add_chapter(ch1)
        self.book.add_chapter(ch2)
        self.book.add_chapter(ch3)

        # Generate TOC
        toc = self.book.generate_table_of_contents()

        # Should only include chapters with titles
        self.assertEqual(len(toc), 2)

        level, title, chapter = toc[0]
        self.assertEqual(title, "Introduction")
        self.assertEqual(chapter, ch1)

        level, title, chapter = toc[1]
        self.assertEqual(title, "Conclusion")
        self.assertEqual(chapter, ch3)

    def test_book_inherits_document_features(self):
        """Test that Book inherits all Document functionality."""
        # Test that book can use all document methods
        # Add blocks directly to book
        para = Paragraph()
        self.book.add_block(para)
        self.assertEqual(len(self.book.blocks), 1)

        # Test metadata
        self.book.set_metadata(MetadataType.PUBLISHER, "Test Publisher")
        self.assertEqual(
            self.book.get_metadata(
                MetadataType.PUBLISHER),
            "Test Publisher")

        # Test anchors
        heading = Heading(HeadingLevel.H1)
        self.book.add_anchor("preface", heading)
        self.assertEqual(self.book.get_anchor("preface"), heading)


class TestDocumentFontRegistry(unittest.TestCase):
    """Test cases for Document font registry functionality."""

    def setUp(self):
        """Set up test fixtures."""
        self.doc = Document("Test Document", "en-US")

    def test_get_or_create_font_creates_new_font(self):
        """Test that get_or_create_font creates a new font when none exists."""
        font = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        self.assertEqual(font.font_size, 14)
        self.assertEqual(font.colour, (255, 0, 0))
        self.assertEqual(font.weight, FontWeight.BOLD)

        # Check that font is stored in registry
        self.assertEqual(len(self.doc._fonts), 1)

    def test_get_or_create_font_reuses_existing_font(self):
        """Test that get_or_create_font reuses existing fonts."""
        # Create first font
        font1 = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create second font with same properties
        font2 = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Should return the same font object
        self.assertIs(font1, font2)

        # Should only have one font in registry
        self.assertEqual(len(self.doc._fonts), 1)

    def test_get_or_create_font_creates_different_fonts(self):
        """Test that different font properties create different fonts."""
        # Create first font
        font1 = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create font with different size
        font2 = self.doc.get_or_create_font(
            font_size=16,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create font with different color
        font3 = self.doc.get_or_create_font(
            font_size=14,
            colour=(0, 255, 0),
            weight=FontWeight.BOLD
        )

        # Create font with different weight
        font4 = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.NORMAL
        )

        # All should be different objects
        self.assertIsNot(font1, font2)
        self.assertIsNot(font1, font3)
        self.assertIsNot(font1, font4)
        self.assertIsNot(font2, font3)
        self.assertIsNot(font2, font4)
        self.assertIsNot(font3, font4)

        # Should have four fonts in registry
        self.assertEqual(len(self.doc._fonts), 4)

    def test_get_or_create_font_with_all_parameters(self):
        """Test get_or_create_font with all parameters."""
        font = self.doc.get_or_create_font(
            font_path="path/to/font.ttf",
            font_size=18,
            colour=(128, 64, 192),
            weight=FontWeight.BOLD,
            style=FontStyle.ITALIC,
            decoration=TextDecoration.UNDERLINE,
            background=(255, 255, 255, 128),
            language="fr_FR",
            min_hyphenation_width=80
        )

        self.assertEqual(font._font_path, "path/to/font.ttf")
        self.assertEqual(font.font_size, 18)
        self.assertEqual(font.colour, (128, 64, 192))
        self.assertEqual(font.weight, FontWeight.BOLD)
        self.assertEqual(font.style, FontStyle.ITALIC)
        self.assertEqual(font.decoration, TextDecoration.UNDERLINE)
        self.assertEqual(font.background, (255, 255, 255, 128))
        self.assertEqual(font.language, "fr_FR")
        self.assertEqual(font.min_hyphenation_width, 80)

    def test_get_or_create_font_with_defaults(self):
        """Test get_or_create_font with default values."""
        font = self.doc.get_or_create_font()

        # Should create font with default values
        self.assertIsNotNone(font)
        self.assertEqual(font.font_size, 16)  # Default font size
        self.assertEqual(font.colour, (0, 0, 0))  # Default black color
        self.assertEqual(font.weight, FontWeight.NORMAL)
        self.assertEqual(font.style, FontStyle.NORMAL)
        self.assertEqual(font.decoration, TextDecoration.NONE)


class TestChapterFontRegistry(unittest.TestCase):
    """Test cases for Chapter font registry functionality."""

    def setUp(self):
        """Set up test fixtures."""
        self.doc = Document("Test Document", "en-US")
        self.chapter = Chapter("Test Chapter", 1, parent=self.doc)

    def test_chapter_uses_parent_font_registry(self):
        """Test that chapter uses parent document's font registry."""
        # Create font through chapter - should delegate to parent
        font1 = self.chapter.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create same font through document - should return same object
        font2 = self.doc.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Should be the same font object
        self.assertIs(font1, font2)

        # Should be stored in document's registry, not chapter's
        self.assertEqual(len(self.doc._fonts), 1)
        self.assertEqual(len(self.chapter._fonts), 0)

    def test_chapter_without_parent_manages_own_fonts(self):
        """Test that chapter without parent manages its own fonts."""
        # Create chapter without parent
        standalone_chapter = Chapter("Standalone Chapter", 1)

        # Create font through chapter
        font1 = standalone_chapter.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create same font again - should reuse
        font2 = standalone_chapter.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Should be the same font object
        self.assertIs(font1, font2)

        # Should be stored in chapter's own registry
        self.assertEqual(len(standalone_chapter._fonts), 1)

    def test_chapter_parent_assignment(self):
        """Test that chapter parent assignment works correctly."""
        # Create chapter with parent
        chapter_with_parent = Chapter("Chapter with Parent", 1, parent=self.doc)
        self.assertEqual(chapter_with_parent._parent, self.doc)

        # Create chapter without parent
        chapter_without_parent = Chapter("Chapter without Parent", 1)
        self.assertIsNone(chapter_without_parent._parent)


class TestBookFontRegistry(unittest.TestCase):
    """Test cases for Book font registry functionality."""

    def setUp(self):
        """Set up test fixtures."""
        self.book = Book("Test Book", "Author Name", "en-US")

    def test_book_inherits_document_font_registry(self):
        """Test that Book inherits Document's font registry functionality."""
        # Create font through book
        font1 = self.book.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Create same font again - should reuse
        font2 = self.book.get_or_create_font(
            font_size=14,
            colour=(255, 0, 0),
            weight=FontWeight.BOLD
        )

        # Should be the same font object
        self.assertIs(font1, font2)

        # Should have one font in registry
        self.assertEqual(len(self.book._fonts), 1)


if __name__ == '__main__':
    unittest.main()
