#!/usr/bin/env python3
import hashlib
import json
import subprocess
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

from render_pool import RENDER_POOLS, atomic_write_json, canonical_payload_sha256, endpoint_is_idle, select_idle_endpoint

PROJECT = Path(__file__).resolve().parents[1]
TTS = PROJECT / "log/tts-verification.json"
PRONUNCIATION = PROJECT / "log/tts-pronunciation.json"
LAYOUT = PROJECT / "image/layout-manifest.json"
AUDIO = PROJECT / "audio/story-full.wav"
LAYOUT_IMAGE = PROJECT / "image/layout-1920x1080.png"
OUTPUT = PROJECT / "output/footage/footage.mp4"
INTENT = PROJECT / "log/footage-intent.json"
SUBMISSION = PROJECT / "log/footage-submission.json"
STAGE = "footage"
SEED = 15015


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


def duration(path):
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", str(path)], capture_output=True, text=True, check=True)
    return float(result.stdout.strip())


def get_json(url, timeout=30):
    with urllib.request.urlopen(url, timeout=timeout) as response:
        return json.load(response)


def post_json(url, payload, timeout=30):
    request = urllib.request.Request(url, data=json.dumps(payload, sort_keys=True, separators=(",", ":")).encode(), headers={"Content-Type": "application/json"}, method="POST")
    with urllib.request.urlopen(request, timeout=timeout) as response:
        return json.load(response)


def main():
    required = (TTS, PRONUNCIATION, LAYOUT, AUDIO, LAYOUT_IMAGE)
    if any(not path.exists() for path in required):
        raise RuntimeError("verified TTS/local-layout inputs are missing")
    tts = json.loads(TTS.read_text(encoding="utf-8"))
    pronunciation = json.loads(PRONUNCIATION.read_text(encoding="utf-8"))
    layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
    canon = tts.get("source_canon_sha256")
    if not (
        tts.get("verified") is True
        and tts.get("pronunciation_verified") is True
        and pronunciation.get("verified") is True
        and pronunciation.get("semantic_content_changed") is False
        and tts.get("source_sha256") == pronunciation.get("projection_sha256")
        and tts.get("spoken_narration_sha256") == pronunciation.get("source_sha256")
        and tts.get("pronunciation_receipt_sha256") == sha(PRONUNCIATION)
        and tts.get("output_sha256") == sha(AUDIO)
        and layout.get("verified") is True
        and layout.get("status") == "completed"
        and layout.get("builder_location") == "agent_local"
        and layout.get("visual_qa", {}).get("verified") is True
        and layout.get("source_canon_sha256") == canon
        and layout.get("output", {}).get("sha256") == sha(LAYOUT_IMAGE)
    ):
        raise RuntimeError("TTS/local-layout authority mismatch")
    input_hashes = {
        str(AUDIO.relative_to(PROJECT)): sha(AUDIO),
        str(LAYOUT_IMAGE.relative_to(PROJECT)): sha(LAYOUT_IMAGE),
        str(LAYOUT.relative_to(PROJECT)): sha(LAYOUT),
        str(PRONUNCIATION.relative_to(PROJECT)): sha(PRONUNCIATION),
    }
    if INTENT.exists() or SUBMISSION.exists():
        record_path = SUBMISSION if SUBMISSION.exists() else INTENT
        prior = json.loads(record_path.read_text(encoding="utf-8"))
        if prior.get("source_canon_sha256") != canon or prior.get("input_sha256") != input_hashes:
            raise RuntimeError("prior attempt belongs to different authority/input bytes")
        if prior.get("base_url") and prior.get("job_id"):
            job = get_json(prior["base_url"] + prior["jobs_path"] + "/" + prior["job_id"])
            if job.get("status") in {"queued", "running", "completed"}:
                print(json.dumps({"job_id": prior["job_id"], "base_url": prior["base_url"], "status": job.get("status"), "resumed": True}))
                return 0
        raise RuntimeError("existing ambiguous/terminal attempt requires reconciliation")
    if OUTPUT.exists():
        raise RuntimeError("unreceipted output exists")
    payload = {
        "layout": str(LAYOUT_IMAGE), "footage_dir": "/data/video-pipeline/GacMaiAudio/footage", "pattern": "*.mp4", "recursive": True,
        "audio": str(AUDIO), "output": str(OUTPUT), "seed": SEED, "overwrite": False,
        "width": 1920, "height": 1080, "fps": 30, "center_x": 656, "center_y": 0, "center_width": 608, "center_height": 1080,
        "video_encoder": "h264_nvenc", "preset": "p4", "cq": 21,
    }
    base_url, jobs_path = select_idle_endpoint(STAGE)
    if not endpoint_is_idle(base_url, jobs_path):
        raise RuntimeError("selected replica is no longer idle")
    now = datetime.now(timezone.utc).isoformat()
    intent = {
        "schema_version": 1, "project_id": "015", "run_id": "015-footage-v1", "stage": STAGE, "status": "creating",
        "base_url": base_url, "jobs_path": jobs_path, "request_sha256": canonical_payload_sha256(payload), "input_sha256": input_hashes,
        "output_path": str(OUTPUT), "source_canon_sha256": canon, "expected_duration_seconds": duration(AUDIO), "created_at": now,
    }
    atomic_write_json(INTENT, intent)
    created = post_json(base_url + jobs_path, payload)
    job_id = created.get("job_id")
    if not job_id or created.get("status") not in {"queued", "running"}:
        raise RuntimeError("POST result ambiguous; reconcile pinned replica")
    intent.update({"status": "submitted", "job_id": job_id, "submitted_at": datetime.now(timezone.utc).isoformat(), "payload": payload})
    atomic_write_json(INTENT, intent)
    atomic_write_json(SUBMISSION, intent)
    print(json.dumps({"job_id": job_id, "base_url": base_url, "status": created.get("status"), "submission": str(SUBMISSION)}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as exc:
        print("Footage blocked: " + str(exc), file=sys.stderr)
        sys.exit(1)
