"""Video frame extraction fallback for multimodal readers."""
from __future__ import annotations

import logging
import re
import subprocess
from pathlib import Path
from typing import Optional

import httpx

from core.models import Card

logger = logging.getLogger(__name__)

SCENE_THRESHOLD = 0.3
MIN_GAP_SECONDS = 1.5
FALLBACK_MIN = 4
FALLBACK_TARGET = 8
SCALE_VF = "scale='min(720,iw)':-2"
REFERER = {
    "douyin": "https://www.douyin.com/",
    "kuaishou": "https://www.kuaishou.com/",
    "bilibili": "https://www.bilibili.com/",
    "shipinhao": "https://channels.weixin.qq.com/",
    "xiaohongshu": "https://www.xiaohongshu.com/",
}


class VideoFramesError(RuntimeError):
    pass


def _ffmpeg_bin() -> str:
    try:
        import imageio_ffmpeg

        return imageio_ffmpeg.get_ffmpeg_exe()
    except Exception:
        return "ffmpeg"


def _download(url: str, platform: str, dst: Path, timeout: float) -> None:
    headers = {"User-Agent": "Mozilla/5.0", "Referer": REFERER.get(platform, "")}
    with httpx.stream("GET", url, headers=headers, timeout=timeout, follow_redirects=True) as response:
        response.raise_for_status()
        with open(dst, "wb") as file:
            for chunk in response.iter_bytes():
                file.write(chunk)


def _run(cmd: list[str]) -> str:
    proc = subprocess.run(cmd, capture_output=True, text=True)
    return proc.stderr or ""


def _probe_duration(ffmpeg: str, video: Path) -> float:
    err = _run([ffmpeg, "-hide_banner", "-i", str(video)])
    match = re.search(r"Duration:\s*(\d+):(\d+):(\d+\.?\d*)", err)
    if not match:
        return 0.0
    hours, minutes, seconds = match.groups()
    return int(hours) * 3600 + int(minutes) * 60 + float(seconds)


def _scene_frames(
    ffmpeg: str,
    video: Path,
    out: Path,
    threshold: float,
    min_gap: float,
    max_frames: int,
) -> list[tuple[Path, Optional[float]]]:
    pattern = str(out / "scene_%04d.jpg")
    err = _run(
        [
            ffmpeg,
            "-hide_banner",
            "-i",
            str(video),
            "-vf",
            f"select='gt(scene,{threshold})',showinfo,{SCALE_VF}",
            "-vsync",
            "vfr",
            "-q:v",
            "3",
            pattern,
        ]
    )
    times = [float(ts) for ts in re.findall(r"pts_time:(\d+\.?\d*)", err)]
    files = sorted(out.glob("scene_*.jpg"))
    paired = list(zip(files, times + [None] * (len(files) - len(times))))
    kept: list[tuple[Path, Optional[float]]] = []
    last = None
    for path, timestamp in paired:
        if timestamp is not None and last is not None and timestamp - last < min_gap:
            continue
        kept.append((path, timestamp))
        if timestamp is not None:
            last = timestamp
        if len(kept) >= max_frames:
            break
    return kept


def _uniform_frames(
    ffmpeg: str,
    video: Path,
    out: Path,
    duration: float,
    count: int,
) -> list[tuple[Path, Optional[float]]]:
    if duration <= 0:
        duration = float(count)
    result: list[tuple[Path, Optional[float]]] = []
    for idx in range(count):
        timestamp = duration * (idx + 0.5) / count
        path = out / f"uni_{idx:04d}.jpg"
        _run(
            [
                ffmpeg,
                "-hide_banner",
                "-ss",
                f"{timestamp:.2f}",
                "-i",
                str(video),
                "-frames:v",
                "1",
                "-vf",
                SCALE_VF,
                "-q:v",
                "3",
                str(path),
            ]
        )
        if path.exists():
            result.append((path, round(timestamp, 2)))
    return result


def extract_frames(
    video_src: str,
    *,
    out_dir: str | Path,
    url_prefix: str,
    platform: str = "",
    max_frames: int = 12,
    start_index: int = 1,
    timeout: float = 60.0,
) -> list[Card]:
    out = Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)
    ffmpeg = _ffmpeg_bin()

    src = Path(video_src)
    if not src.exists():
        src = out / "_src.mp4"
        _download(video_src, platform, src, timeout)

    frames = _scene_frames(ffmpeg, src, out, SCENE_THRESHOLD, MIN_GAP_SECONDS, max_frames)
    if len(frames) < FALLBACK_MIN:
        logger.info("scene frames only %d, falling back to uniform sampling", len(frames))
        duration = _probe_duration(ffmpeg, src)
        frames = _uniform_frames(ffmpeg, src, out, duration, FALLBACK_TARGET)
    frames = frames[:max_frames]
    if not frames:
        raise VideoFramesError(f"未能从视频抽出任何帧: {video_src}")

    return [
        Card(index=start_index + idx, kind="frame", url=f"{url_prefix.rstrip('/')}/{path.name}", timestamp=timestamp)
        for idx, (path, timestamp) in enumerate(frames)
    ]
