#!/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 atomic_write_json, canonical_payload_sha256, endpoint_is_idle, select_idle_endpoint

PROJECT = Path(__file__).resolve().parents[1]
PROMOTION = PROJECT / "story/promotion-report.json"
INTRO_RECEIPT = PROJECT / "log/intro-render.json"
FOOTAGE_RECEIPT = PROJECT / "log/footage-job.json"
INTRO = PROJECT / "output/intro/intro.mp4"
FOOTAGE = PROJECT / "output/footage/footage.mp4"
OUTPUT = PROJECT / "output/final.mp4"
INTENT = PROJECT / "log/final-intent.json"
SUBMISSION = PROJECT / "log/final-submission.json"
STAGE = "final"


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 probe(path):
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration,size", "-show_entries", "stream=index,codec_name,width,height,sample_rate,channels", "-of", "json", str(path)], capture_output=True, text=True, check=True)
    return json.loads(result.stdout)


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 = (PROMOTION, INTRO_RECEIPT, FOOTAGE_RECEIPT, INTRO, FOOTAGE)
    if any(not path.exists() for path in required):
        raise RuntimeError("verified intro/footage inputs are missing")
    promotion = json.loads(PROMOTION.read_text(encoding="utf-8"))
    intro_receipt = json.loads(INTRO_RECEIPT.read_text(encoding="utf-8"))
    footage_receipt = json.loads(FOOTAGE_RECEIPT.read_text(encoding="utf-8"))
    canon = promotion.get("spoken_sha256")
    if not (
        promotion.get("verified") is True
        and intro_receipt.get("verified") is True
        and footage_receipt.get("verified") is True
        and intro_receipt.get("source_canon_sha256") == canon
        and footage_receipt.get("source_canon_sha256") == canon
        and intro_receipt.get("output_sha256") == sha(INTRO)
        and footage_receipt.get("output_sha256") == sha(FOOTAGE)
        and footage_receipt.get("server_verification", {}).get("mirror_applied") is True
        and footage_receipt.get("server_verification", {}).get("source_audio_discarded") is True
    ):
        raise RuntimeError("authority receipt mismatch")
    intro_probe, footage_probe = probe(INTRO), probe(FOOTAGE)
    if not any(s.get("codec_name") == "aac" for s in footage_probe.get("streams", [])):
        raise RuntimeError("footage does not carry narration audio")
    input_hashes = {str(INTRO.relative_to(PROJECT)): sha(INTRO), str(FOOTAGE.relative_to(PROJECT)): sha(FOOTAGE)}
    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 = {"intro_video_path": str(INTRO), "footage_video_path": str(FOOTAGE), "output_video_path": str(OUTPUT), "fps": 30, "width": 1920, "height": 1080}
    if "main_audio_path" in payload:
        raise RuntimeError("final payload must not include main_audio_path")
    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")
    expected = float(intro_probe["format"]["duration"]) + float(footage_probe["format"]["duration"])
    intent = {
        "schema_version": 1, "project_id": "014", "run_id": "014-final-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": expected,
        "intro_duration_seconds": float(intro_probe["format"]["duration"]), "footage_duration_seconds": float(footage_probe["format"]["duration"]),
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    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")}, ensure_ascii=False))
    return 0


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