#!/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]
MANIFEST = ROOT / "script/project-manifest.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:
        raise RuntimeError(f"missing/empty image: {path}")
    if path.read_bytes()[:8] != b"\x89PNG\r\n\x1a\n":
        raise RuntimeError(f"not PNG: {path}")


def prompt_text_blocks(prompt):
    if "RENDER CHÍNH XÁC" not in prompt:
        raise RuntimeError("artwork prompt lacks exact provider-rendered text contract")
    block = prompt.split("RENDER CHÍNH XÁC", 1)[1]
    return re.findall(r'^\d+\. "([^"]+)"$', block, flags=re.MULTILINE)


def main():
    manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
    active = manifest["active_paths"]
    prompts_path = ROOT / active["image_prompts"]
    receipt_path = ROOT / active["artwork_generation_receipt"]
    outputs = {
        "intro": ROOT / active["intro_poster_master"],
        "left_panel": ROOT / active["left_panel_master"],
        "right_panel": ROOT / active["right_panel_master"],
    }
    if manifest.get("steps", {}).get("story") != "completed" or not manifest.get("canon_sha256"):
        raise RuntimeError("artwork is blocked until Story Promotion Gate PASS")
    bundle = json.loads(prompts_path.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("image prompt bundle not virgin/locked")
    expected_blocks = {
        "intro": bundle.get("text_contracts", {}).get("intro_and_right"),
        "left_panel": bundle.get("text_contracts", {}).get("left"),
        "right_panel": bundle.get("text_contracts", {}).get("intro_and_right"),
    }
    for key, expected in expected_blocks.items():
        prompt = bundle["prompts"][key]
        if not expected or prompt_text_blocks(prompt) != expected or "no text" in prompt.casefold():
            raise RuntimeError(f"artwork text contract drift: {key}")
    for authority_key, expected in bundle["source_authority_hashes"].items():
        if authority_key not in active or sha256(ROOT / active[authority_key]) != expected:
            raise RuntimeError(f"artwork authority drift: {authority_key}")
    results = {}
    if receipt_path.exists():
        checkpoint = json.loads(receipt_path.read_text(encoding="utf-8"))
        if checkpoint.get("status") != "running" or checkpoint.get("source_canon_sha256") != manifest["canon_sha256"] or checkpoint.get("source_authority_hashes") != bundle["source_authority_hashes"]:
            raise RuntimeError("artwork receipt is not a reusable running checkpoint")
        results = checkpoint.get("results", {})
        for key, row in results.items():
            if key not in outputs or row.get("path") != str(outputs[key].relative_to(ROOT)):
                raise RuntimeError("artwork checkpoint output map drift")
            check_png(outputs[key])
            if sha256(outputs[key]) != row.get("sha256"):
                raise RuntimeError("artwork checkpoint artifact drift")
    elif any(x.exists() for x in outputs.values()):
        raise RuntimeError("artwork 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)
        for authority_key, expected in bundle["source_authority_hashes"].items():
            if authority_key not in active or sha256(ROOT / active[authority_key]) != expected:
                raise RuntimeError(f"authority drift after provider call: {authority_key}")
        results[key] = {"path": str(output.relative_to(ROOT)), "sha256": sha256(output), "bytes": output.stat().st_size}
        atomic_json(receipt_path, {"status": "running", "verified": False, "provider": "cx/gpt-5.5-image", "source_canon_sha256": manifest["canon_sha256"], "source_authority_hashes": bundle["source_authority_hashes"], "results": results, "updated_at": datetime.datetime.now(datetime.timezone.utc).isoformat()})
    now = datetime.datetime.now(datetime.timezone.utc).isoformat()
    receipt = {"status": "completed", "verified": True, "provider": "cx/gpt-5.5-image", "source_canon_sha256": manifest["canon_sha256"], "source_authority_hashes": bundle["source_authority_hashes"], "results": results, "completed_at": now}
    atomic_json(receipt_path, receipt)
    bundle["image_api_called"] = True
    bundle["generation_receipt"] = str(receipt_path.relative_to(ROOT))
    bundle["updated_at"] = now
    atomic_json(prompts_path, bundle)
    manifest["artwork"] = {"status": "generated", "verified": True, "receipt": str(receipt_path.relative_to(ROOT)), "completed_at": now}
    manifest["updated_at"] = now
    atomic_json(MANIFEST, manifest)
    print(json.dumps({"status": "completed", "targets": results}, ensure_ascii=False))


if __name__ == "__main__":
    main()
