#!/usr/bin/env python3
from __future__ import annotations

import argparse
import hashlib
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PROJECT_MANIFEST = ROOT / "script/project-manifest.json"
CONFIG = {
    "footage": {"video_key": "footage", "receipt_key": "footage_receipt", "out_key": "footage_qa_dir"},
    "final": {"video_key": "final_master", "receipt_key": "final_receipt", "out_key": "final_qa_dir"},
}


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def resolve_active(manifest: dict, key: str) -> Path:
    value = (manifest.get("active_paths") or {}).get(key)
    if not isinstance(value, str) or not value:
        raise RuntimeError(f"active_paths.{key} is missing")
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    try:
        path.resolve().relative_to(ROOT.resolve())
    except ValueError as exc:
        raise RuntimeError(f"active path escapes project: {key}") from exc
    return path


def atomic_json(path: Path, value: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    data = (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        with os.fdopen(fd, "wb") as handle:
            handle.write(data); handle.flush(); os.fsync(handle.fileno())
        os.replace(name, path)
    finally:
        if os.path.exists(name): os.unlink(name)


def duration(path: Path) -> float:
    return float(subprocess.check_output([
        "ffprobe", "-v", "error", "-show_entries", "format=duration",
        "-of", "default=noprint_wrappers=1:nokey=1", str(path),
    ], text=True).strip())


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("stage", choices=CONFIG)
    args = parser.parse_args()
    cfg = CONFIG[args.stage]
    project = json.loads(PROJECT_MANIFEST.read_text(encoding="utf-8"))
    video = resolve_active(project, cfg["video_key"])
    receipt_path = resolve_active(project, cfg["receipt_key"])
    out = resolve_active(project, cfg["out_key"])
    receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
    if receipt.get("status") != "completed" or receipt.get("verified") is not True:
        raise RuntimeError("render receipt is not terminal completed/verified")
    if receipt.get("artifact_sha256") != sha256(video):
        raise RuntimeError("video drift detected after render verification")
    output = out / "extraction.json"
    if output.exists() or any((out / f"frame-{percent:02d}.jpg").exists() for percent in (10, 50, 90)):
        raise RuntimeError("QA output already exists and requires audit/invalidation")
    out.mkdir(parents=True, exist_ok=True)
    seconds = duration(video)
    frames = []
    for percent in (10, 50, 90):
        timestamp = seconds * percent / 100
        target = out / f"frame-{percent:02d}.jpg"
        subprocess.run([
            "ffmpeg", "-v", "error", "-y", "-ss", f"{timestamp:.3f}",
            "-i", str(video), "-frames:v", "1", "-vf", "scale=960:-2",
            "-q:v", "2", str(target),
        ], check=True)
        frames.append({"percent": percent, "timestamp_seconds": timestamp, "path": str(target), "sha256": sha256(target), "bytes": target.stat().st_size})
    result = {"status": "completed_pending_vision_review", "verified": False, "stage": args.stage, "source_canon_sha256": receipt["source_canon_sha256"], "render_receipt": {"path": str(receipt_path), "sha256": sha256(receipt_path)}, "video_path": str(video), "video_sha256": sha256(video), "duration_seconds": seconds, "frames": frames, "vision_review_required": True, "method": "sequential extraction at 10/50/90 percent, each resized independently"}
    atomic_json(output, result)
    print(json.dumps({"status": result["status"], "verified": False, "stage": args.stage, "frames": len(frames), "receipt": str(output)}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    try: sys.exit(main())
    except Exception as exc:
        print(json.dumps({"status": "failed", "verified": False, "error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr); sys.exit(1)
