"""Tests for core functionality."""

import pytest
import asyncio
from semantic_comparer.core import (
    split_paragraphs,
    calculate_containment_score,
    AlignmentPair,
    SemanticAligner,
)


def test_split_paragraphs():
    """Test paragraph splitting functionality."""
    text = "Paragraph 1.\n\nParagraph 2.\n\n\nParagraph 3."
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 3
    assert paragraphs[0] == "Paragraph 1."
    assert paragraphs[1] == "Paragraph 2."
    assert paragraphs[2] == "Paragraph 3."


def test_split_paragraphs_empty():
    """Test paragraph splitting with empty text."""
    text = ""
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 0


def test_split_paragraphs_single_paragraph():
    """Test paragraph splitting with single paragraph."""
    text = "This is a single paragraph without any line breaks."
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 1
    assert paragraphs[0] == "This is a single paragraph without any line breaks."


def test_split_paragraphs_multiple_empty_lines():
    """Test paragraph splitting with multiple consecutive empty lines."""
    text = "First paragraph.\n\n\n\n\nSecond paragraph.\n\n\nThird paragraph."
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 3
    assert paragraphs[0] == "First paragraph."
    assert paragraphs[1] == "Second paragraph."
    assert paragraphs[2] == "Third paragraph."


def test_split_paragraphs_whitespace_only():
    """Test paragraph splitting with whitespace-only lines."""
    text = "   \n\nParagraph.\n\n   \n\nAnother paragraph."
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 2
    assert paragraphs[0] == "Paragraph."
    assert paragraphs[1] == "Another paragraph."


def test_split_paragraphs_unicode():
    """Test paragraph splitting with Unicode characters."""
    text = "中文段落1。\n\nEnglish paragraph.\n\nFrançais paragraphe."
    paragraphs = split_paragraphs(text)
    assert len(paragraphs) == 3
    assert paragraphs[0] == "中文段落1。"
    assert paragraphs[1] == "English paragraph."
    assert paragraphs[2] == "Français paragraphe."


def test_calculate_containment_score():
    """Test containment score calculation."""
    paragraphs_a = ["Text A1", "Text A2", "Text A3"]
    
    # Perfect matches
    pairs = [
        AlignmentPair(type="match", text_a="Text A1", text_b="Text B1", score=0.9),
        AlignmentPair(type="match", text_a="Text A2", text_b="Text B2", score=0.8),
        AlignmentPair(type="match", text_a="Text A3", text_b="Text B3", score=0.7),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert 0.7 <= score <= 0.9  # Weighted average should be in this range


def test_calculate_containment_score_no_matches():
    """Test containment score with no matches."""
    paragraphs_a = ["Text A1", "Text A2"]
    pairs = [
        AlignmentPair(type="gap_b", text_a="Text A1"),
        AlignmentPair(type="gap_b", text_a="Text A2"),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert score == 0.0


def test_calculate_containment_score_mixed_matches():
    """Test containment score with mixed match types."""
    paragraphs_a = ["Short", "Medium length text", "Very long text with many words"]
    
    pairs = [
        AlignmentPair(type="match", text_a="Short", text_b="Brief", score=0.8),
        AlignmentPair(type="gap_b", text_a="Medium length text"),
        AlignmentPair(type="match", text_a="Very long text with many words", text_b="Long text", score=0.6),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    # Should be weighted by character length
    assert 0.0 < score < 1.0


def test_calculate_containment_score_empty_paragraphs():
    """Test containment score with empty paragraphs."""
    paragraphs_a = []
    pairs = []
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert score == 1.0  # Empty input should return 1.0


def test_calculate_containment_score_negative_scores():
    """Test containment score with negative similarity scores."""
    paragraphs_a = ["Text A1", "Text A2"]
    
    pairs = [
        AlignmentPair(type="match", text_a="Text A1", text_b="Text B1", score=-0.5),
        AlignmentPair(type="match", text_a="Text A2", text_b="Text B2", score=0.3),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    # Negative scores should be treated as 0
    assert score >= 0.0


def test_calculate_containment_score_zero_length_text():
    """Test containment score with zero-length text."""
    paragraphs_a = ["", "Normal text"]
    
    pairs = [
        AlignmentPair(type="match", text_a="", text_b="Something", score=0.5),
        AlignmentPair(type="match", text_a="Normal text", text_b="Other text", score=0.8),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert score >= 0.0


@pytest.mark.asyncio
async def test_semantic_aligner_initialization():
    """Test SemanticAligner initialization."""
    aligner = SemanticAligner(
        model_name="paraphrase-multilingual-MiniLM-L12-v2",
        gap_penalty=0.3,
        similarity_threshold=0.5
    )
    
    assert aligner.model_name == "paraphrase-multilingual-MiniLM-L12-v2"
    assert aligner.gap_penalty == 0.3
    assert aligner.similarity_threshold == 0.5
    assert aligner._model is None


@pytest.mark.asyncio
async def test_semantic_aligner_simple_alignment():
    """Test simple text alignment."""
    aligner = SemanticAligner()
    
    text_a = "This is the first paragraph.\n\nThis is the second paragraph."
    text_b = "This is the first paragraph.\n\nThis is a different second paragraph."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 2
    assert result.total_paragraphs_b == 2
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0
    assert len(result.pairs) > 0


@pytest.mark.asyncio
async def test_semantic_aligner_empty_texts():
    """Test alignment with empty texts."""
    aligner = SemanticAligner()
    
    # Both texts empty
    result = await aligner.align_texts("", "")
    assert result.total_paragraphs_a == 0
    assert result.total_paragraphs_b == 0
    assert result.containment_score == 1.0
    assert len(result.pairs) == 0
    
    # One text empty
    result = await aligner.align_texts("Some text", "")
    assert result.total_paragraphs_a == 1
    assert result.total_paragraphs_b == 0
    assert result.containment_score == 0.0
    
    result = await aligner.align_texts("", "Some text")
    assert result.total_paragraphs_a == 0
    assert result.total_paragraphs_b == 1


@pytest.mark.asyncio
async def test_semantic_aligner_identical_texts():
    """Test alignment with identical texts."""
    aligner = SemanticAligner()
    
    text = "This is identical text.\n\nThis is also identical."
    result = await aligner.align_texts(text, text)
    
    assert result.total_paragraphs_a == 2
    assert result.total_paragraphs_b == 2
    assert result.containment_score > 0.8  # Should be very high
    assert result.matched_pairs == 2


@pytest.mark.asyncio
async def test_semantic_aligner_completely_different_texts():
    """Test alignment with completely different texts."""
    aligner = SemanticAligner()
    
    text_a = "This is about machine learning and AI."
    text_b = "The weather is sunny today with clear skies."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 1
    assert result.total_paragraphs_b == 1
    assert result.containment_score < 0.5  # Should be low
    assert result.matched_pairs == 0 or result.matched_pairs == 1


@pytest.mark.asyncio
async def test_semantic_aligner_multilingual_texts():
    """Test alignment with multilingual texts."""
    aligner = SemanticAligner()
    
    text_a = "Machine learning is a subset of artificial intelligence.\n\nDeep learning uses neural networks."
    text_b = "机器学习是人工智能的一个子集。\n\n深度学习使用神经网络。"
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 2
    assert result.total_paragraphs_b == 2
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_long_texts():
    """Test alignment with longer texts."""
    aligner = SemanticAligner()
    
    text_a = """Machine learning is a subset of artificial intelligence that focuses on the development of computer programs that can access data and use it to learn for themselves.

Deep learning is a subset of machine learning that uses neural networks with multiple layers to model and understand complex patterns.

Natural language processing is a field of AI that focuses on the interaction between computers and human language."""
    
    text_b = """Machine learning is part of AI that creates systems that can learn from data automatically.

Neural networks with many layers are used in deep learning to understand complex patterns.

NLP is an AI field that deals with computer understanding of human language.

Computer vision is another important AI field."""
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 4
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0
    assert result.matched_pairs >= 0


@pytest.mark.asyncio
async def test_semantic_aligner_different_gap_penalties():
    """Test alignment with different gap penalty values."""
    text_a = "First paragraph.\n\nSecond paragraph."
    text_b = "First paragraph.\n\nThird paragraph."
    
    # Low gap penalty - more likely to create gaps
    aligner_low = SemanticAligner(gap_penalty=0.1)
    result_low = await aligner_low.align_texts(text_a, text_b)
    
    # High gap penalty - less likely to create gaps
    aligner_high = SemanticAligner(gap_penalty=0.8)
    result_high = await aligner_high.align_texts(text_a, text_b)
    
    # Both should produce valid results
    assert result_low.total_paragraphs_a == 2
    assert result_low.total_paragraphs_b == 2
    assert result_high.total_paragraphs_a == 2
    assert result_high.total_paragraphs_b == 2


@pytest.mark.asyncio
async def test_semantic_aligner_different_similarity_thresholds():
    """Test alignment with different similarity thresholds."""
    text_a = "This is a test paragraph."
    text_b = "This is a similar test paragraph."
    
    # Low threshold - more matches
    aligner_low = SemanticAligner(similarity_threshold=0.1)
    result_low = await aligner_low.align_texts(text_a, text_b)
    
    # High threshold - fewer matches
    aligner_high = SemanticAligner(similarity_threshold=0.9)
    result_high = await aligner_high.align_texts(text_a, text_b)
    
    # Both should produce valid results
    assert result_low.total_paragraphs_a == 1
    assert result_low.total_paragraphs_b == 1
    assert result_high.total_paragraphs_a == 1
    assert result_high.total_paragraphs_b == 1


@pytest.mark.asyncio
async def test_semantic_aligner_special_characters():
    """Test alignment with special characters and symbols."""
    aligner = SemanticAligner()
    
    text_a = "Text with special chars: @#$%^&*()\n\nNumbers: 12345\n\nEmojis: 🚀📚💻"
    text_b = "Text with symbols: @#$%^&*()\n\nDigits: 12345\n\nIcons: 🚀📚💻"
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_very_short_texts():
    """Test alignment with very short texts."""
    aligner = SemanticAligner()
    
    text_a = "Hi."
    text_b = "Hello."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 1
    assert result.total_paragraphs_b == 1
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_repeated_content():
    """Test alignment with repeated content."""
    aligner = SemanticAligner()
    
    text_a = "Same text.\n\nSame text.\n\nDifferent text."
    text_b = "Same text.\n\nDifferent text.\n\nSame text."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_model_loading():
    """Test that model loading works correctly."""
    aligner = SemanticAligner()
    
    # First call should load the model
    text_a = "Test text."
    text_b = "Another test."
    
    result1 = await aligner.align_texts(text_a, text_b)
    assert aligner._model is not None
    
    # Second call should reuse the loaded model
    result2 = await aligner.align_texts(text_a, text_b)
    assert aligner._model is not None
    
    # Both results should be valid
    assert result1.total_paragraphs_a == 1
    assert result1.total_paragraphs_b == 1
    assert result2.total_paragraphs_a == 1
    assert result2.total_paragraphs_b == 1


def test_alignment_pair_creation():
    """Test AlignmentPair dataclass creation."""
    # Match pair
    match_pair = AlignmentPair(
        type="match",
        text_a="Text A",
        text_b="Text B",
        score=0.85
    )
    assert match_pair.type == "match"
    assert match_pair.text_a == "Text A"
    assert match_pair.text_b == "Text B"
    assert match_pair.score == 0.85
    
    # Gap pair
    gap_pair = AlignmentPair(type="gap_b", text_a="Text A")
    assert gap_pair.type == "gap_b"
    assert gap_pair.text_a == "Text A"
    assert gap_pair.text_b is None
    assert gap_pair.score is None


def test_alignment_pair_types():
    """Test all possible alignment pair types."""
    types = ["match", "gap_a", "gap_b", "unaligned_a", "unaligned_b"]
    
    for pair_type in types:
        pair = AlignmentPair(type=pair_type)
        assert pair.type == pair_type


@pytest.mark.asyncio
async def test_semantic_aligner_edge_case_single_character():
    """Test alignment with single character texts."""
    aligner = SemanticAligner()
    
    result = await aligner.align_texts("A", "B")
    
    assert result.total_paragraphs_a == 1
    assert result.total_paragraphs_b == 1
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_whitespace_only():
    """Test alignment with whitespace-only texts."""
    aligner = SemanticAligner()
    
    result = await aligner.align_texts("   \n\n  ", "  \n\n   ")
    
    assert result.total_paragraphs_a == 0
    assert result.total_paragraphs_b == 0
    assert result.containment_score == 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_statistics_consistency():
    """Test that alignment statistics are consistent."""
    aligner = SemanticAligner()
    
    text_a = "First.\n\nSecond.\n\nThird."
    text_b = "First.\n\nFourth.\n\nFifth."
    
    result = await aligner.align_texts(text_a, text_b)
    
    # Check that statistics add up correctly
    total_pairs = result.matched_pairs + result.gap_pairs + result.unaligned_pairs
    assert total_pairs == len(result.pairs)
    
    # Check that counts are non-negative
    assert result.matched_pairs >= 0
    assert result.gap_pairs >= 0
    assert result.unaligned_pairs >= 0
    
    # Check that total paragraphs are correct
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3


@pytest.mark.asyncio
async def test_semantic_aligner_extremely_long_paragraphs():
    """Test alignment with extremely long paragraphs."""
    aligner = SemanticAligner()
    
    # Create very long paragraphs
    long_paragraph_a = "This is a very long paragraph. " * 100
    long_paragraph_b = "This is a very long paragraph. " * 100
    
    text_a = f"{long_paragraph_a}\n\nAnother long paragraph."
    text_b = f"{long_paragraph_b}\n\nAnother long paragraph."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 2
    assert result.total_paragraphs_b == 2
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0 + 1e-6  # Allow for floating-point precision


@pytest.mark.asyncio
async def test_semantic_aligner_numeric_content():
    """Test alignment with numeric content."""
    aligner = SemanticAligner()
    
    text_a = "The number is 42.\n\nThe price is $99.99.\n\nThe percentage is 75.5%."
    text_b = "The number is 42.\n\nThe cost is $99.99.\n\nThe rate is 75.5%."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_html_like_content():
    """Test alignment with HTML-like content."""
    aligner = SemanticAligner()
    
    text_a = "<h1>Title</h1>\n\n<p>This is a paragraph.</p>\n\n<div>Content here.</div>"
    text_b = "<h1>Title</h1>\n\n<p>This is a paragraph.</p>\n\n<span>Content here.</span>"
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3
    assert result.containment_score >= 0.0
    assert result.containment_score <= 1.0


@pytest.mark.asyncio
async def test_semantic_aligner_code_like_content():
    """Test alignment with code-like content."""
    aligner = SemanticAligner()
    
    text_a = "def hello_world():\n    print('Hello')\n\nclass MyClass:\n    pass"
    text_b = "def hello_world():\n    print('Hello')\n\nclass MyClass:\n    pass"
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 2
    assert result.total_paragraphs_b == 2
    assert result.containment_score > 0.8  # Should be very high for identical code


@pytest.mark.asyncio
async def test_semantic_aligner_mixed_language_content():
    """Test alignment with mixed language content."""
    aligner = SemanticAligner()
    
    text_a = "Hello world.\n\n你好世界。\n\nBonjour le monde."
    text_b = "Hello world.\n\n你好世界。\n\nBonjour le monde."
    
    result = await aligner.align_texts(text_a, text_b)
    
    assert result.total_paragraphs_a == 3
    assert result.total_paragraphs_b == 3
    assert result.containment_score > 0.8  # Should be very high for identical content


@pytest.mark.asyncio
async def test_semantic_aligner_parameter_boundaries():
    """Test alignment with parameter boundary values."""
    aligner = SemanticAligner()
    
    text_a = "Test text."
    text_b = "Test text."
    
    # Test with minimum gap penalty
    aligner_min_gap = SemanticAligner(gap_penalty=0.0)
    result_min = await aligner_min_gap.align_texts(text_a, text_b)
    assert result_min.total_paragraphs_a == 1
    assert result_min.total_paragraphs_b == 1
    
    # Test with maximum gap penalty
    aligner_max_gap = SemanticAligner(gap_penalty=1.0)
    result_max = await aligner_max_gap.align_texts(text_a, text_b)
    assert result_max.total_paragraphs_a == 1
    assert result_max.total_paragraphs_b == 1
    
    # Test with minimum similarity threshold
    aligner_min_sim = SemanticAligner(similarity_threshold=0.0)
    result_min_sim = await aligner_min_sim.align_texts(text_a, text_b)
    assert result_min_sim.total_paragraphs_a == 1
    assert result_min_sim.total_paragraphs_b == 1
    
    # Test with maximum similarity threshold
    aligner_max_sim = SemanticAligner(similarity_threshold=1.0)
    result_max_sim = await aligner_max_sim.align_texts(text_a, text_b)
    assert result_max_sim.total_paragraphs_a == 1
    assert result_max_sim.total_paragraphs_b == 1


@pytest.mark.asyncio
async def test_semantic_aligner_reuse_same_aligner():
    """Test that the same aligner can be reused for multiple alignments."""
    aligner = SemanticAligner()
    
    # First alignment
    result1 = await aligner.align_texts("Text A1", "Text B1")
    assert result1.total_paragraphs_a == 1
    assert result1.total_paragraphs_b == 1
    assert result1.containment_score >= 0.0
    assert result1.containment_score <= 1.0
    
    # Second alignment with same aligner
    result2 = await aligner.align_texts("Text A2", "Text B2")
    assert result2.total_paragraphs_a == 1
    assert result2.total_paragraphs_b == 1
    assert result2.containment_score >= 0.0
    assert result2.containment_score <= 1.0
    
    # Third alignment with same aligner
    result3 = await aligner.align_texts("Text A3", "Text B3")
    assert result3.total_paragraphs_a == 1
    assert result3.total_paragraphs_b == 1
    assert result3.containment_score >= 0.0
    assert result3.containment_score <= 1.0


def test_split_paragraphs_preserves_content():
    """Test that paragraph splitting preserves original content."""
    original_text = "Paragraph 1.\n\nParagraph 2.\n\nParagraph 3."
    paragraphs = split_paragraphs(original_text)
    
    # Reconstruct the text (without the extra newlines)
    reconstructed = "\n\n".join(paragraphs)
    
    # The reconstructed text should match the original (ignoring extra whitespace)
    assert reconstructed.strip() == original_text.strip()


def test_calculate_containment_score_edge_cases():
    """Test containment score calculation with various edge cases."""
    # Test with single character paragraphs
    paragraphs_a = ["A", "B", "C"]
    pairs = [
        AlignmentPair(type="match", text_a="A", text_b="X", score=0.5),
        AlignmentPair(type="match", text_a="B", text_b="Y", score=0.8),
        AlignmentPair(type="gap_b", text_a="C"),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert 0.0 <= score <= 1.0
    
    # Test with very long paragraphs
    long_text = "Very long text. " * 100
    paragraphs_a = [long_text]
    pairs = [
        AlignmentPair(type="match", text_a=long_text, text_b="Other text", score=0.6),
    ]
    
    score = calculate_containment_score(pairs, paragraphs_a)
    assert 0.0 <= score <= 1.0 