"""
NMS (Non-Manual Signal) detection API endpoints for SLTK.

Provides endpoints for:
- Dependency status check
- Single H5 file NMS detection
- Batch directory NMS detection
"""

from __future__ import annotations

import asyncio
import logging
from dataclasses import asdict

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field

from sltk.api.dependencies import safe_error_detail, validate_path

router = APIRouter(prefix="/api/nms", tags=["nms"])
logger = logging.getLogger(__name__)


# ============================================================================
# Pydantic Models
# ============================================================================


class NMSStatusResponse(BaseModel):
    """NMS detector dependency availability."""

    available: bool
    dependencies: dict[str, bool] = {}
    error: str | None = None


class NMSDetectRequest(BaseModel):
    """Request for NMS detection on a single H5 file."""

    h5_path: str = Field(..., description="Path to TEASER HDF5 file")
    person: str = Field(default="0", description="Person group in HDF5 file")
    detectors: list[str] = Field(
        default=["all"],
        description="Detectors to run: all, blink, nod, shake, tilt, mouth, eyebrow, gaze, squint",
    )
    format: list[str] | None = Field(
        default=None,
        description="Export formats: elan, csv, json",
    )
    output_dir: str | None = Field(
        default=None,
        description="Output directory for exports",
    )
    video_path: str | None = Field(
        default=None,
        description="Source video path (for ELAN export)",
    )
    smpl_path: str | None = Field(
        default=None,
        description="SMPL NLF file path for eye gaze data",
    )
    no_adaptive: bool = Field(
        default=False,
        description="Disable adaptive thresholds",
    )
    no_gaze: bool = Field(
        default=False,
        description="Skip eye gaze detection",
    )
    thresholds: dict[str, float] | None = Field(
        default=None,
        description="Threshold overrides (e.g. prominence, nod_amplitude)",
    )
    participant_id: str | None = Field(
        default=None,
        description="Participant ID for ELAN tier names (auto-derived if not set)",
    )


class BlinkInfo(BaseModel):
    """A detected blink event."""

    start_frame: int
    end_frame: int
    peak_frame: int
    peak_value: float
    prominence: float
    width_ms: float
    duration_ms: float
    confidence: float


class NMSEventInfo(BaseModel):
    """A detected non-manual signal event."""

    tier: str
    start_frame: int
    end_frame: int
    peak_frame: int
    peak_value: float
    duration_ms: float
    confidence: float
    label: str


class QualityInfo(BaseModel):
    """Tracking quality summary."""

    total_data_frames: int
    total_video_frames: int
    detection_rate: float
    eyelid_valid_frames: int
    eyelid_valid_rate: float
    score_mean: float
    active_eye: int
    usable_time_sec: float


class NMSDetectResponse(BaseModel):
    """Response from NMS detection."""

    video_name: str
    num_frames: int
    blinks: list[BlinkInfo]
    nms_events: list[NMSEventInfo]
    quality: QualityInfo
    export_paths: dict[str, str] = {}
    tier_counts: dict[str, int] = {}


class BatchNMSDetectRequest(BaseModel):
    """Request for batch NMS detection on a directory."""

    directory: str = Field(..., description="Directory containing H5 files")
    person: str = Field(default="0", description="Person group in HDF5 files")
    detectors: list[str] = Field(default=["all"], description="Detectors to run")
    format: list[str] | None = Field(default=None, description="Export formats")
    output_dir: str | None = Field(default=None, description="Output directory")
    no_adaptive: bool = Field(default=False, description="Disable adaptive thresholds")


class BatchNMSResult(BaseModel):
    """Result for a single file in batch processing."""

    video_name: str
    num_blinks: int
    num_nms_events: int
    tier_counts: dict[str, int]
    export_paths: dict[str, str] = {}
    error: str | None = None


class BatchNMSDetectResponse(BaseModel):
    """Response from batch NMS detection."""

    results: dict[str, BatchNMSResult]
    total_files: int
    successful: int
    failed: int


# ============================================================================
# Endpoints
# ============================================================================


@router.get("/status", response_model=NMSStatusResponse)
async def get_nms_status():
    """Check if NMS detector dependencies are available."""
    deps = {}

    for pkg in ["numpy", "scipy", "h5py", "hdf5plugin"]:
        try:
            __import__(pkg)
            deps[pkg] = True
        except ImportError:
            deps[pkg] = False

    all_available = all(deps.values())
    error = None
    if not all_available:
        missing = [k for k, v in deps.items() if not v]
        error = f"Missing dependencies: {', '.join(missing)}"

    return NMSStatusResponse(
        available=all_available,
        dependencies=deps,
        error=error,
    )


@router.post("/detect", response_model=NMSDetectResponse)
async def detect_single(body: NMSDetectRequest):
    """Run NMS detection on a single TEASER H5 file."""
    h5_path = validate_path(body.h5_path, must_exist=True)

    if not h5_path.suffix.lower() == ".h5":
        raise HTTPException(400, "File must be an H5 file")

    # Validate optional paths
    if body.output_dir:
        validate_path(body.output_dir, must_exist=True)
    if body.video_path:
        validate_path(body.video_path, must_exist=True)
    if body.smpl_path:
        validate_path(body.smpl_path, must_exist=True)

    def _run():
        from sltk.nms.runner import detect_nms, export_results

        blinks, nms_events, quality = detect_nms(
            str(h5_path),
            person=body.person,
            detectors=set(body.detectors),
            no_adaptive=body.no_adaptive,
            no_gaze=body.no_gaze,
            smpl_path=body.smpl_path,
            video_path=body.video_path,
            thresholds=body.thresholds,
        )

        # Export if requested
        export_paths = {}
        if body.format:
            export_paths = export_results(
                blinks,
                nms_events,
                str(h5_path),
                output_dir=body.output_dir,
                formats=body.format,
                participant_id=body.participant_id,
                media_path=body.video_path or "",
            )

        # Build response
        blink_infos = [
            BlinkInfo(
                start_frame=b.start_frame,
                end_frame=b.end_frame,
                peak_frame=b.peak_frame,
                peak_value=round(b.peak_value, 4),
                prominence=round(b.prominence, 4),
                width_ms=round(b.width_ms, 1),
                duration_ms=round(b.duration_ms, 1),
                confidence=round(b.confidence, 3),
            )
            for b in blinks
        ]

        event_infos = [
            NMSEventInfo(
                tier=e.tier,
                start_frame=e.start_frame,
                end_frame=e.end_frame,
                peak_frame=e.peak_frame,
                peak_value=round(e.peak_value, 4),
                duration_ms=round(e.duration_ms, 1),
                confidence=round(e.confidence, 3),
                label=e.label,
            )
            for e in nms_events
        ]

        tier_counts: dict[str, int] = {}
        if blinks:
            tier_counts["BLINK"] = len(blinks)
        for e in nms_events:
            tier_counts[e.tier] = tier_counts.get(e.tier, 0) + 1

        return NMSDetectResponse(
            video_name=h5_path.stem,
            num_frames=quality.total_data_frames,
            blinks=blink_infos,
            nms_events=event_infos,
            quality=QualityInfo(**asdict(quality)),
            export_paths=export_paths,
            tier_counts=tier_counts,
        )

    try:
        return await asyncio.to_thread(_run)
    except HTTPException:
        raise
    except FileNotFoundError as e:
        raise HTTPException(404, str(e))
    except KeyError as e:
        raise HTTPException(400, f"Invalid H5 file structure: {e}")
    except Exception as e:
        raise HTTPException(500, safe_error_detail("NMS detection", e))


@router.post("/detect/batch", response_model=BatchNMSDetectResponse)
async def detect_batch(body: BatchNMSDetectRequest):
    """Run NMS detection on all H5 files in a directory."""
    dir_path = validate_path(body.directory, must_exist=True)

    if not dir_path.is_dir():
        raise HTTPException(400, "Path must be a directory")

    if body.output_dir:
        validate_path(body.output_dir, must_exist=True)

    def _run():
        from sltk.nms.runner import detect_nms, export_results

        h5_files = sorted(dir_path.glob("*.h5"))
        # Exclude NLF files
        h5_files = [f for f in h5_files if not f.stem.endswith("_nlf")]

        if not h5_files:
            raise HTTPException(400, f"No H5 files found in {body.directory}")

        results: dict[str, BatchNMSResult] = {}
        successful = 0
        failed = 0

        for h5_file in h5_files:
            try:
                blinks, nms_events, quality = detect_nms(
                    str(h5_file),
                    person=body.person,
                    detectors=set(body.detectors),
                    no_adaptive=body.no_adaptive,
                )

                export_paths = {}
                if body.format:
                    export_paths = export_results(
                        blinks,
                        nms_events,
                        str(h5_file),
                        output_dir=body.output_dir,
                        formats=body.format,
                    )

                tier_counts: dict[str, int] = {}
                if blinks:
                    tier_counts["BLINK"] = len(blinks)
                for e in nms_events:
                    tier_counts[e.tier] = tier_counts.get(e.tier, 0) + 1

                results[h5_file.stem] = BatchNMSResult(
                    video_name=h5_file.stem,
                    num_blinks=len(blinks),
                    num_nms_events=len(nms_events),
                    tier_counts=tier_counts,
                    export_paths=export_paths,
                )
                successful += 1

            except Exception as e:
                logger.warning("NMS detection failed for %s: %s", h5_file.name, e)
                results[h5_file.stem] = BatchNMSResult(
                    video_name=h5_file.stem,
                    num_blinks=0,
                    num_nms_events=0,
                    tier_counts={},
                    error=str(e),
                )
                failed += 1

        return BatchNMSDetectResponse(
            results=results,
            total_files=len(h5_files),
            successful=successful,
            failed=failed,
        )

    try:
        return await asyncio.to_thread(_run)
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(500, safe_error_detail("Batch NMS detection", e))
