"""
Unit Tests for ChunkHive Schema
================================

Comprehensive test suite for core data structures.

Run with: pytest tests/test_schema.py -v
"""

import pytest
from typing import Dict, Any
from chunkhive.chunkers.schema import (
    SemanticChunk,
    ASTNodeMetadata,
    HierarchyMetadata,
    SourceLocation,
    # Backward compatibility
    CodeChunk,
    ChunkAST,
    ChunkHierarchy,
    ChunkSpan,
)


class TestSourceLocation:
    """Tests for SourceLocation class."""
    
    def test_basic_creation(self):
        """Test basic SourceLocation creation."""
        loc = SourceLocation(
            start_byte=0,
            end_byte=100,
            start_line=1,
            end_line=5
        )
        assert loc.start_byte == 0
        assert loc.end_byte == 100
        assert loc.start_line == 1
        assert loc.end_line == 5
    
    def test_line_count_property(self):
        """Test line_count calculated property."""
        loc = SourceLocation(start_line=1, end_line=5)
        assert loc.line_count == 5
        
        loc2 = SourceLocation(start_line=10, end_line=10)
        assert loc2.line_count == 1
    
    def test_byte_length_property(self):
        """Test byte_length calculated property."""
        loc = SourceLocation(start_byte=100, end_byte=250)
        assert loc.byte_length == 150
    
    def test_validation(self):
        """Test location validation."""
        valid_loc = SourceLocation(start_byte=0, end_byte=100, start_line=1, end_line=5)
        assert valid_loc.validate() is True
        
        invalid_loc = SourceLocation(start_byte=100, end_byte=50)  # end before start
        assert invalid_loc.validate() is False
        
        invalid_loc2 = SourceLocation(start_line=10, end_line=5)  # end before start
        assert invalid_loc2.validate() is False


class TestASTNodeMetadata:
    """Tests for ASTNodeMetadata class."""
    
    def test_basic_creation(self):
        """Test basic AST metadata creation."""
        ast = ASTNodeMetadata(
            symbol_type="function",
            name="test_function",
            docstring="Test docstring"
        )
        assert ast.symbol_type == "function"
        assert ast.name == "test_function"
        assert ast.docstring == "Test docstring"
    
    def test_has_docstring(self):
        """Test docstring checking."""
        ast_with_doc = ASTNodeMetadata(docstring="Some documentation")
        assert ast_with_doc.has_docstring() is True
        
        ast_without_doc = ASTNodeMetadata(docstring=None)
        assert ast_without_doc.has_docstring() is False
        
        ast_empty_doc = ASTNodeMetadata(docstring="   ")
        assert ast_empty_doc.has_docstring() is False
    
    def test_get_signature(self):
        """Test function signature generation."""
        ast = ASTNodeMetadata(
            symbol_type="function",
            name="calculate",
            parameters=["x: int", "y: int"],
            return_type="int"
        )
        signature = ast.get_signature()
        assert signature == "calculate(x: int, y: int) -> int"
        
        # Test without return type
        ast2 = ASTNodeMetadata(
            symbol_type="function",
            name="process",
            parameters=["data: str"]
        )
        assert ast2.get_signature() == "process(data: str)"
        
        # Test non-function (should return None)
        ast3 = ASTNodeMetadata(symbol_type="class", name="MyClass")
        assert ast3.get_signature() is None


class TestHierarchyMetadata:
    """Tests for HierarchyMetadata class."""
    
    def test_basic_creation(self):
        """Test basic hierarchy creation."""
        hier = HierarchyMetadata(
            parent_id="module_123",
            depth=1,
            is_primary=True
        )
        assert hier.parent_id == "module_123"
        assert hier.depth == 1
        assert hier.is_primary is True
        assert hier.children_ids == []
    
    def test_add_child(self):
        """Test adding children."""
        hier = HierarchyMetadata()
        hier.add_child("child_1")
        hier.add_child("child_2")
        assert len(hier.children_ids) == 2
        assert "child_1" in hier.children_ids
        
        # Adding same child again shouldn't duplicate
        hier.add_child("child_1")
        assert len(hier.children_ids) == 2
    
    def test_get_lineage_path(self):
        """Test lineage path generation."""
        hier = HierarchyMetadata(lineage=["module", "Class", "method"])
        path = hier.get_lineage_path()
        assert path == "module -> Class -> method"
        
        # Empty lineage
        hier2 = HierarchyMetadata()
        assert hier2.get_lineage_path() == "root"


class TestSemanticChunk:
    """Tests for SemanticChunk class."""
    
    @pytest.fixture
    def sample_chunk(self) -> SemanticChunk:
        """Create a sample chunk for testing."""
        return SemanticChunk(
            chunk_id="test_chunk_123",
            file_path="src/test.py",
            language="python",
            chunk_type="function",
            code="def test():\n    pass",
            ast=ASTNodeMetadata(
                symbol_type="function",
                name="test",
                docstring="Test function"
            ),
            span=SourceLocation(
                start_byte=0,
                end_byte=20,
                start_line=1,
                end_line=2
            ),
            metadata={"repo": "test_repo"},
            hierarchy=HierarchyMetadata(depth=1)
        )
    
    def test_basic_creation(self, sample_chunk):
        """Test basic chunk creation."""
        assert sample_chunk.chunk_id == "test_chunk_123"
        assert sample_chunk.language == "python"
        assert sample_chunk.chunk_type == "function"
    
    def test_to_dict(self, sample_chunk):
        """Test conversion to dictionary."""
        chunk_dict = sample_chunk.to_dict()
        assert isinstance(chunk_dict, dict)
        assert chunk_dict["chunk_id"] == "test_chunk_123"
        assert chunk_dict["language"] == "python"
        assert "ast" in chunk_dict
        assert "span" in chunk_dict
        assert "hierarchy" in chunk_dict
    
    def test_from_dict(self, sample_chunk):
        """Test creation from dictionary."""
        chunk_dict = sample_chunk.to_dict()
        reconstructed = SemanticChunk.from_dict(chunk_dict)
        
        assert reconstructed.chunk_id == sample_chunk.chunk_id
        assert reconstructed.language == sample_chunk.language
        assert reconstructed.code == sample_chunk.code
        assert reconstructed.ast.name == sample_chunk.ast.name
    
    def test_get_code_preview(self, sample_chunk):
        """Test code preview generation."""
        # Short code (should return full)
        preview = sample_chunk.get_code_preview(max_lines=5)
        assert preview == sample_chunk.code
        
        # Create chunk with long code
        long_code = "\n".join([f"line {i}" for i in range(10)])
        long_chunk = SemanticChunk(
            chunk_id="long",
            file_path="test.py",
            language="python",
            chunk_type="function",
            code=long_code,
            ast=ASTNodeMetadata(),
            span=SourceLocation()
        )
        preview = long_chunk.get_code_preview(max_lines=3)
        assert "..." in preview
        assert preview.count('\n') <= 4  # 3 lines + "..."
    
    def test_has_embedding(self, sample_chunk):
        """Test embedding detection."""
        assert sample_chunk.has_embedding() is False
        
        sample_chunk.embedding = [0.1, 0.2, 0.3]
        assert sample_chunk.has_embedding() is True
    
    def test_has_ai_analysis(self, sample_chunk):
        """Test AI analysis detection."""
        assert sample_chunk.has_ai_analysis() is False
        
        sample_chunk.ai_analysis = {"complexity": 5}
        assert sample_chunk.has_ai_analysis() is True
    
    def test_get_full_name(self, sample_chunk):
        """Test full name generation."""
        # Function without parent
        assert sample_chunk.get_full_name() == "test"
        
        # Function with parent
        sample_chunk.ast.parent = "MyClass"
        assert sample_chunk.get_full_name() == "MyClass.test"


class TestBackwardCompatibility:
    """Tests for backward compatibility aliases."""
    
    def test_old_names_still_work(self):
        """Ensure old class names still work."""
        # Old names should be aliases to new names
        assert CodeChunk is SemanticChunk
        assert ChunkAST is ASTNodeMetadata
        assert ChunkHierarchy is HierarchyMetadata
        assert ChunkSpan is SourceLocation
    
    def test_can_create_with_old_names(self):
        """Test creating instances with old class names."""
        chunk = CodeChunk(
            chunk_id="test",
            file_path="test.py",
            language="python",
            chunk_type="function",
            code="def test(): pass",
            ast=ChunkAST(name="test"),
            span=ChunkSpan(start_line=1, end_line=1),
            hierarchy=ChunkHierarchy()
        )
        assert isinstance(chunk, SemanticChunk)


class TestIntegration:
    """Integration tests for complete chunk workflows."""
    
    def test_complete_chunk_workflow(self):
        """Test creating, serializing, and deserializing a complete chunk."""
        # Create a complete chunk
        chunk = SemanticChunk(
            chunk_id="func_auth_123",
            file_path="src/auth.py",
            language="python",
            chunk_type="function",
            code="def authenticate(user, password):\n    return True",
            ast=ASTNodeMetadata(
                symbol_type="function",
                name="authenticate",
                parameters=["user: str", "password: str"],
                return_type="bool",
                docstring="Authenticate user credentials",
                decorators=["@login_required"]
            ),
            span=SourceLocation(
                start_byte=100,
                end_byte=250,
                start_line=10,
                end_line=12
            ),
            metadata={
                "repo_info": {"name": "auth_service"},
                "complexity": 5
            },
            hierarchy=HierarchyMetadata(
                parent_id="module_auth",
                depth=1,
                lineage=["module_auth"],
                sibling_index=0
            ),
            embedding=[0.1, 0.2, 0.3, 0.4],
            embedding_model="codebert-base",
            ai_analysis={
                "explanation": "Authenticates user credentials",
                "complexity_score": 5,
                "suggestions": ["Add input validation"]
            }
        )
        
        # Test all properties work
        assert chunk.get_full_name() == "authenticate"
        assert chunk.has_embedding() is True
        assert chunk.has_ai_analysis() is True
        assert chunk.span.validate() is True
        assert chunk.ast.has_docstring() is True
        
        # Serialize
        chunk_dict = chunk.to_dict()
        assert isinstance(chunk_dict, dict)
        assert "embedding" in chunk_dict
        assert "ai_analysis" in chunk_dict
        
        # Deserialize
        reconstructed = SemanticChunk.from_dict(chunk_dict)
        assert reconstructed.chunk_id == chunk.chunk_id
        assert reconstructed.embedding == chunk.embedding
        assert reconstructed.ai_analysis == chunk.ai_analysis


if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
