"""Tests for feature discovery utilities."""

from __future__ import annotations

from pathlib import Path

import pytest

from sltk.data.feature_discovery import (
    detect_naming_pattern,
    discover_features,
    extract_sample_id,
    list_sample_ids,
    scan_feature_directory,
)


class TestDiscoverFeatures:
    """Tests for discover_features function."""

    @pytest.fixture
    def feature_dir(self, tmp_path):
        """Create temp directory with feature files."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        # Create ID-based files
        (feat_dir / "sample_001_wilor.h5").touch()
        (feat_dir / "sample_001_smpl.h5").touch()
        (feat_dir / "sample_002_wilor.h5").touch()

        return feat_dir

    def test_discover_existing_features(self, feature_dir):
        """Test discovering existing features."""
        found = discover_features(
            sample_id="sample_001",
            source_path=feature_dir,
            feature_types=["wilor", "smpl"],
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        assert "wilor" in found
        assert "smpl" in found
        assert found["wilor"].endswith("sample_001_wilor.h5")

    def test_discover_missing_features(self, feature_dir):
        """Test discovering when feature is missing."""
        found = discover_features(
            sample_id="sample_001",
            source_path=feature_dir,
            feature_types=["mediapipe"],  # Not in directory
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        assert "mediapipe" not in found

    def test_discover_nonexistent_sample(self, feature_dir):
        """Test discovering features for nonexistent sample."""
        found = discover_features(
            sample_id="nonexistent",
            source_path=feature_dir,
            feature_types=["wilor"],
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        assert len(found) == 0

    def test_discover_nonexistent_dir(self, tmp_path):
        """Test discovering in nonexistent directory."""
        found = discover_features(
            sample_id="sample_001",
            source_path=tmp_path / "nonexistent",
            feature_types=["wilor"],
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        assert len(found) == 0


class TestDetectNamingPattern:
    """Tests for detect_naming_pattern function."""

    @pytest.fixture
    def id_based_dir(self, tmp_path):
        """Create directory with ID-based naming."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        for i in range(10):
            (feat_dir / f"sample_{i:03d}_wilor.h5").touch()

        return feat_dir

    @pytest.fixture
    def gloss_based_dir(self, tmp_path):
        """Create directory with gloss-based naming."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        for gloss in ["HELLO", "WORLD", "SIGN"]:
            (feat_dir / f"asldict-{gloss}_wilor.h5").touch()

        return feat_dir

    def test_detect_id_based_pattern(self, id_based_dir):
        """Test detecting ID-based naming pattern."""
        patterns = detect_naming_pattern(id_based_dir)

        assert len(patterns) > 0
        # Should detect the {sample_id}_{feature_type}.{ext} pattern
        pattern_strs = [p["pattern"] for p in patterns]
        assert any("sample_id" in p for p in pattern_strs)

    def test_detect_gloss_based_pattern(self, gloss_based_dir):
        """Test detecting gloss-based naming pattern."""
        patterns = detect_naming_pattern(gloss_based_dir)

        assert len(patterns) > 0

    def test_detect_empty_dir(self, tmp_path):
        """Test detecting patterns in empty directory."""
        empty_dir = tmp_path / "empty"
        empty_dir.mkdir()

        patterns = detect_naming_pattern(empty_dir)
        assert len(patterns) == 0

    def test_detect_nonexistent_dir(self, tmp_path):
        """Test detecting patterns in nonexistent directory."""
        patterns = detect_naming_pattern(tmp_path / "nonexistent")
        assert len(patterns) == 0


class TestExtractSampleId:
    """Tests for extract_sample_id function."""

    def test_extract_id_based(self):
        """Test extracting from ID-based naming."""
        sample_id = extract_sample_id(
            "sample_001_wilor.h5",
            "{sample_id}_{feature_type}.h5",
        )
        assert sample_id == "sample_001"

    def test_extract_gloss_based(self):
        """Test extracting from gloss-based naming."""
        sample_id = extract_sample_id(
            "asldict-HELLO_wilor.h5",
            "{source}-{gloss}_{feature_type}.h5",
        )
        # Should construct sample_id from source-gloss
        assert sample_id == "asldict-HELLO"

    def test_extract_no_match(self):
        """Test extracting from non-matching filename."""
        sample_id = extract_sample_id(
            "random_file.txt",
            "{sample_id}_{feature_type}.h5",
        )
        assert sample_id is None

    def test_extract_flat_pattern(self):
        """Test extracting from flat naming pattern."""
        sample_id = extract_sample_id(
            "sample_001.h5",
            "{sample_id}.h5",
        )
        assert sample_id == "sample_001"


class TestListSampleIds:
    """Tests for list_sample_ids function."""

    @pytest.fixture
    def feature_dir(self, tmp_path):
        """Create directory with multiple samples."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        samples = ["sample_001", "sample_002", "sample_003"]
        for sample in samples:
            (feat_dir / f"{sample}_wilor.h5").touch()
            (feat_dir / f"{sample}_smpl.h5").touch()

        return feat_dir

    def test_list_unique_sample_ids(self, feature_dir):
        """Test listing unique sample IDs."""
        sample_ids = list_sample_ids(
            feature_dir,
            "{sample_id}_{feature_type}.h5",
        )

        assert len(sample_ids) == 3
        assert "sample_001" in sample_ids
        assert "sample_002" in sample_ids
        assert "sample_003" in sample_ids

    def test_list_empty_dir(self, tmp_path):
        """Test listing in empty directory."""
        empty_dir = tmp_path / "empty"
        empty_dir.mkdir()

        sample_ids = list_sample_ids(
            empty_dir,
            "{sample_id}_{feature_type}.h5",
        )

        assert len(sample_ids) == 0

    def test_list_nonexistent_dir(self, tmp_path):
        """Test listing in nonexistent directory."""
        sample_ids = list_sample_ids(
            tmp_path / "nonexistent",
            "{sample_id}_{feature_type}.h5",
        )

        assert len(sample_ids) == 0


class TestScanFeatureDirectory:
    """Tests for scan_feature_directory function."""

    @pytest.fixture
    def mixed_feature_dir(self, tmp_path):
        """Create directory with multiple feature types."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        # Create wilor files
        (feat_dir / "sample_001_wilor.h5").touch()
        (feat_dir / "sample_002_wilor.h5").touch()

        # Create smpl files
        (feat_dir / "sample_001_smpl.h5").touch()

        # Create mediapipe files
        (feat_dir / "sample_001_mediapipe.h5").touch()

        return feat_dir

    def test_scan_all_types(self, mixed_feature_dir):
        """Test scanning for all feature types."""
        result = scan_feature_directory(mixed_feature_dir)

        assert "wilor" in result
        assert len(result["wilor"]) == 2
        assert "smpl" in result
        assert len(result["smpl"]) == 1

    def test_scan_specific_type(self, mixed_feature_dir):
        """Test scanning for specific feature type."""
        result = scan_feature_directory(mixed_feature_dir, feature_type="wilor")

        assert "wilor" in result
        assert len(result["wilor"]) == 2
        assert "smpl" not in result

    def test_scan_empty_dir(self, tmp_path):
        """Test scanning empty directory."""
        empty_dir = tmp_path / "empty"
        empty_dir.mkdir()

        result = scan_feature_directory(empty_dir)
        assert len(result) == 0

    def test_scan_nonexistent_dir(self, tmp_path):
        """Test scanning nonexistent directory."""
        result = scan_feature_directory(tmp_path / "nonexistent")
        assert len(result) == 0


class TestFuzzyMatching:
    """Tests for fuzzy feature file matching."""

    @pytest.fixture
    def varied_names_dir(self, tmp_path):
        """Create directory with varied naming conventions."""
        feat_dir = tmp_path / "features"
        feat_dir.mkdir()

        # Different separators
        (feat_dir / "sample_001-wilor.h5").touch()
        (feat_dir / "sample_002.wilor.h5").touch()

        # Different extensions
        (feat_dir / "sample_003_wilor.pt").touch()
        (feat_dir / "sample_004_wilor.lzma").touch()

        return feat_dir

    def test_discover_with_different_separators(self, varied_names_dir):
        """Test discovering with different separators."""
        found = discover_features(
            sample_id="sample_001",
            source_path=varied_names_dir,
            feature_types=["wilor"],
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        # Should find even with dash separator via fuzzy matching
        assert "wilor" in found

    def test_discover_different_extensions(self, varied_names_dir):
        """Test discovering with different extensions."""
        found = discover_features(
            sample_id="sample_003",
            source_path=varied_names_dir,
            feature_types=["wilor"],
            naming_pattern="{sample_id}_{feature_type}.h5",
        )

        # Should find .pt file via fuzzy matching
        assert "wilor" in found
