"""Tests for ELAN I/O module."""

import tempfile
from pathlib import Path

import pytest

from sltk.data.core import Segment, SegmentList
from sltk.io.elan import add_tier_to_eaf, get_eaf_metadata, get_eaf_tiers, read_eaf, write_eaf


class TestELANWriteRead:
    """Tests for ELAN write and read roundtrip."""

    def test_write_read_roundtrip(self):
        """Test that segments survive write/read cycle."""
        segments = SegmentList(
            [
                Segment(start=0.0, end=1.5, label="HELLO", tier="Gloss"),
                Segment(start=1.5, end=3.0, label="WORLD", tier="Gloss"),
                Segment(start=0.0, end=3.0, label="Hello world", tier="Translation"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            # Write
            write_eaf(segments, output_path, author="TestAuthor")

            # Read back
            loaded = read_eaf(output_path)

            # Verify
            assert len(loaded) == 3
            assert set(loaded.tiers) == {"Gloss", "Translation"}

            gloss_segs = loaded.filter_by_tier("Gloss")
            assert len(gloss_segs) == 2
            assert gloss_segs[0].label in ["HELLO", "WORLD"]

        finally:
            output_path.unlink(missing_ok=True)

    def test_write_with_video_link(self):
        """Test writing EAF with video link."""
        segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label="TEST", tier="default"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            # Write with video path (doesn't need to exist)
            write_eaf(segments, output_path, video_path="test_video.mp4")

            # Read metadata
            meta = get_eaf_metadata(output_path)

            assert len(meta["media_files"]) == 1
            assert "test_video.mp4" in meta["media_files"][0]["relative_url"]

        finally:
            output_path.unlink(missing_ok=True)

    def test_read_specific_tiers(self):
        """Test reading only specific tiers."""
        segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label="A", tier="Tier1"),
                Segment(start=0.0, end=1.0, label="B", tier="Tier2"),
                Segment(start=0.0, end=1.0, label="C", tier="Tier3"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            write_eaf(segments, output_path)

            # Read only Tier1 and Tier2
            loaded = read_eaf(output_path, tiers=["Tier1", "Tier2"])

            assert len(loaded) == 2
            assert set(loaded.tiers) == {"Tier1", "Tier2"}

        finally:
            output_path.unlink(missing_ok=True)

    def test_get_tiers(self):
        """Test getting tier names without loading all data."""
        segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label="A", tier="Gloss"),
                Segment(start=0.0, end=1.0, label="B", tier="Translation"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            write_eaf(segments, output_path)

            tiers = get_eaf_tiers(output_path)
            assert set(tiers) == {"Gloss", "Translation"}

        finally:
            output_path.unlink(missing_ok=True)

    def test_time_precision(self):
        """Test that time values maintain precision."""
        segments = SegmentList(
            [
                Segment(start=0.123, end=0.456, label="TEST", tier="default"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            write_eaf(segments, output_path)
            loaded = read_eaf(output_path)

            # ELAN uses milliseconds, so precision is to 3 decimal places
            assert abs(loaded[0].start - 0.123) < 0.001
            assert abs(loaded[0].end - 0.456) < 0.001

        finally:
            output_path.unlink(missing_ok=True)

    def test_empty_labels(self):
        """Test handling of empty labels."""
        segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label=None, tier="default"),
                Segment(start=1.0, end=2.0, label="", tier="default"),
            ]
        )

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            output_path = Path(f.name)

        try:
            write_eaf(segments, output_path)

            # By default, empty labels are excluded
            loaded = read_eaf(output_path, include_empty=False)
            assert len(loaded) == 0

            # But can be included
            loaded_all = read_eaf(output_path, include_empty=True)
            assert len(loaded_all) == 2

        finally:
            output_path.unlink(missing_ok=True)


class TestAddTierToEAF:
    """Tests for add_tier_to_eaf functionality."""

    def test_add_tier_to_existing_file(self):
        """Test adding a new tier to an existing ELAN file."""
        # Create initial file with one tier
        original_segments = SegmentList(
            [
                Segment(start=0.0, end=1.5, label="HELLO", tier="Gloss"),
                Segment(start=1.5, end=3.0, label="WORLD", tier="Gloss"),
            ]
        )

        # New AI-detected segments
        ai_segments = SegmentList(
            [
                Segment(start=0.2, end=1.3, label="SIGN_1", tier="AI_Predictions"),
                Segment(start=1.7, end=2.8, label="SIGN_2", tier="AI_Predictions"),
            ]
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "original.eaf"
            output_path = Path(tmpdir) / "output_with_ai.eaf"

            # Write original file
            write_eaf(original_segments, source_path)

            # Add new tier
            result_path = add_tier_to_eaf(
                source_path=source_path,
                segments=ai_segments,
                output_path=output_path,
                tier_name="AI_Segmentation",
            )

            # Verify result
            assert result_path == output_path
            assert output_path.exists()
            assert source_path.exists()  # Original unchanged

            # Read back the combined file
            combined = read_eaf(output_path)

            # Should have both original and new segments
            assert len(combined) == 4
            assert "Gloss" in combined.tiers
            assert "AI_Segmentation" in combined.tiers

            # Check AI tier segments
            ai_tier = combined.filter_by_tier("AI_Segmentation")
            assert len(ai_tier) == 2
            assert ai_tier[0].label in ["SIGN_1", "SIGN_2"]

            # Verify original file is unchanged
            original_loaded = read_eaf(source_path)
            assert len(original_loaded) == 2
            assert "AI_Segmentation" not in original_loaded.tiers

    def test_add_tier_prevents_overwrite_source(self):
        """Test that add_tier_to_eaf prevents overwriting source file."""
        segments = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="Tier1")])
        new_segments = SegmentList([Segment(start=0.5, end=1.5, label="B", tier="AI")])

        with tempfile.NamedTemporaryFile(suffix=".eaf", delete=False) as f:
            source_path = Path(f.name)

        try:
            write_eaf(segments, source_path)

            # Try to use same path for output - should raise ValueError
            with pytest.raises(ValueError, match="Output path cannot be the same"):
                add_tier_to_eaf(
                    source_path=source_path,
                    segments=new_segments,
                    output_path=source_path,  # Same as source!
                    tier_name="AI_Tier",
                )

        finally:
            source_path.unlink(missing_ok=True)

    def test_add_tier_handles_duplicate_tier_names(self):
        """Test that duplicate tier names get auto-renamed."""
        segments = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="AI_Segmentation")])
        new_segments = SegmentList([Segment(start=1.0, end=2.0, label="B", tier="AI_Segmentation")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            output_path = Path(tmpdir) / "output.eaf"

            write_eaf(segments, source_path)

            # Add tier with same name - should be auto-renamed
            add_tier_to_eaf(
                source_path=source_path,
                segments=new_segments,
                output_path=output_path,
                tier_name="AI_Segmentation",  # Same name as existing
            )

            # Should have renamed the new tier
            tiers = get_eaf_tiers(output_path)
            assert len(tiers) == 2
            assert "AI_Segmentation" in tiers
            # The new tier should have timestamp suffix
            renamed_tier = [t for t in tiers if t != "AI_Segmentation"][0]
            assert renamed_tier.startswith("AI_Segmentation_")

    def test_add_tier_preserves_existing_annotations(self):
        """Test that existing annotations are preserved exactly."""
        original = SegmentList(
            [
                Segment(start=0.123, end=1.456, label="PRECISE", tier="Original"),
                Segment(start=2.0, end=3.0, label="ANOTHER", tier="Original"),
            ]
        )
        new_segments = SegmentList([Segment(start=0.5, end=1.5, label="NEW", tier="New")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            output_path = Path(tmpdir) / "output.eaf"

            write_eaf(original, source_path)
            add_tier_to_eaf(source_path, new_segments, output_path, "NewTier")

            # Check original segments are preserved
            combined = read_eaf(output_path)
            orig_tier = combined.filter_by_tier("Original")

            assert len(orig_tier) == 2
            assert abs(orig_tier[0].start - 0.123) < 0.001
            assert abs(orig_tier[0].end - 1.456) < 0.001

    def test_add_tier_with_reused_time_slots(self):
        """Test that existing time slots are reused when possible."""
        # Create segments that share some time boundaries
        original = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])
        new_segments = SegmentList(
            [
                Segment(start=1.0, end=2.0, label="B", tier="T2"),  # Shares 1.0
                Segment(start=0.0, end=0.5, label="C", tier="T2"),  # Shares 0.0
            ]
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            output_path = Path(tmpdir) / "output.eaf"

            write_eaf(original, source_path)
            add_tier_to_eaf(source_path, new_segments, output_path, "NewTier")

            # Verify all segments readable
            combined = read_eaf(output_path)
            assert len(combined) == 3

    def test_add_tier_file_not_found(self):
        """Test error handling for non-existent source file."""
        from sltk.exceptions import SLTKFileNotFoundError

        new_segments = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])

        with pytest.raises(SLTKFileNotFoundError):
            add_tier_to_eaf(
                source_path="/nonexistent/path.eaf",
                segments=new_segments,
                output_path="/tmp/output.eaf",
                tier_name="Test",
            )


class TestTierManagement:
    """Tests for tier management functions."""

    def test_list_elan_tiers_alias(self):
        """Test that list_elan_tiers is an alias for get_eaf_tiers."""
        from sltk.io.elan import get_eaf_tiers, list_elan_tiers

        assert list_elan_tiers is get_eaf_tiers

    def test_get_eaf_tier_info(self):
        """Test getting detailed tier information."""
        from sltk.io.elan import get_eaf_tier_info

        segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label="A", tier="Gloss"),
                Segment(start=1.0, end=2.0, label="B", tier="Gloss"),
                Segment(start=0.0, end=2.0, label="Translation text", tier="Translation"),
            ]
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            eaf_path = Path(tmpdir) / "test.eaf"
            write_eaf(segments, eaf_path)

            tier_info = get_eaf_tier_info(eaf_path)

            assert len(tier_info) == 2

            # Find the Gloss tier
            gloss_info = next(t for t in tier_info if t["tier_id"] == "Gloss")
            assert gloss_info["annotation_count"] == 2
            assert gloss_info["linguistic_type"] == "default-lt"

            # Find the Translation tier
            trans_info = next(t for t in tier_info if t["tier_id"] == "Translation")
            assert trans_info["annotation_count"] == 1

    def test_generate_safe_output_path(self):
        """Test safe output path generation with versioning."""
        from sltk.io.elan import _generate_safe_output_path

        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a source file
            source_path = Path(tmpdir) / "video.eaf"
            source_path.touch()

            # First version should be v1
            output1 = _generate_safe_output_path(source_path)
            assert output1.name == "video_AI_v1.eaf"
            assert not output1.exists()

            # Create v1, next should be v2
            output1.touch()
            output2 = _generate_safe_output_path(source_path)
            assert output2.name == "video_AI_v2.eaf"

            # Create v2, next should be v3
            output2.touch()
            output3 = _generate_safe_output_path(source_path)
            assert output3.name == "video_AI_v3.eaf"

    def test_generate_default_tier_name(self):
        """Test default tier name generation."""
        from sltk.io.elan import _generate_default_tier_name

        tier_name = _generate_default_tier_name()
        assert tier_name.startswith("AI_Segmentation_")
        # Should have format AI_Segmentation_YYYYMMDD_HHMMSS
        assert len(tier_name) > len("AI_Segmentation_")

    def test_validate_eaf_xml(self):
        """Test EAF XML validation."""
        from sltk.io.elan import _validate_eaf_xml

        segments = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])

        with tempfile.TemporaryDirectory() as tmpdir:
            valid_eaf = Path(tmpdir) / "valid.eaf"
            write_eaf(segments, valid_eaf)

            # Valid file should pass validation
            assert _validate_eaf_xml(valid_eaf) is True

            # Invalid XML should fail
            invalid_xml = Path(tmpdir) / "invalid.eaf"
            invalid_xml.write_text("<invalid>not an eaf file</invalid>")
            assert _validate_eaf_xml(invalid_xml) is False

            # Non-existent file should fail
            nonexistent = Path(tmpdir) / "nonexistent.eaf"
            assert _validate_eaf_xml(nonexistent) is False

    def test_merge_tiers(self):
        """Test merge_tiers function."""
        from sltk.io.elan import merge_tiers

        # Create initial file
        original_segments = SegmentList(
            [
                Segment(start=0.0, end=1.0, label="A", tier="Original"),
                Segment(start=1.0, end=2.0, label="B", tier="Original"),
            ]
        )

        # New segments to merge
        new_segments = SegmentList(
            [
                Segment(start=0.5, end=1.5, label="X", tier="SomeOtherTier"),
                Segment(start=1.5, end=2.5, label="Y", tier="AnotherTier"),
            ]
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            write_eaf(original_segments, source_path)

            # Merge with explicit tier name
            result_path = merge_tiers(
                source_eaf=source_path,
                new_segments=new_segments,
                tier_name="MergedAI",
            )

            # Check the result
            assert result_path.exists()
            assert result_path != source_path  # Never overwrites

            combined = read_eaf(result_path)
            assert len(combined) == 4

            # All new segments should be in the merged tier
            merged_tier = combined.filter_by_tier("MergedAI")
            assert len(merged_tier) == 2
            assert merged_tier[0].label in ["X", "Y"]

            # Clean up
            result_path.unlink()

    def test_merge_tiers_auto_generated_path(self):
        """Test that merge_tiers generates versioned output path."""
        from sltk.io.elan import merge_tiers

        original = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])
        new_segs = SegmentList([Segment(start=1.0, end=2.0, label="B", tier="T2")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "video.eaf"
            write_eaf(original, source_path)

            # Call without explicit output_path
            result_path = merge_tiers(
                source_eaf=source_path,
                new_segments=new_segs,
                tier_name="AI",
            )

            # Should generate versioned filename
            assert result_path.name == "video_AI_v1.eaf"
            assert result_path.exists()

            # Clean up
            result_path.unlink()

    def test_merge_tiers_auto_generated_tier_name(self):
        """Test that merge_tiers generates default tier name."""
        from sltk.io.elan import merge_tiers

        original = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])
        new_segs = SegmentList([Segment(start=1.0, end=2.0, label="B", tier="T2")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            write_eaf(original, source_path)

            # Call without tier_name - should auto-generate
            result_path = merge_tiers(
                source_eaf=source_path,
                new_segments=new_segs,
                tier_name=None,  # Auto-generate
            )

            # Read and check tier names
            combined = read_eaf(result_path)
            new_tier = [t for t in combined.tiers if t != "T1"][0]
            assert new_tier.startswith("AI_Segmentation_")

            # Clean up
            result_path.unlink()

    def test_safe_add_tier(self):
        """Test safe_add_tier function with XML validation."""
        from sltk.io.elan import safe_add_tier

        original = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])
        new_segs = SegmentList([Segment(start=1.0, end=2.0, label="B", tier="T2")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            write_eaf(original, source_path)

            # Use safe_add_tier
            result_path = safe_add_tier(
                eaf_path=source_path,
                tier_name="SafeAI",
                segments=new_segs,
            )

            # Verify result
            assert result_path.exists()
            assert result_path != source_path
            assert result_path.name.startswith("source_AI_v")

            combined = read_eaf(result_path)
            assert "SafeAI" in combined.tiers
            assert len(combined) == 2

            # Clean up
            result_path.unlink()

    def test_safe_add_tier_prevents_existing_output(self):
        """Test that safe_add_tier rejects explicit paths that exist."""
        from sltk.io.elan import safe_add_tier

        original = SegmentList([Segment(start=0.0, end=1.0, label="A", tier="T1")])
        new_segs = SegmentList([Segment(start=1.0, end=2.0, label="B", tier="T2")])

        with tempfile.TemporaryDirectory() as tmpdir:
            source_path = Path(tmpdir) / "source.eaf"
            existing_output = Path(tmpdir) / "existing.eaf"

            write_eaf(original, source_path)
            existing_output.touch()  # Create existing file

            # Should raise FileExistsError
            with pytest.raises(FileExistsError):
                safe_add_tier(
                    eaf_path=source_path,
                    tier_name="Test",
                    segments=new_segs,
                    output_path=existing_output,
                )
