"""
Unit tests for BlockMD Parser.
"""

import unittest
import sys
import os

# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

from bmd.parser import (
    Block, SourceLocation, BlockMDParser, 
    BlockVisitor, BlockTraverser, parse_blockmd
)


class TestBlock(unittest.TestCase):
    """Test Block AST node functionality."""
    
    def setUp(self):
        """Set up test blocks."""
        self.root = Block("Root", level=1)
        self.root.properties = {"version": "1.0", "type": "root"}
        self.root.content = ["Root content line 1", "Root content line 2"]
        
        self.child1 = Block("Child 1", level=2, parent=self.root)
        self.child1.properties = {"status": "active"}
        self.child1.content = ["Child 1 content"]
        
        self.child2 = Block("Child 2", level=2, parent=self.root)
        
        self.grandchild = Block("Grandchild", level=3, parent=self.child1)
        
        self.root.children = [self.child1, self.child2]
        self.child1.children = [self.grandchild]
    
    def test_get_content_text(self):
        """Test content retrieval."""
        self.assertEqual(
            self.root.get_content_text(), 
            "Root content line 1\nRoot content line 2"
        )
        self.assertEqual(self.child1.get_content_text(), "Child 1 content")
        self.assertEqual(self.child2.get_content_text(), "")
    
    def test_get_property(self):
        """Test property access."""
        self.assertEqual(self.root.get_property("version"), "1.0")
        self.assertEqual(self.root.get_property("type"), "root")
        self.assertIsNone(self.root.get_property("nonexistent"))
        self.assertEqual(self.root.get_property("nonexistent", "default"), "default")
    
    def test_has_property(self):
        """Test property existence check."""
        self.assertTrue(self.root.has_property("version"))
        self.assertTrue(self.child1.has_property("status"))
        self.assertFalse(self.child2.has_property("status"))
    
    def test_get_path(self):
        """Test path retrieval."""
        self.assertEqual(self.root.get_path(), ["Root"])
        self.assertEqual(self.child1.get_path(), ["Root", "Child 1"])
        self.assertEqual(self.grandchild.get_path(), ["Root", "Child 1", "Grandchild"])
    
    def test_get_path_string(self):
        """Test path string formatting."""
        self.assertEqual(self.root.get_path_string(), "Root")
        self.assertEqual(self.child1.get_path_string(), "Root > Child 1")
        self.assertEqual(
            self.grandchild.get_path_string(separator=" / "), 
            "Root / Child 1 / Grandchild"
        )
    
    def test_is_root(self):
        """Test root detection."""
        self.assertTrue(self.root.is_root())
        self.assertFalse(self.child1.is_root())
        self.assertFalse(self.grandchild.is_root())
    
    def test_is_leaf(self):
        """Test leaf detection."""
        self.assertFalse(self.root.is_leaf())
        self.assertFalse(self.child1.is_leaf())
        self.assertTrue(self.child2.is_leaf())
        self.assertTrue(self.grandchild.is_leaf())
    
    def test_get_depth(self):
        """Test depth calculation."""
        self.assertEqual(self.root.get_depth(), 0)
        self.assertEqual(self.child1.get_depth(), 1)
        self.assertEqual(self.child2.get_depth(), 1)
        self.assertEqual(self.grandchild.get_depth(), 2)
    
    def test_to_dict(self):
        """Test dictionary conversion."""
        result = self.root.to_dict()
        self.assertEqual(result["title"], "Root")
        self.assertEqual(result["level"], 1)
        self.assertEqual(result["properties"]["version"], "1.0")
        self.assertEqual(len(result["children"]), 2)


class TestParser(unittest.TestCase):
    """Test BlockMD parser functionality."""
    
    def test_simple_parsing(self):
        """Test parsing simple BlockMD."""
        text = """# Header 1
Content for header 1.

## Header 2
Content for header 2.
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 1)
        self.assertEqual(roots[0].title, "Header 1")
        self.assertEqual(roots[0].level, 1)
        self.assertEqual(len(roots[0].children), 1)
        self.assertEqual(roots[0].children[0].title, "Header 2")
    
    def test_property_parsing(self):
        """Test property parsing."""
        text = """# Task
`status`: TODO
`priority`: high

Task content.
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 1)
        block = roots[0]
        self.assertEqual(block.properties["status"], "TODO")
        self.assertEqual(block.properties["priority"], "high")
        self.assertIn("Task content", block.get_content_text())
    
    def test_property_inheritance(self):
        """Test property inheritance with * syntax."""
        text = """# Parent
`status`: TODO

## Child
`status*`: doing
"""
        roots = parse_blockmd(text)
        
        parent = roots[0]
        child = parent.children[0]
        
        self.assertEqual(parent.properties["status"], "TODO")
        self.assertEqual(child.properties["status"], "TODO/doing")
    
    def test_property_inheritance_without_parent(self):
        """Test property inheritance when parent doesn't have property."""
        text = """# Parent

## Child
`status*`: doing
"""
        roots = parse_blockmd(text)
        
        child = roots[0].children[0]
        self.assertEqual(child.properties["status"], "doing")
    
    def test_property_inheritance_from_grandparent(self):
        """Test property inheritance from grandparent when parent lacks property."""
        text = """# Grandparent
`knowledges`: 408

## Parent
`source`: 2025

### Child
`source*`: 8
`knowledges*`: ds/b树
"""
        roots = parse_blockmd(text)
        
        grandparent = roots[0]
        parent = grandparent.children[0]
        child = parent.children[0]
        
        # Grandparent has knowledges
        self.assertEqual(grandparent.properties["knowledges"], "408")
        
        # Parent has source but not knowledges
        self.assertEqual(parent.properties["source"], "2025")
        self.assertNotIn("knowledges", parent.properties)
        
        # Child should inherit source from parent and knowledges from grandparent
        self.assertEqual(child.properties["source"], "2025/8")
        self.assertEqual(child.properties["knowledges"], "408/ds/b树")
    
    def test_property_inheritance_multiple_levels(self):
        """Test property inheritance through multiple ancestor levels."""
        text = """# Level 1
`tag`: root

## Level 2

### Level 3

#### Level 4
`tag*`: leaf
"""
        roots = parse_blockmd(text)
        
        level1 = roots[0]
        level4 = level1.children[0].children[0].children[0]
        
        # Level 4 should inherit from Level 1 (skipping Level 2 and 3)
        self.assertEqual(level4.properties["tag"], "root/leaf")
    
    def test_multiple_roots(self):
        """Test parsing multiple root blocks."""
        text = """# Root 1

# Root 2

# Root 3
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 3)
        self.assertEqual(roots[0].title, "Root 1")
        self.assertEqual(roots[1].title, "Root 2")
        self.assertEqual(roots[2].title, "Root 3")
    
    def test_nested_structure(self):
        """Test parsing nested block structure."""
        text = """# Level 1
## Level 2
### Level 3
#### Level 4
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 1)
        self.assertEqual(len(roots[0].children), 1)
        self.assertEqual(len(roots[0].children[0].children), 1)
        self.assertEqual(len(roots[0].children[0].children[0].children), 1)
        
        level4 = roots[0].children[0].children[0].children[0]
        self.assertEqual(level4.title, "Level 4")
        self.assertEqual(level4.level, 4)
    
    def test_code_block_handling(self):
        """Test that code blocks are treated as content."""
        text = """# Header
`prop`: value

```python
# This should not be a header
`key`: value
```

More content.
"""
        roots = parse_blockmd(text)
        
        block = roots[0]
        content = block.get_content_text()
        
        self.assertIn("```python", content)
        self.assertIn("# This should not be a header", content)
        self.assertIn("More content", content)
        self.assertEqual(block.properties["prop"], "value")
        self.assertNotIn("key", block.properties)
    
    def test_empty_blocks(self):
        """Test parsing empty blocks."""
        text = """# Header 1

## Header 2

### Header 3
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 1)
        self.assertEqual(roots[0].get_content_text(), "")
    
    def test_source_location(self):
        """Test that source locations are tracked."""
        text = """# Header 1
Content

## Header 2
More content
"""
        roots = parse_blockmd(text)
        
        self.assertIsNotNone(roots[0].location)
        self.assertEqual(roots[0].location.line_start, 1)
        self.assertIsNotNone(roots[0].children[0].location)


class TestVisitor(unittest.TestCase):
    """Test visitor pattern."""
    
    def test_visitor_pattern(self):
        """Test basic visitor functionality."""
        text = """# Root
## Child 1
## Child 2
### Grandchild
"""
        roots = parse_blockmd(text)
        
        # Create a visitor that collects titles
        class TitleCollector(BlockVisitor):
            def __init__(self):
                self.titles = []
            
            def visit_block(self, block):
                self.titles.append(block.title)
        
        visitor = TitleCollector()
        BlockTraverser.traverse_preorder(roots[0], visitor)
        
        self.assertEqual(len(visitor.titles), 4)
        self.assertEqual(visitor.titles[0], "Root")
        self.assertEqual(visitor.titles[1], "Child 1")
    
    def test_postorder_traversal(self):
        """Test post-order traversal."""
        text = """# Root
## Child
"""
        roots = parse_blockmd(text)
        
        class OrderRecorder(BlockVisitor):
            def __init__(self):
                self.order = []
            
            def visit_block(self, block):
                self.order.append(block.title)
        
        visitor = OrderRecorder()
        BlockTraverser.traverse_postorder(roots[0], visitor)
        
        # In post-order, children come before parent
        self.assertEqual(visitor.order, ["Child", "Root"])


class TestEdgeCases(unittest.TestCase):
    """Test edge cases and error conditions."""
    
    def test_empty_text(self):
        """Test parsing empty text."""
        roots = parse_blockmd("")
        self.assertEqual(len(roots), 0)
    
    def test_no_headers(self):
        """Test text with no headers."""
        text = "Just some content without headers."
        roots = parse_blockmd(text)
        self.assertEqual(len(roots), 0)
    
    def test_whitespace_handling(self):
        """Test whitespace in properties and content."""
        text = """# Header
`key`:    value with spaces   

Content with leading space.
"""
        roots = parse_blockmd(text)
        block = roots[0]
        
        self.assertEqual(block.properties["key"], "value with spaces")
        self.assertIn("Content with leading space", block.get_content_text())
    
    def test_special_characters_in_title(self):
        """Test special characters in header titles."""
        text = """# Header with `code` and *emphasis*
"""
        roots = parse_blockmd(text)
        self.assertEqual(roots[0].title, "Header with `code` and *emphasis*")
    
    def test_level_jumping(self):
        """Test jumping header levels (# to ###)."""
        text = """# Level 1
### Level 3
## Level 2
"""
        roots = parse_blockmd(text)
        
        self.assertEqual(len(roots), 1)
        self.assertEqual(len(roots[0].children), 2)
        # Level 3 should be child of Level 1
        self.assertEqual(roots[0].children[0].level, 3)
        # Level 2 should also be child of Level 1
        self.assertEqual(roots[0].children[1].level, 2)


def run_tests():
    """Run all tests."""
    loader = unittest.TestLoader()
    suite = unittest.TestSuite()
    
    suite.addTests(loader.loadTestsFromTestCase(TestBlock))
    suite.addTests(loader.loadTestsFromTestCase(TestParser))
    suite.addTests(loader.loadTestsFromTestCase(TestVisitor))
    suite.addTests(loader.loadTestsFromTestCase(TestEdgeCases))
    
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    return result.wasSuccessful()


if __name__ == '__main__':
    success = run_tests()
    sys.exit(0 if success else 1)
