#!/usr/bin/env python3
from __future__ import annotations

import hashlib
import json
import os
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PROJECT_MANIFEST = ROOT / "script/project-manifest.json"
RECEIPT_KEYS = {
    "promotion": "promotion_receipt", "tts": "tts_receipt", "layout": "layout_receipt",
    "intro": "intro_receipt", "footage": "footage_receipt", "footage_qa": "footage_qa_receipt",
    "final": "final_receipt", "final_qa": "final_qa_receipt",
    "transcode": "transcode_receipt", "metadata": "metadata_receipt",
}
ARTIFACT_KEYS = {
    "canon": "story_canon", "narration": "spoken_narration", "audio": "tts_audio",
    "layout": "layout", "thumbnail": "intro_normalized", "intro": "intro",
    "footage": "footage", "final": "final_master", "upload": "final_upload", "metadata": "metadata",
}


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


def resolve_active(manifest: dict, key: str) -> Path:
    value = (manifest.get("active_paths") or {}).get(key)
    if not isinstance(value, str) or not value:
        raise RuntimeError(f"active_paths.{key} is missing")
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    try:
        path.resolve().relative_to(ROOT.resolve())
    except ValueError as exc:
        raise RuntimeError(f"active path escapes project: {key}") from exc
    return path


def atomic_json(path: Path, value: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    data = (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        with os.fdopen(fd, "wb") as handle:
            handle.write(data); handle.flush(); os.fsync(handle.fileno())
        os.replace(name, path)
    finally:
        if os.path.exists(name): os.unlink(name)


def main() -> int:
    project = json.loads(PROJECT_MANIFEST.read_text(encoding="utf-8"))
    receipts = {name: resolve_active(project, key) for name, key in RECEIPT_KEYS.items()}
    artifacts = {name: resolve_active(project, key) for name, key in ARTIFACT_KEYS.items()}
    output = resolve_active(project, "publish_ready_receipt")
    if output.exists():
        raise RuntimeError("publish-ready receipt already exists and requires audit/invalidation")
    missing_receipts = [str(path) for path in receipts.values() if not path.exists()]
    missing_artifacts = [str(path) for path in artifacts.values() if not path.exists()]
    if missing_receipts or missing_artifacts:
        raise RuntimeError(f"publish inputs missing; receipts={missing_receipts}; artifacts={missing_artifacts}")
    values = {name: json.loads(path.read_text(encoding="utf-8")) for name, path in receipts.items()}
    promotion = values["promotion"]
    canon_hash = promotion.get("canon_sha256")
    receipt_checks = {}
    for name, value in values.items():
        current_hash = value.get("canon_sha256") if name == "promotion" else value.get("source_canon_sha256")
        receipt_checks[name] = {"completed": value.get("status") == "completed", "verified": value.get("verified") is True, "canon_current": current_hash == canon_hash, "path": str(receipts[name]), "receipt_sha256": sha256(receipts[name])}
    artifact_hashes = {name: sha256(path) for name, path in artifacts.items()}
    layout_relative = str(artifacts["layout"].relative_to(ROOT))
    hash_links = {
        "canon": artifact_hashes["canon"] == canon_hash,
        "narration": artifact_hashes["narration"] == promotion.get("spoken_narration_sha256"),
        "audio": artifact_hashes["audio"] == values["tts"].get("artifact_sha256"),
        "layout": artifact_hashes["layout"] == values["layout"].get("artifacts", {}).get(layout_relative, {}).get("sha256"),
        "intro": artifact_hashes["intro"] == values["intro"].get("artifact_sha256"),
        "footage": artifact_hashes["footage"] == values["footage"].get("artifact_sha256"),
        "final": artifact_hashes["final"] == values["final"].get("artifact_sha256"),
        "upload": artifact_hashes["upload"] == values["transcode"].get("artifact_sha256"),
        "metadata": artifact_hashes["metadata"] == values["metadata"].get("artifact_sha256"),
    }
    upload_size = artifacts["upload"].stat().st_size
    checks = {"all_receipts_completed": all(value["completed"] for value in receipt_checks.values()), "all_receipts_verified": all(value["verified"] for value in receipt_checks.values()), "all_receipts_current_canon": all(value["canon_current"] for value in receipt_checks.values()), "all_artifact_hash_links": all(hash_links.values()), "upload_under_one_gb": upload_size < 1_000_000_000, "no_publish_block_marker": not any(ROOT.glob("DO_NOT_PUBLISH*")) and not (ROOT / "publish_block").exists()}
    verified = all(checks.values())
    result = {"status": "completed" if verified else "failed", "verified": verified, "project_id": project.get("project_id"), "source_canon_sha256": canon_hash, "receipt_checks": receipt_checks, "artifacts": {name: {"path": str(path), "sha256": artifact_hashes[name], "bytes": path.stat().st_size} for name, path in artifacts.items()}, "hash_links": {name: {"verified": value, "value": value} for name, value in hash_links.items()}, "checks": {name: {"verified": value, "value": value} for name, value in checks.items()}, "upload_bytes": upload_size, "blockers": [name for name, value in checks.items() if not value], "hash_method": "sha256 streaming 8 MiB"}
    atomic_json(output, result)
    print(json.dumps({"status": result["status"], "verified": verified, "blockers": result["blockers"], "receipt": str(output)}, ensure_ascii=False))
    return 0 if verified else 1


if __name__ == "__main__":
    try: sys.exit(main())
    except Exception as exc:
        print(json.dumps({"status": "failed", "verified": False, "error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr); sys.exit(1)
