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

ROOT = Path(__file__).resolve().parents[1]
TITLE = "Chúng Ta Không Cưới Để Thay Ca"
DISCLAIMER = "Truyện được phát độc quyền tại Gác Mái Audio, nghiêm cấm sao chép dưới mọi hình thức"
REVEAL_LIMIT = (
    "Chỉ gợi áp lực dọn nhà, nhịp sống lệch ca và một cuộc hôn nhân còn khoảng cách; không lộ nguyên nhân khoản tiền mất, "
    "điều kiện của hai gói cứu trợ, ba đêm tách ở, hai lựa chọn cao trào hoặc trạng thái tình cảm cuối."
)
NEGATIVE = (
    "Không chibi, không hoạt hình trẻ em, không 3D nhựa, không ảnh stock, không collage, không bố cục phẳng, "
    "không ánh sáng studio trắng, không neon lòe loẹt, không tím mặc định, không anatomy lỗi, không thương hiệu thật, "
    "không QR, không mockup, không viền giả, không chữ ngẫu nhiên, không sai dấu trong chữ chính. Không váy cưới, nhẫn, "
    "bàn tiệc, ghế trống, mưa, đồng hồ, hồ sơ, màn hình chứng cứ, diễn đàn công khai hoặc hình ảnh hào môn."
)


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")
    data = (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    with open(part, "xb") as handle:
        handle.write(data)
        handle.flush()
        os.fsync(handle.fileno())
    os.replace(part, path)


def main():
    manifest_path = ROOT / "script/project-manifest.json"
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    active = manifest["active_paths"]
    if manifest.get("active_run") != "run-002" or manifest.get("steps", {}).get("story") != "completed" or not manifest.get("canon_sha256"):
        raise RuntimeError("image prompts blocked until active run-002 Story Promotion Gate PASS")
    promotion_path = ROOT / active["promotion_receipt"]
    promotion = json.loads(promotion_path.read_text(encoding="utf-8"))
    if promotion.get("verified") is not True or promotion.get("source_canon_sha256") != manifest["canon_sha256"]:
        raise RuntimeError("promotion receipt missing, failed or stale")

    authority_keys = ["story_brief", "identity_registry", "reveal_ledger", "outline"]
    authority_paths = {key: ROOT / active[key] for key in authority_keys}
    authority_hashes = {key: sha256(path) for key, path in authority_paths.items()}
    brief = json.loads(authority_paths["story_brief"].read_text(encoding="utf-8"))
    identity = json.loads(authority_paths["identity_registry"].read_text(encoding="utf-8"))
    reveal = json.loads(authority_paths["reveal_ledger"].read_text(encoding="utf-8"))
    for name, doc in (("brief", brief), ("identity", identity), ("reveal", reveal)):
        if doc.get("authority_revision") != "v2" or doc.get("run_id") != "run-002":
            raise RuntimeError(f"{name} authority revision drift")
    if brief.get("canonical_title") != TITLE or identity.get("canonical_title") != TITLE:
        raise RuntimeError("canonical title authority drift")
    output = ROOT / active["image_prompts"]
    if output.exists() or output.with_suffix(output.suffix + ".part").exists():
        raise RuntimeError("image prompt bundle must be virgin")

    protagonist = identity["visual_identity"][0]
    spouse = identity["visual_identity"][1]
    common = (
        "Truyện audio hư cấu Trung Quốc đương đại tại Trùng Khánh cuối hè nóng khô. Một căn bếp tầng áp mái chật nhưng có "
        "chiều sâu, thùng carton dọn nhà, máy giặt cũ hơi lệch chân và bình giữ nhiệt đỏ. Tống Dao Quỳnh, 28 tuổi, "
        f"{protagonist['appearance']}. Vệ Hoài Túc, 31 tuổi, {spouse['appearance']}. Hai người cùng hiện diện nhưng ở hai "
        "nhịp hành động: cô chuẩn bị rời ca sáng, anh vừa về ca đêm; khoảng cách tình cảm nằm trong cách họ tránh nhìn thẳng, "
        "không phải đối đầu kịch tính. " + REVEAL_LIMIT
    )
    intro = f'''Tạo key art poster điện ảnh tỷ lệ 16:9, master chính xác 1920x1080 cho Gác Mái Audio.

NỘI DUNG: {common}

STYLE LOCK: digital illustration bán hiện thực cao cấp, editorial cinematic realism, ánh nắng cuối hè xuyên cửa sổ kết hợp practical glow màu cam nhạt, bảng màu xanh khoáng, vàng đất, đỏ gạch và charcoal; texture vải, carton và kim loại tự nhiên; foreground-midground-background rõ. Một cảnh thống nhất, không collage. TUYỆT ĐỐI KHÔNG VẼ ghế, đôn, băng ngồi, sofa hoặc bất kỳ vật dùng để ngồi nào, kể cả ở mép khung.

BỐ CỤC: Provider tự quyết hierarchy, negative space và typography; không áp tọa độ/safe rectangle. Không để chữ che mặt hoặc bị crop. Chữ phải do chính provider render trong ảnh, cấm hậu kỳ chữ, crop hoặc upscale để sửa chữ.

RENDER CHÍNH XÁC BA KHỐI CHỮ TRỰC TIẾP TRONG ẢNH, KHÔNG THÊM CHỮ KHÁC:
1. "Gác Mái Audio"
2. "{TITLE}"
3. "{DISCLAIMER}"

Giữ nguyên từng chữ, dấu tiếng Việt và dấu phẩy; không dịch, không viết lại, không lặp chữ, không thêm slogan. {NEGATIVE}'''
    right = f'''Tạo key art poster điện ảnh dọc tỷ lệ chính xác 656:1080, master 1080x1778, dùng làm panel phải Gác Mái Audio.

NỘI DUNG: {common}

STYLE LOCK: digital illustration bán hiện thực cao cấp, editorial cinematic realism, xanh khoáng, vàng đất, đỏ gạch và charcoal. Hoài Túc là FOCAL POINT DUY NHẤT: nam Việt 30 tuổi, tóc đen ngắn, áo khoác kho hàng xanh đậm, gương mặt và thân người đủ lớn ở tiền cảnh. Dao Quỳnh KHÔNG XUẤT HIỆN trong panel này. Căn bếp, thùng carton và máy giặt tạo chiều sâu đời sống. Không ghế/đôn/vật ngồi, không dark-mode tím, không stock romance.

BỐ CỤC: Provider tự quyết hierarchy và typography; ba khối chữ phải nguyên vẹn sau chuẩn hóa về 656x1080, không che mặt/crop. Chữ do provider render trực tiếp; cấm hậu kỳ chữ, crop hoặc upscale.

RENDER CHÍNH XÁC BA KHỐI CHỮ, KHÔNG THÊM CHỮ KHÁC:
1. "Gác Mái Audio"
2. "{TITLE}"
3. "{DISCLAIMER}"

Giữ nguyên chính tả, dấu và dấu phẩy. {NEGATIVE}'''
    left = f'''Tạo branding panel điện ảnh dọc tỷ lệ chính xác 656:1080, master 1080x1778, dành cho cạnh trái layout video Gác Mái Audio.

STYLE: cùng ngôn ngữ editorial cinematic của poster "{TITLE}". Dao Quỳnh là FOCAL POINT DUY NHẤT: nữ Việt 28 tuổi, tóc đen ngang vai, áo sơ mi xanh nhạt, hiện rõ gương mặt và thân người trong không gian giặt là đời sống với khăn vải, thùng carton và một phần máy giặt thật. Hoài Túc KHÔNG XUẤT HIỆN trong panel này. Không poster cơ khí trừu tượng, không motif quỹ đạo/thiên văn, không ghế/đôn/vật ngồi, không title truyện, không dòng độc quyền, không mockup hoặc layout ba panel.

BỐ CỤC: Provider tự quyết typography. Chữ do provider render trực tiếp; cấm hậu kỳ chữ, crop hoặc upscale.

RENDER CHÍNH XÁC HAI KHỐI CHỮ, KHÔNG THÊM CHỮ KHÁC:
1. "Gác Mái Audio"
2. "Like • Chia sẻ • Đăng ký"

Giữ nguyên chính tả và dấu tiếng Việt. {NEGATIVE}'''

    bundle = {
        "status": "locked",
        "verified": True,
        "image_api_called": False,
        "run_id": "run-002",
        "authority_revision": "v2",
        "source_canon_sha256": manifest["canon_sha256"],
        "canonical_title": TITLE,
        "source_authority_hashes": authority_hashes,
        "visual_inputs": {
            "genre": brief["story_family_primary"],
            "core_conflict": brief["central_question"],
            "protagonist": protagonist,
            "supporting_cast": [spouse],
            "world": brief["setting"],
            "motifs": ["máy giặt cũ rung lệch", "bình giữ nhiệt đỏ", "thùng carton dọn nhà"],
            "palette": "xanh khoáng, vàng đất, đỏ gạch, charcoal",
            "reveal_limit": REVEAL_LIMIT,
        },
        "provider_text_render_required": True,
        "postprocess_text_forbidden": True,
        "crop_for_text_repair_forbidden": True,
        "upscale_for_text_repair_forbidden": True,
        "text_contracts": {
            "intro_and_right": ["Gác Mái Audio", TITLE, DISCLAIMER],
            "left": ["Gác Mái Audio", "Like • Chia sẻ • Đăng ký"],
        },
        "prompts": {"intro": intro, "left_panel": left, "right_panel": right},
        "negative_constraints": NEGATIVE,
        "ocr_checklist": {
            "intro": ["Gác Mái Audio", TITLE, DISCLAIMER],
            "left_panel": ["Gác Mái Audio", "Like • Chia sẻ • Đăng ký"],
            "right_panel": ["Gác Mái Audio", TITLE, DISCLAIMER],
        },
        "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    }
    before = {key: sha256(path) for key, path in authority_paths.items()}
    if before != authority_hashes:
        raise RuntimeError("authority drift before prompt promotion")
    atomic_json(output, bundle)
    after = {key: sha256(path) for key, path in authority_paths.items()}
    if after != authority_hashes:
        raise RuntimeError("authority drift after prompt promotion")
    print(json.dumps({"status": "locked", "verified": True, "image_api_called": False, "path": str(output), "sha256": sha256(output)}, ensure_ascii=False))


if __name__ == "__main__":
    main()
