#!/usr/bin/env python3
"""Fail-closed artifact/path/schema preflight before StickerMan service side effects."""

from __future__ import annotations

import argparse
import json
import subprocess
from pathlib import Path

from project_runtime import atomic_write_json, require_beneath, require_project_root, valid_asr, valid_png, valid_wav


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


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("project_root", type=Path)
    parser.add_argument("stage", choices=("tts", "asr", "images", "gpu-intro", "gpu-outro", "gpu-main", "package", "upload", "calendar"))
    args = parser.parse_args()
    root = require_project_root(args.project_root)
    checks: dict[str, object] = {"status": "PASS", "stage": args.stage, "project_root": str(root)}

    if args.stage in {"tts", "asr"}:
        manifest = require_beneath(root / "narration" / "narration.json", root, must_exist=True)
        items = json.loads(manifest.read_text(encoding="utf-8"))["items"]
        checks["items"] = len(items)
        if args.stage == "asr":
            invalid = [item["id"] for item in items if not valid_wav(Path(item["audio_path"]))]
            if invalid:
                raise ValueError(f"invalid TTS inputs: {invalid}")

    elif args.stage == "images":
        timeline = require_beneath(root / "asr" / "timeline.json", root, must_exist=True)
        prompts = require_beneath(root / "prompts" / "image_prompts.json", root, must_exist=True)
        t = json.loads(timeline.read_text(encoding="utf-8"))["scenes"]
        p = json.loads(prompts.read_text(encoding="utf-8"))["items"]
        if len(t) != len(p) or [x["id"] for x in t] != [x["id"] for x in p]:
            raise ValueError("timeline/prompt manifest mismatch")
        checks["scenes"] = len(t)

    elif args.stage.startswith("gpu-"):
        role = args.stage.removeprefix("gpu-")
        if role == "intro":
            speech = require_beneath(root / "audio" / "intro.wav", root, must_exist=True)
            padded = require_beneath(root / "audio" / "intro_padded.wav", root, must_exist=True)
            image = require_beneath(root / "images" / "intro_poster.png", root, must_exist=True)
            if not valid_wav(speech) or not valid_wav(padded) or not valid_png(image):
                raise ValueError("invalid intro input")
            delta = duration(padded) - duration(speech)
            if abs(delta - 2.0) > 0.01:
                raise ValueError(f"intro physical silence mismatch: {delta}")
            checks.update({"audio_path": str(padded), "scene_duration": duration(padded), "physical_silence": delta})
        elif role == "outro":
            audio = require_beneath(root / "audio" / "outro.wav", root, must_exist=True)
            image = require_beneath(root / "images" / "outro_poster.png", root, must_exist=True)
            if not valid_wav(audio) or not valid_png(image):
                raise ValueError("invalid outro input")
            checks.update({"audio_path": str(audio), "scene_duration": duration(audio)})
        else:
            audio = require_beneath(root / "audio" / "full_narration.wav", root, must_exist=True)
            timeline = json.loads((root / "asr" / "timeline.json").read_text(encoding="utf-8"))["scenes"]
            invalid = [row["id"] for row in timeline if not valid_png(Path(row["image_path"]))]
            if not valid_wav(audio) or invalid:
                raise ValueError(f"invalid main inputs; images={invalid}")
            checks.update({"audio_path": str(audio), "scenes": len(timeline), "timeline_end_sec": sum(float(x["duration"]) for x in timeline)})

    elif args.stage == "package":
        for relative in ("video/intro.mp4", "video/output/main-with-outro.mp4"):
            require_beneath(root / relative, root, must_exist=True)
        checks["bgm_volume"] = 0.2

    elif args.stage in {"upload", "calendar"}:
        for relative in ("video/output/final.mp4", "video/output/final-fb.mp4", "images/intro_poster.png"):
            require_beneath(root / relative, root, must_exist=True)
        if args.stage == "calendar":
            release = require_beneath(root / "logs" / "bgm_audit_release.json", root, must_exist=True)
            checks["release_audit"] = str(release)

    atomic_write_json(root / "logs" / f"preflight_{args.stage}.json", checks)
    print(json.dumps(checks, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
