#!/usr/bin/env python3
import argparse
import hashlib
import json
import math
from pathlib import Path
import shutil
import subprocess
import sys


def command(args, timeout=120):
    try:
        result = subprocess.run(args, capture_output=True, text=True, timeout=timeout, check=False)
    except subprocess.TimeoutExpired:
        raise ValueError("Media inspection timed out; decoding is unconfirmed") from None
    if result.returncode:
        raise ValueError("Media tool rejected the file; inspect it locally")
    return result.stdout


def inspect(path, width=None, height=None, duration=None, tolerance=0.25, no_audio=False):
    if not path.is_file() or path.stat().st_size < 16:
        raise ValueError("Input must be an existing local MP4 file")
    if path.stat().st_size > 300 * 1024 * 1024:
        raise ValueError("Input exceeds the 300 MiB local inspection limit")
    with path.open("rb") as file:
        if file.read(12)[4:8] != b"ftyp":
            raise ValueError("Expected an MP4 ftyp header; a filename extension is insufficient")
    probe = shutil.which("ffprobe")
    decoder = shutil.which("ffmpeg")
    if not probe or not decoder:
        raise ValueError("Install FFmpeg and ffprobe, then run this local check again")
    source = str(path.resolve())
    data = json.loads(command([probe, "-v", "error", "-protocol_whitelist", "file,pipe", "-f", "mov", "-show_entries", "stream=index,codec_type,codec_name,width,height:format=duration,size", "-of", "json", source]))
    streams = data.get("streams", [])
    videos = [item for item in streams if item.get("codec_type") == "video"]
    audio = [item for item in streams if item.get("codec_type") == "audio"]
    if len(videos) != 1:
        raise ValueError("Expected exactly one video stream")
    video = videos[0]
    actual_width = video.get("width")
    actual_height = video.get("height")
    actual_duration = float(data.get("format", {}).get("duration", "nan"))
    if not isinstance(actual_width, int) or actual_width <= 0 or not isinstance(actual_height, int) or actual_height <= 0 or not math.isfinite(actual_duration) or actual_duration <= 0:
        raise ValueError("Missing usable dimensions or duration")
    failures = []
    if width is not None and (actual_width, actual_height) != (width, height):
        failures.append("dimensions_mismatch")
    if duration is not None and abs(actual_duration - duration) > tolerance:
        failures.append("duration_mismatch")
    if no_audio and audio:
        failures.append("audio_stream_present")
    command([decoder, "-v", "error", "-xerror", "-protocol_whitelist", "file,pipe", "-f", "mov", "-i", source, "-map", "0:v:0", "-map", "0:a?", "-f", "null", "-"])
    digest = hashlib.sha256()
    with path.open("rb") as file:
        for chunk in iter(lambda: file.read(1024 * 1024), b""):
            digest.update(chunk)
    return {
        "schema_version": 1,
        "technical_status": "failed" if failures else "passed",
        "manual_review_status": "pending",
        "width": actual_width,
        "height": actual_height,
        "duration_seconds": actual_duration,
        "bytes": path.stat().st_size,
        "sha256": digest.hexdigest(),
        "video_codec": video.get("codec_name"),
        "audio_codecs": [item.get("codec_name") for item in audio],
        "full_decode": "passed",
        "requirements": {"width": width, "height": height, "duration_seconds": duration, "duration_tolerance_seconds": tolerance, "no_audio_stream": no_audio},
        "failures": failures,
        "scope": "Local MP4 and technical checks only. Visual continuity, product facts, text, sound quality, licensing and billing require separate review.",
    }


def main():
    parser = argparse.ArgumentParser(description="Inspect a local MP4. No API calls, uploads or Key access.")
    parser.add_argument("file", type=Path)
    parser.add_argument("--size", help="Exact expected dimensions, e.g. 720x720")
    parser.add_argument("--duration", type=float, help="Expected seconds")
    parser.add_argument("--duration-tolerance", type=float, default=0.25)
    parser.add_argument("--expect-no-audio", action="store_true", help="Require no audio stream; a silent stream still fails")
    args = parser.parse_args()
    width = height = None
    if args.size:
        try:
            parts = args.size.lower().split("x")
            if len(parts) != 2:
                raise ValueError
            width, height = map(int, parts)
            if width <= 0 or height <= 0:
                raise ValueError
        except ValueError:
            parser.error("--size must contain two positive integer dimensions")
    if not math.isfinite(args.duration_tolerance) or args.duration_tolerance < 0 or (args.duration is not None and (not math.isfinite(args.duration) or args.duration <= 0)):
        parser.error("Duration must be positive and finite; tolerance must be finite and nonnegative")
    result = inspect(args.file, width, height, args.duration, args.duration_tolerance, args.expect_no_audio)
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0 if result["technical_status"] == "passed" else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except (OSError, ValueError, json.JSONDecodeError) as error:
        print(json.dumps({"technical_status": "unconfirmed", "manual_review_status": "pending", "error": str(error)}, ensure_ascii=False), file=sys.stderr)
        sys.exit(2)
