"""Video metadata extraction via ffprobe."""

import hashlib
import json
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

_HASH_CHUNK_SIZE = 65536  # 64 KB

VIDEO_EXTENSIONS = {
    ".mp4", ".mov", ".avi", ".mkv", ".webm", ".mts", ".m4v",
    ".wmv", ".flv", ".mpg", ".mpeg", ".m2ts", ".3gp", ".ts",
}


class FFProbeError(Exception):
    """Error running ffprobe."""


@dataclass
class VideoMetadata:
    file_path: str
    file_name: str
    file_size: int
    duration: Optional[float] = None
    width: Optional[int] = None
    height: Optional[int] = None
    frame_rate: Optional[float] = None
    codec: Optional[str] = None
    audio_codec: Optional[str] = None
    audio_sample_rate: Optional[int] = None
    content_hash: Optional[str] = None


def _parse_frame_rate(rate_str: str) -> Optional[float]:
    """Parse frame rate string like '30000/1001' → 29.97."""
    if not rate_str or rate_str == "0/0":
        return None
    if "/" in rate_str:
        parts = rate_str.split("/")
        try:
            num, den = float(parts[0]), float(parts[1])
            return round(num / den, 2) if den != 0 else None
        except (ValueError, ZeroDivisionError):
            return None
    try:
        return round(float(rate_str), 2)
    except ValueError:
        return None


def _compute_content_hash(file_path: Path) -> str:
    """Compute SHA-256 hash of the first 64KB of a file."""
    h = hashlib.sha256()
    with open(file_path, "rb") as f:
        h.update(f.read(_HASH_CHUNK_SIZE))
    return h.hexdigest()


def extract_metadata(file_path: str | Path) -> VideoMetadata:
    """Extract video metadata using ffprobe."""
    file_path = Path(file_path)
    if not file_path.exists():
        raise FFProbeError(f"File not found: {file_path}")

    try:
        result = subprocess.run(
            [
                "ffprobe", "-v", "quiet",
                "-print_format", "json",
                "-show_format", "-show_streams",
                str(file_path),
            ],
            capture_output=True, text=True, timeout=30,
        )
    except FileNotFoundError:
        raise FFProbeError("ffprobe not found. Install ffmpeg.")
    except subprocess.TimeoutExpired:
        raise FFProbeError(f"ffprobe timed out for {file_path}")

    if result.returncode != 0:
        raise FFProbeError(f"ffprobe failed: {result.stderr}")

    try:
        probe = json.loads(result.stdout)
    except json.JSONDecodeError:
        raise FFProbeError(f"Failed to parse ffprobe output for {file_path}")

    fmt = probe.get("format", {})
    streams = probe.get("streams", [])

    video_stream = next((s for s in streams if s.get("codec_type") == "video"), None)
    audio_stream = next((s for s in streams if s.get("codec_type") == "audio"), None)

    duration = None
    if fmt.get("duration"):
        try:
            duration = float(fmt["duration"])
        except ValueError:
            pass

    meta = VideoMetadata(
        file_path=str(file_path.resolve()),
        file_name=file_path.name,
        file_size=file_path.stat().st_size,
        duration=duration,
    )

    if video_stream:
        meta.width = video_stream.get("width")
        meta.height = video_stream.get("height")
        meta.codec = video_stream.get("codec_name")
        rate_str = video_stream.get("avg_frame_rate") or video_stream.get("r_frame_rate")
        if rate_str:
            meta.frame_rate = _parse_frame_rate(rate_str)

    if audio_stream:
        meta.audio_codec = audio_stream.get("codec_name")
        sr = audio_stream.get("sample_rate")
        if sr:
            try:
                meta.audio_sample_rate = int(sr)
            except ValueError:
                pass

    meta.content_hash = _compute_content_hash(file_path)

    return meta


def is_video_file(path: str | Path) -> bool:
    """Check if a path has a recognized video extension."""
    return Path(path).suffix.lower() in VIDEO_EXTENSIONS


def scan_directory(path: str | Path, recursive: bool = True) -> list[Path]:
    """Return sorted list of video file paths in a directory."""
    path = Path(path)
    if not path.is_dir():
        if path.is_file() and is_video_file(path):
            return [path]
        return []

    if recursive:
        files = [p for p in path.rglob("*") if p.is_file() and is_video_file(p)]
    else:
        files = [p for p in path.iterdir() if p.is_file() and is_video_file(p)]

    return sorted(files)
