#!/usr/bin/env python3
import datetime
import hashlib
import json
import os
import re
import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
BUNDLE = ROOT / "image/prompts-r2.json"
RECEIPT = ROOT / "log/artwork-generation-r2.json"
GENERATOR = Path.home() / ".hermes/skills/content-creation/tao-anh/scripts/generate_image.py"


def sha256(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 atomic_json(path, value):
    path.parent.mkdir(parents=True, exist_ok=True)
    part = path.with_suffix(path.suffix + ".part")
    part.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    os.replace(part, path)


def check_png(path):
    if not path.is_file() or path.stat().st_size <= 8 or path.read_bytes()[:8] != b"\x89PNG\r\n\x1a\n":
        raise RuntimeError(f"invalid PNG: {path}")


def main():
    manifest = json.loads((ROOT / "script/project-manifest.json").read_text(encoding="utf-8"))
    bundle = json.loads(BUNDLE.read_text(encoding="utf-8"))
    if bundle.get("status") != "locked" or bundle.get("verified") is not True or bundle.get("image_api_called") is not False:
        raise RuntimeError("r2 prompt bundle not virgin/locked")
    if manifest.get("canon_sha256") != bundle.get("source_canon_sha256") or manifest.get("steps", {}).get("story") != "completed":
        raise RuntimeError("Story Gate/canon drift")
    for rel, expected in bundle["source_authority_hashes"].items():
        if sha256(ROOT / rel) != expected:
            raise RuntimeError(f"authority drift: {rel}")
    expected_text = bundle["text_contract"]
    for key, prompt in bundle["prompts"].items():
        hard = prompt.rsplit("TYPOGRAPHY RETRY R2", 1)[-1]
        blocks = re.findall(r'^\d+\. "([^"]+)"$', hard, flags=re.MULTILINE)
        if blocks != expected_text:
            raise RuntimeError(f"r2 exact text contract drift: {key}")
    outputs = {key: ROOT / rel for key, rel in bundle["outputs"].items()}
    results = {}
    if RECEIPT.exists():
        state = json.loads(RECEIPT.read_text(encoding="utf-8"))
        if state.get("status") != "running" or state.get("prompt_bundle_sha256") != sha256(BUNDLE):
            raise RuntimeError("r2 receipt not safely resumable")
        results = state.get("results", {})
        for key, row in results.items():
            check_png(outputs[key])
            if row.get("sha256") != sha256(outputs[key]):
                raise RuntimeError("r2 checkpoint hash drift")
    elif any(path.exists() for path in outputs.values()):
        raise RuntimeError("r2 output exists without checkpoint")
    for key, output in outputs.items():
        if key in results:
            continue
        output.parent.mkdir(parents=True, exist_ok=True)
        subprocess.run(["python3", str(GENERATOR), "--prompt", bundle["prompts"][key], "--output", str(output), "--n", "1", "--detail", "high", "--format", "png", "--timeout", "600"], check=True)
        check_png(output)
        results[key] = {"path": str(output.relative_to(ROOT)), "sha256": sha256(output), "bytes": output.stat().st_size}
        atomic_json(RECEIPT, {"status": "running", "verified": False, "revision": "r2", "provider": "cx/gpt-5.5-image", "source_canon_sha256": bundle["source_canon_sha256"], "prompt_bundle_path": str(BUNDLE.relative_to(ROOT)), "prompt_bundle_sha256": sha256(BUNDLE), "results": results, "updated_at": datetime.datetime.now(datetime.timezone.utc).isoformat()})
    now = datetime.datetime.now(datetime.timezone.utc).isoformat()
    atomic_json(RECEIPT, {"status": "completed", "verified": True, "revision": "r2", "provider": "cx/gpt-5.5-image", "source_canon_sha256": bundle["source_canon_sha256"], "prompt_bundle_path": str(BUNDLE.relative_to(ROOT)), "prompt_bundle_sha256": sha256(BUNDLE), "preserved_left_panel": bundle["preserved_asset"], "results": results, "completed_at": now})
    latest = json.loads(BUNDLE.read_text(encoding="utf-8"))
    latest["image_api_called"] = True
    latest["generation_receipt"] = str(RECEIPT.relative_to(ROOT))
    latest["updated_at"] = now
    atomic_json(BUNDLE, latest)
    print(json.dumps({"status": "completed", "revision": "r2", "results": results}, ensure_ascii=False))


if __name__ == "__main__":
    main()
