#!/usr/bin/env python3
import hashlib
import json
import os
import subprocess
import sys
import time
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]
FINAL = PROJECT / "output/final.mp4"
FINAL_RECEIPT = PROJECT / "log/final-render.json"
PROMOTION = PROJECT / "story/promotion.json"
OUTPUT = PROJECT / "output/final-upload.mp4"
STAGING = PROJECT / "output/final-upload.staging.mp4"
INTENT = PROJECT / "log/upload-transcode-intent.json"
SUBMISSION = PROJECT / "log/upload-transcode-submission.json"
RECEIPT = PROJECT / "log/upload-transcode.json"
STAGE = "transcode"


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 request_json(base_url, method, path, payload=None, timeout=30):
    body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() if payload is not None else None
    request = urllib.request.Request(base_url + path, data=body, headers={"Content-Type": "application/json"} if body else {}, method=method)
    with urllib.request.urlopen(request, timeout=timeout) as response:
        return json.load(response)


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:stream_tags=encoder", "-of", "json", str(path)], capture_output=True, text=True, check=True)
    return json.loads(result.stdout)


def main():
    for path in (FINAL, FINAL_RECEIPT, PROMOTION):
        if not path.exists():
            raise RuntimeError(f"missing {path}")
    final_receipt = json.loads(FINAL_RECEIPT.read_text(encoding="utf-8"))
    promotion = json.loads(PROMOTION.read_text(encoding="utf-8"))
    canon = promotion.get("spoken_narration_sha256")
    final_sha = sha(FINAL)
    if not (promotion.get("verified") is True and final_receipt.get("verified") is True and final_receipt.get("source_canon_sha256") == canon and final_receipt.get("output_sha256") == final_sha):
        raise RuntimeError("final/canon/hash receipt mismatch")
    if RECEIPT.exists():
        old = json.loads(RECEIPT.read_text(encoding="utf-8"))
        if old.get("verified") is True and old.get("source_canon_sha256") == canon and old.get("input_sha256") == final_sha and OUTPUT.exists() and OUTPUT.stat().st_size < 1_000_000_000 and old.get("output_sha256") == sha(OUTPUT):
            print(json.dumps({"reused_verified": True, "base_url": old["base_url"], "job_id": old["job_id"], "bytes": OUTPUT.stat().st_size}))
            return 0
    if OUTPUT.exists() and not RECEIPT.exists():
        raise RuntimeError("unreceipted upload output exists")
    input_hashes = {str(FINAL.relative_to(PROJECT)): final_sha}
    submission = None
    if INTENT.exists() or SUBMISSION.exists():
        record_path = SUBMISSION if SUBMISSION.exists() else INTENT
        candidate = json.loads(record_path.read_text(encoding="utf-8"))
        if candidate.get("source_canon_sha256") != canon or candidate.get("input_sha256") != input_hashes:
            raise RuntimeError("prior attempt belongs to different authority/input bytes")
        if not candidate.get("base_url") or not candidate.get("jobs_path"):
            raise RuntimeError("prior attempt lacks pinned replica")
        if not candidate.get("job_id"):
            raise RuntimeError("ambiguous POST; manual reconciliation required")
        submission = candidate
    if submission is None:
        payload = {"input_video_path": str(FINAL), "output_video_path": str(STAGING), "target_output_bytes": 800_000_000, "max_output_bytes": 1_000_000_000, "audio_bitrate_kbps": 96, "width": 1920, "height": 1080, "fps": 30, "overwrite": False}
        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")
        submission = {"schema_version": 1, "project_id": "016", "run_id": "016-transcode-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(STAGING), "source_canon_sha256": canon, "created_at": datetime.now(timezone.utc).isoformat(), "payload": payload}
        atomic_write_json(INTENT, submission)
        created = request_json(base_url, "POST", 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")
        submission.update({"status": "submitted", "job_id": job_id, "submitted_at": datetime.now(timezone.utc).isoformat()})
        atomic_write_json(INTENT, submission)
        atomic_write_json(SUBMISSION, submission)
    base_url, jobs_path, job_id = submission["base_url"], submission["jobs_path"], submission["job_id"]
    deadline = time.monotonic() + 5400
    job = None
    while time.monotonic() < deadline:
        remaining = deadline - time.monotonic()
        job = request_json(base_url, "GET", jobs_path + "/" + job_id, timeout=max(1, min(30, remaining)))
        if job.get("status") in {"completed", "failed", "cancelled"}:
            break
        time.sleep(min(10, max(0, deadline - time.monotonic())))
    else:
        raise RuntimeError("observer timeout; resume same pinned job")
    verification = job.get("verification", {})
    if job.get("status") != "completed" or verification.get("verified") is not True:
        raise RuntimeError(f"job ended {job.get('status')}: {job.get('error')}")
    if not STAGING.is_file():
        raise RuntimeError("completed job has no staging output")
    local = probe(STAGING)
    streams = local.get("streams", [])
    video = [s for s in streams if s.get("width")]
    audio = [s for s in streams if s.get("sample_rate")]
    verified = len(video) == 1 and len(audio) == 1 and video[0].get("codec_name") == "h264" and audio[0].get("codec_name") == "aac" and video[0].get("width") == 1920 and video[0].get("height") == 1080 and STAGING.stat().st_size < 1_000_000_000 and verification.get("under_one_gb") is True
    if not verified:
        raise RuntimeError("staging verification failed")
    os.replace(STAGING, OUTPUT)
    receipt = {"version": 3, "verified": True, "status": "completed", "source_canon_sha256": canon, "run_id": submission.get("run_id"), "base_url": base_url, "jobs_path": jobs_path, "job_id": job_id, "request_sha256": submission.get("request_sha256"), "input": str(FINAL.relative_to(PROJECT)), "input_sha256": final_sha, "output": str(OUTPUT.relative_to(PROJECT)), "output_bytes": OUTPUT.stat().st_size, "output_sha256": sha(OUTPUT), "server_verification": verification, "independent_probe": local, "visual_qa": {"required": False, "status": "not_required", "reason": "contains_user_preapproved_footage"}, "checked_at": datetime.now(timezone.utc).isoformat()}
    atomic_write_json(RECEIPT, receipt)
    print(json.dumps({"verified": True, "base_url": base_url, "job_id": job_id, "bytes": receipt["output_bytes"], "sha256": receipt["output_sha256"]}, ensure_ascii=False))
    return 0


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