"""
Feature discovery utilities for matching sample IDs to feature files.

Supports multiple naming patterns commonly found in sign language datasets:
- ID-based: {sample_id}_{feature_type}.h5 (BSLCP, BOBSL)
- Gloss-based: {source}-{gloss}_{feature_type}.h5 (asldict)
- Flat: {sample_id}.h5 (when feature type is implicit from directory)

The discovery functions handle variations like:
- Different separators (_, -, .)
- Case variations
- File extensions (.h5, .pt, .lzma)
"""

from __future__ import annotations

import logging
import re
from pathlib import Path

logger = logging.getLogger(__name__)

# Common feature file extensions
FEATURE_EXTENSIONS = {".h5", ".pt", ".lzma", ".npy", ".npz", ".pkl"}

# Feature type aliases for flexible matching
FEATURE_TYPE_ALIASES: dict[str, list[str]] = {
    "wilor": ["wilor", "mano", "hand"],
    "smpl": ["smpl", "smplx", "nlf", "body"],
    "mediapipe": ["mediapipe", "mp", "holistic"],
    "angles": ["angles", "angle", "uplift"],
    "hamer": ["hamer", "ham"],
    "segments": ["segments", "segment", "seg"],
}


def discover_features(
    sample_id: str,
    source_path: Path,
    feature_types: list[str],
    naming_pattern: str,
) -> dict[str, str]:
    """
    Discover feature files for a sample in a directory.

    Args:
        sample_id: The sample identifier to look for
        source_path: Path to the feature directory
        feature_types: List of feature types to look for
        naming_pattern: Expected naming pattern (e.g., "{sample_id}_{feature_type}.h5")

    Returns:
        Dict of feature_type -> absolute file path for found features
    """
    if not source_path.exists():
        return {}

    found: dict[str, str] = {}

    for feature_type in feature_types:
        # Try exact pattern match first
        expected_name = naming_pattern.format(
            sample_id=sample_id,
            feature_type=feature_type,
        )

        # Check for file with expected name
        expected_path = source_path / expected_name
        if expected_path.exists():
            found[feature_type] = str(expected_path)
            continue

        # Try fuzzy matching if exact match fails
        match = _fuzzy_find_feature(
            sample_id=sample_id,
            feature_type=feature_type,
            source_path=source_path,
        )
        if match:
            found[feature_type] = match

    return found


def _fuzzy_find_feature(
    sample_id: str,
    feature_type: str,
    source_path: Path,
) -> str | None:
    """
    Try to find a feature file using fuzzy matching.

    Handles variations in:
    - Separators (_, -, .)
    - Case
    - Feature type aliases
    - Extensions
    """
    # Get all aliases for this feature type
    aliases = FEATURE_TYPE_ALIASES.get(feature_type, [feature_type])

    # Escape special regex characters in sample_id
    escaped_id = re.escape(sample_id)

    # Build patterns to try
    patterns = []
    for alias in aliases:
        # Pattern: sample_id + separator + feature_type + extension
        patterns.append(
            re.compile(
                rf"^{escaped_id}[_\-\.]{alias}\.(?:h5|pt|lzma|npy|npz|pkl)$",
                re.IGNORECASE,
            )
        )
        # Pattern: sample_id + feature_type (no separator)
        patterns.append(
            re.compile(
                rf"^{escaped_id}{alias}\.(?:h5|pt|lzma|npy|npz|pkl)$",
                re.IGNORECASE,
            )
        )

    # Search directory
    try:
        for file in source_path.iterdir():
            if not file.is_file():
                continue
            for pattern in patterns:
                if pattern.match(file.name):
                    return str(file)
    except PermissionError:
        logger.warning(f"Permission denied accessing: {source_path}")

    return None


def detect_naming_pattern(source_path: Path) -> list[dict[str, str | int]]:
    """
    Detect the naming pattern(s) used in a feature directory.

    Scans the directory and attempts to identify the naming convention.

    Args:
        source_path: Path to the feature directory

    Returns:
        List of detected patterns with examples:
        [{"pattern": "{sample_id}_{feature_type}.h5", "example": "001_wilor.h5", "count": 100}]
    """
    if not source_path.exists():
        return []

    patterns_found: dict[str, dict] = {}

    # Common pattern regexes
    pattern_matchers = [
        # ID_type.ext pattern
        (
            r"^(.+?)_([a-zA-Z]+)\.([a-zA-Z0-9]+)$",
            "{sample_id}_{feature_type}.{ext}",
        ),
        # source-gloss_type.ext pattern
        (
            r"^([^-]+)-([^_]+)_([a-zA-Z]+)\.([a-zA-Z0-9]+)$",
            "{source}-{gloss}_{feature_type}.{ext}",
        ),
        # ID.ext pattern (flat, type from directory name)
        (
            r"^(.+?)\.([a-zA-Z0-9]+)$",
            "{sample_id}.{ext}",
        ),
    ]

    try:
        files = list(source_path.iterdir())[:1000]  # Sample up to 1000 files
    except PermissionError:
        return []

    for file in files:
        if not file.is_file():
            continue
        if file.suffix.lower() not in FEATURE_EXTENSIONS:
            continue

        for regex, pattern_template in pattern_matchers:
            match = re.match(regex, file.name)
            if match:
                if pattern_template not in patterns_found:
                    patterns_found[pattern_template] = {
                        "pattern": pattern_template,
                        "example": file.name,
                        "count": 0,
                    }
                patterns_found[pattern_template]["count"] += 1
                break

    # Sort by count and return
    return sorted(patterns_found.values(), key=lambda x: x["count"], reverse=True)


def extract_sample_id(
    filename: str,
    naming_pattern: str,
) -> str | None:
    """
    Extract the sample ID from a filename given a naming pattern.

    Args:
        filename: The filename to parse
        naming_pattern: The naming pattern template

    Returns:
        Extracted sample ID or None if pattern doesn't match
    """
    # Convert pattern to regex
    # First escape dots (before inserting regex patterns that contain dots)
    regex_pattern = naming_pattern.replace(".", r"\.")

    # Then replace placeholders with regex groups
    regex_pattern = regex_pattern.replace("{sample_id}", r"(?P<sample_id>.+?)")
    regex_pattern = regex_pattern.replace("{feature_type}", r"(?P<feature_type>[a-zA-Z]+)")
    regex_pattern = regex_pattern.replace("{source}", r"(?P<source>[^-]+)")
    regex_pattern = regex_pattern.replace("{gloss}", r"(?P<gloss>[^_]+)")
    regex_pattern = regex_pattern.replace("{ext}", r"(?P<ext>[a-zA-Z0-9]+)")

    match = re.match(f"^{regex_pattern}$", filename)
    if match:
        groups = match.groupdict()
        if "sample_id" in groups:
            return groups["sample_id"]
        # For gloss-based patterns, construct sample_id
        if "source" in groups and "gloss" in groups:
            return f"{groups['source']}-{groups['gloss']}"

    return None


def list_sample_ids(
    source_path: Path,
    naming_pattern: str,
) -> list[str]:
    """
    List all sample IDs found in a feature directory.

    Args:
        source_path: Path to the feature directory
        naming_pattern: The naming pattern template

    Returns:
        List of unique sample IDs
    """
    if not source_path.exists():
        return []

    sample_ids: set[str] = set()

    try:
        for file in source_path.iterdir():
            if not file.is_file():
                continue
            if file.suffix.lower() not in FEATURE_EXTENSIONS:
                continue

            sample_id = extract_sample_id(file.name, naming_pattern)
            if sample_id:
                sample_ids.add(sample_id)
    except PermissionError:
        logger.warning(f"Permission denied accessing: {source_path}")

    return sorted(sample_ids)


def scan_feature_directory(
    source_path: Path,
    feature_type: str | None = None,
) -> dict[str, list[str]]:
    """
    Scan a feature directory and group files by detected feature type.

    Args:
        source_path: Path to the feature directory
        feature_type: If provided, only look for this feature type

    Returns:
        Dict of feature_type -> list of file paths
    """
    if not source_path.exists():
        return {}

    result: dict[str, list[str]] = {}

    try:
        for file in source_path.iterdir():
            if not file.is_file():
                continue
            if file.suffix.lower() not in FEATURE_EXTENSIONS:
                continue

            # Try to detect feature type from filename
            detected_type = _detect_feature_type(file.name)
            if detected_type is None:
                # Use directory name as feature type
                detected_type = source_path.name.lower()

            if feature_type is not None and detected_type != feature_type:
                continue

            if detected_type not in result:
                result[detected_type] = []
            result[detected_type].append(str(file))

    except PermissionError:
        logger.warning(f"Permission denied accessing: {source_path}")

    return result


def _detect_feature_type(filename: str) -> str | None:
    """Detect feature type from filename."""
    filename_lower = filename.lower()

    for feature_type, aliases in FEATURE_TYPE_ALIASES.items():
        for alias in aliases:
            # Check if alias appears before the extension
            pattern = rf"[_\-\.]{alias}[_\-\.]|\_{alias}\.|{alias}\."
            if re.search(pattern, filename_lower):
                return feature_type

    return None
