#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""make_delivery.py — 生成客户交付包（自包含单文件 HTML，可离线打开/打印；中/英双版）

内容：记录信息 + 文件指纹 + 验证页链接 + 二维码（内联 SVG）+（如有）三个独立锚
（至信链 / RFC 3161 时间戳 / OpenTimestamps 比特币）+ 核验步骤 + 口径说明
（不含“司法认可/版权认证”等表述）。

用法
----
  python make_delivery.py --record WDY-2026-0012-P2XK4P            # 默认中英两份
  python make_delivery.py --record WDY-2026-0012-P2XK4P --lang zh  # 只出中文
  python make_delivery.py --record WDY-2026-0012-P2XK4P --lang en  # 只出英文

默认输出：wdy-manifest/delivery/<record_id>-delivery.html     （中文）
         wdy-manifest/delivery/<record_id>-delivery.en.html  （英文）
依赖：仅标准库；二维码取验证页目录里的 qr.svg（内联，离线可用）。
"""

import argparse
import json
import re
from datetime import datetime, timezone
from pathlib import Path

HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[3]
WORK = ROOT / "wdy-manifest" / "work"
VERIFY = ROOT / "wdy.org" / "certified" / "verify"
OUTDIR = ROOT / "wdy-manifest" / "delivery"
BASE = "https://wdy.org"

CSS = """
:root{--ink:#1F2430;--muted:#6B7280;--gold:#B9822B;--line:#E4E0D6}
*{margin:0;padding:0;box-sizing:border-box}
body{background:#F4F1EA;color:var(--ink);font:400 15px/1.8 Georgia,'Noto Serif SC',serif;padding:34px 18px}
.sheet{max-width:780px;margin:0 auto;background:#fff;border:1px solid var(--line);padding:44px 52px;box-shadow:0 2px 20px rgba(31,36,48,.07)}
h1{font-size:24px;margin-bottom:6px} h2{font-size:17px;margin:28px 0 8px;border-left:3px solid var(--gold);padding-left:10px}
.sub{color:var(--muted);font-size:13px;margin-bottom:22px}
table{width:100%;border-collapse:collapse;margin-top:6px}
th,td{border-bottom:1px solid var(--line);padding:9px 6px;text-align:left;vertical-align:top;font-size:14px}
th{width:150px;color:var(--muted);font-weight:600;font:600 12px/1.6 'Noto Sans SC',sans-serif;letter-spacing:.1em}
.mono{font:500 12.5px/1.7 ui-monospace,Menlo,Consolas,monospace;word-break:break-all}
.muted{color:var(--muted);font-size:13.5px}
.qr{display:flex;gap:18px;align-items:center;margin-top:14px;border:1px solid var(--line);border-radius:6px;padding:14px}
.qr svg{flex:0 0 auto} .qr .txt{font-size:13px;color:var(--muted)}
.foot{margin-top:26px;border-top:1px solid var(--line);padding-top:14px;font-size:12.5px;color:var(--muted)}
a{color:var(--gold)}
code{font:500 12.5px/1.7 ui-monospace,Menlo,Consolas,monospace;background:#F7F5EF;padding:1px 5px;border-radius:3px}
@media print{body{background:#fff;padding:0} .sheet{border:0;box-shadow:none;padding:0}}
"""

# ─────────────────────────── 文案（中/英） ───────────────────────────
T = {
    "zh": {
        "lang": "zh-CN",
        "h1": "WDY 存证交付包",
        "sub": "{rid} · 生成于 {date} · WDY Certified（出处存证，非版权登记）",
        "sec_record": "记录信息",
        "sec_claims": "权利主张（申报值）",
        "sec_verify": "如何独立核验（无需信任 WDY）",
        "k_record": "记录号", "k_title": "作品名称", "k_type": "内容类型",
        "k_mode": "生产方式", "k_file": "存证文件", "k_hash": "文件指纹（SHA-256）",
        "k_issued": "签发时间", "k_key": "联签密钥", "k_page": "验证页",
        "mode": {"ai": "AI 生成", "live": "实拍", "hybrid": "混合", "authored": "人类撰写"},
        "mode_ratio": "（AI 占比 {r}）",
        "bytes": "（{n} 字节）",
        "key_note": "（Ed25519，公钥公开）",
        "no_claims": "（未申报权利主张）",
        "c_role": "角色", "c_party": "主体", "c_basis": "依据", "c_scope": "范围",
        "a_zhixin_h": "链上锚定（至信链）",
        "a_zhixin": {"ev": "存证 ID", "tx": "上链交易", "bh": "区块高度", "tt": "上链时间",
                     "tt_note": "链方时间源：北斗卫星授权时间", "sm3": "电子证据哈希（SM3）",
                     "holder": "链上存证主体", "cert": "存证证书（链方签发）",
                     "holder_v": "天津渤海智能机器人有限责任公司（统一社会信用代码 91120222MAC95RTA43）；主体信息以链方证书公示为准",
                     "cert_v": "下载证书 PDF"},
        "a_tsa_h": "可信时间戳（RFC 3161）",
        "a_tsa": {"prov": "时间戳机构", "serial": "序列号", "gen": "签发时间",
                  "imprint": "电子证据哈希（SHA-256）", "file": "令牌文件", "verify": "核验（离线）",
                  "file_v": "下载 {f}", "verify_v": "openssl ts -verify -in {f} -data <原件> -CAfile <机构根证书>"},
        "a_ots_h": "比特币时间戳（OpenTimestamps）",
        "a_ots": {"digest": "电子证据哈希（SHA-256）", "sub": "提交时间", "cals": "日历服务器",
                  "status": "状态", "file": "证明文件", "verify": "核验（无需比特币全节点）",
                  "pending": "已提交至日历网络 — 待比特币确认（一般数小时内完成）",
                  "confirmed": "已确认 — 比特币区块高度 {h}",
                  "file_v": "下载 {f}",
                  "verify_v": "python rules/tools/ots_anchor.py --verify {f} -f <原件>（公共区块浏览器多源对照）"},
        "verify_p": ("1）向权利人索取母版文件；2）重算 SHA-256：Windows <code>certutil -hashfile 文件 SHA256</code>，"
                     "macOS/Linux <code>shasum -a 256 文件</code>；3）与上面的指纹比对，完全一致即证明文件与存证当日逐字节相同；"
                     "4）进阶：下载验证页同目录的 <code>manifest.json</code> 与公示公钥，运行开源工具 <code>wdy_verify.py</code> 复验联签；"
                     "5）独立锚核验：时间戳令牌用 <code>openssl ts -verify</code>，比特币证明用 <code>ots_anchor.py --verify</code>（命令见上表）。"),
        "qr_t": "手机扫码直达验证页",
        "qr_n": "可印在交付手册、片尾字幕、投标文件或海报上。",
        "foot_b": ("本交付包含：记录元数据与文件指纹、可公开复验的验证页、及（如已锚定）独立第三方锚（链方证书 / RFC 3161 时间戳 / OpenTimestamps 比特币证明）。"
                   "<strong>范围说明：</strong>WDY Certified 是公开的出处存证与披露服务，记录“某时点该文件已提交并复核”；"
                   "它不是法律意见、不是版权登记、不设立任何权属。权属以登记证、合同与司法判定为准。权属材料原件由提交方自留。发生争议时，本记录可作为“某时点已披露”的辅助证据。"
                   "<strong>一包两用：</strong>同一份记录可同时用于多个法域——国内（《微短剧发展管理办法》总局令第 16 号第 34 条：标识与可追溯）、"
                   "欧盟（AI Act 第 50 条：发布方举证）、美国（FRE 901(b)(4) / 902(13)–(14)：鉴真与自我鉴真）。"
                   "WDY 提供出处与可追溯层，不替代任何一方的合规义务，也不生成标识本身。"),
    },
    "en": {
        "lang": "en",
        "h1": "WDY Certified — Delivery Pack",
        "sub": "{rid} · generated {date} · WDY Certified (provenance record — not a copyright registration)",
        "sec_record": "Record",
        "sec_claims": "Rights claims (as declared)",
        "sec_verify": "How to verify independently (no trust in WDY required)",
        "k_record": "Record ID", "k_title": "Title", "k_type": "Content type",
        "k_mode": "Production mode", "k_file": "File", "k_hash": "File fingerprint (SHA-256)",
        "k_issued": "Issued at", "k_key": "Co-signing key", "k_page": "Verify page",
        "mode": {"ai": "AI-generated", "live": "live-action", "hybrid": "hybrid", "authored": "authored (human-written)"},
        "mode_ratio": " (AI ratio {r})",
        "bytes": " ({n} bytes)",
        "key_note": " (Ed25519, public key published)",
        "no_claims": "(none declared)",
        "c_role": "Role", "c_party": "Party", "c_basis": "Basis", "c_scope": "Scope",
        "a_zhixin_h": "On-chain anchor (ZhiXinChain)",
        "a_zhixin": {"ev": "Evidence ID", "tx": "Chain transaction", "bh": "Block height", "tt": "Anchored at",
                     "tt_note": "chain time source: BeiDou-authorized", "sm3": "Evidence hash (SM3)",
                     "holder": "On-chain account holder", "cert": "Certificate (chain-issued)",
                     "holder_v": "Tianjin Bohai Intelligent Robot Co., Ltd. (unified social credit code 91120222MAC95RTA43); the entity shown on the chain operator's certificate governs",
                     "cert_v": "download certificate PDF"},
        "a_tsa_h": "Trusted timestamp (RFC 3161)",
        "a_tsa": {"prov": "Timestamping authority", "serial": "Serial number", "gen": "Issued at",
                  "imprint": "Evidence hash (SHA-256)", "file": "Token file", "verify": "Verify (offline)",
                  "file_v": "download {f}", "verify_v": "openssl ts -verify -in {f} -data <file> -CAfile <authority root>"},
        "a_ots_h": "Bitcoin timestamp (OpenTimestamps)",
        "a_ots": {"digest": "Evidence hash (SHA-256)", "sub": "Submitted at", "cals": "Calendar servers",
                  "status": "Status", "file": "Proof file", "verify": "Verify (no Bitcoin node required)",
                  "pending": "Submitted to the calendar network — pending Bitcoin confirmation (usually within hours)",
                  "confirmed": "Confirmed — Bitcoin block height {h}",
                  "file_v": "download {f}",
                  "verify_v": "python rules/tools/ots_anchor.py --verify {f} -f <file> (multi-source public block-explorer check)"},
        "verify_p": ("1) Obtain the master file from the rights holder; 2) recompute SHA-256: Windows <code>certutil -hashfile FILE SHA256</code>, "
                     "macOS/Linux <code>shasum -a 256 FILE</code>; 3) compare with the fingerprint above — a match proves the file is byte-for-byte identical to the recorded day; "
                     "4) advanced: download <code>manifest.json</code> (served next to the verify page) and the published public key, run the open-source <code>wdy_verify.py</code> tool to re-check the co-signature; "
                     "5) independent anchors: verify the timestamp token with <code>openssl ts -verify</code> and the Bitcoin proof with <code>ots_anchor.py --verify</code> (commands above)."),
        "qr_t": "Scan for the verify page",
        "qr_n": "Print it on delivery binders, end credits, tender documents or posters.",
        "foot_b": ("This pack contains: record metadata and file fingerprint, a public re-verifiable verify page, and (where anchored) independent third-party anchors "
                   "(chain-issued certificate / RFC 3161 timestamp / OpenTimestamps Bitcoin proof). <strong>Scope:</strong> WDY Certified is a public provenance and disclosure service: it records that a file was submitted and reviewed at a point in time. "
                   "It is not a legal opinion, not a copyright registration, and does not establish ownership. Ownership is decided by registrations, contracts and courts. Originals stay with the submitter. "
                   "In a dispute, this record may serve as supporting evidence of disclosure at a point in time. "
                   "<strong>One bundle, more than one regime:</strong> the same record travels into more than one framework — China (NRTA Order No. 16, Art. 34: labelling and traceability), "
                   "the EU (AI Act Art. 50: deployer disclosure), and the US (FRE 901(b)(4) / 902(13)–(14): authentication and self-authentication). "
                   "WDY provides the provenance and traceability layer and does not replace any party's compliance obligations, nor generate labels itself."),
    },
}


def load(record_id):
    mp = WORK / f"{record_id}.manifest.json"
    if not mp.exists():
        cand = list(WORK.glob(f"{record_id}*.manifest.json"))
        if not cand:
            raise SystemExit(f"找不到 manifest：{mp}")
        mp = cand[0]
    m = json.loads(mp.read_text(encoding="utf-8"))
    page = VERIFY / record_id / "index.html"
    qr = VERIFY / record_id / "qr.svg"
    return m, mp, page.exists(), (qr if qr.exists() else None)


def inline_qr(qr_path):
    if not qr_path:
        return ""
    s = qr_path.read_text(encoding="utf-8", errors="replace")
    s = re.sub(r"<\?xml.*?\?>", "", s, flags=re.S)
    s = re.sub(r"<!DOCTYPE.*?>", "", s, flags=re.S)
    s = re.sub(r"<svg", '<svg width="150" height="150"', s, count=1)
    return s


def render(m, rid, has_page, qr_svg, lang):
    t = T[lang]
    f = m.get("file", {})
    prov = m.get("provenance", {})
    anchors = m.get("anchors") or {}
    claims = m.get("rights_claims") or []
    url = f"{BASE}/certified/verify/{rid}/" if has_page else None

    def esc(x):
        return str(x).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

    mode = t["mode"].get(prov.get("production_mode"), prov.get("production_mode"))
    mode_cell = esc(mode) + t["mode_ratio"].format(r=prov.get("ai_generated_ratio"))

    claims_html = "".join(
        f"<tr><td>{esc(c.get('role',''))}</td><td>{esc(c.get('party',''))}</td>"
        f"<td>{esc(c.get('basis',''))}</td><td>{esc(c.get('scope',''))}</td></tr>"
        for c in claims) or f'<tr><td colspan="4" style="color:#6B7280">{t["no_claims"]}</td></tr>'

    sections = []

    zx = anchors.get("zhixin")
    if zx:
        lz = t["a_zhixin"]
        sections.append(f"""
  <h2>{t['a_zhixin_h']}</h2>
  <table>
    <tr><th>{lz['ev']}</th><td class="mono">{esc(zx.get('ev_id'))}</td></tr>
    <tr><th>{lz['tx']}</th><td class="mono">{esc(zx.get('tx_id'))}</td></tr>
    <tr><th>{lz['bh']}</th><td>{esc(zx.get('block_height'))}</td></tr>
    <tr><th>{lz['tt']}</th><td>{esc(zx.get('tx_time'))}（{lz['tt_note']}）</td></tr>
    <tr><th>{lz['sm3']}</th><td class="mono">{esc(zx.get('hash_sm3'))}</td></tr>
    <tr><th>{lz['holder']}</th><td>{lz['holder_v']}</td></tr>
    <tr><th>{lz['cert']}</th><td><a href="{esc(zx.get('cert_url'))}">{lz['cert_v']} →</a></td></tr>
  </table>""")

    tsa = anchors.get("tsa")
    if tsa:
        lt = t["a_tsa"]
        tsr = tsa.get("tsr_file", "tsa.tsr")
        sections.append(f"""
  <h2>{t['a_tsa_h']}</h2>
  <table>
    <tr><th>{lt['prov']}</th><td>{esc(tsa.get('provider'))}（{esc(tsa.get('endpoint'))}）</td></tr>
    <tr><th>{lt['serial']}</th><td class="mono">{esc(tsa.get('serial'))}</td></tr>
    <tr><th>{lt['gen']}</th><td>{esc(tsa.get('gen_time') or tsa.get('timestamp'))}</td></tr>
    <tr><th>{lt['imprint']}</th><td class="mono">{esc(tsa.get('message_imprint'))}</td></tr>
    <tr><th>{lt['file']}</th><td>{lt['file_v'].format(f=tsr)}（sha256 {esc((tsa.get('tsr_sha256') or '')[:16])}…）</td></tr>
    <tr><th>{lt['verify']}</th><td class="mono"><code>{esc(lt['verify_v'].format(f=tsr))}</code></td></tr>
  </table>""")

    ots = anchors.get("opentimestamps")
    if ots:
        lo = t["a_ots"]
        pf = ots.get("proof_file", "bitcoin.ots")
        if ots.get("status") == "confirmed" and ots.get("block_height"):
            status = lo["confirmed"].format(h=ots.get("block_height"))
        else:
            status = lo["pending"]
        cals = " · ".join(ots.get("calendars") or [])
        sections.append(f"""
  <h2>{t['a_ots_h']}</h2>
  <table>
    <tr><th>{lo['digest']}</th><td class="mono">{esc(ots.get('message_digest'))}</td></tr>
    <tr><th>{lo['sub']}</th><td>{esc(ots.get('submitted_at'))}</td></tr>
    <tr><th>{lo['cals']}</th><td class="muted">{esc(cals)}</td></tr>
    <tr><th>{lo['status']}</th><td>{status}</td></tr>
    <tr><th>{lo['file']}</th><td>{lo['file_v'].format(f=pf)}（sha256 {esc((ots.get('proof_sha256') or '')[:16])}…）</td></tr>
    <tr><th>{lo['verify']}</th><td class="mono"><code>{esc(lo['verify_v'].format(f=pf))}</code></td></tr>
  </table>""")

    anchors_html = "".join(sections)

    body = f"""
<div class="sheet">
  <h1>{t['h1']}</h1>
  <div class="sub">{t['sub'].format(rid=esc(rid), date=datetime.now(timezone.utc).strftime('%Y-%m-%d'))}</div>

  <h2>{t['sec_record']}</h2>
  <table>
    <tr><th>{t['k_record']}</th><td class="mono">{esc(rid)}</td></tr>
    <tr><th>{t['k_title']}</th><td>{esc(m.get('title',''))}</td></tr>
    <tr><th>{t['k_type']}</th><td>{esc(m.get('content_type',''))}</td></tr>
    <tr><th>{t['k_mode']}</th><td>{mode_cell}</td></tr>
    <tr><th>{t['k_file']}</th><td>{esc(f.get('filename',''))}{t['bytes'].format(n=f.get('size_bytes',''))}</td></tr>
    <tr><th>{t['k_hash']}</th><td class="mono">{esc(f.get('sha256',''))}</td></tr>
    <tr><th>{t['k_issued']}</th><td>{esc(m.get('created_at',''))}</td></tr>
    <tr><th>{t['k_key']}</th><td>{(m.get('signature') or {}).get('key_id','')}{t['key_note']}</td></tr>
    <tr><th>{t['k_page']}</th><td>{f'<a href="{url}">{url}</a>' if url else '—'}</td></tr>
  </table>

  <h2>{t['sec_claims']}</h2>
  <table><tr><th>{t['c_role']}</th><th>{t['c_party']}</th><th>{t['c_basis']}</th><th>{t['c_scope']}</th></tr>{claims_html}</table>
{anchors_html}

  <h2>{t['sec_verify']}</h2>
  <p class="muted">{t['verify_p']}</p>
  <div class="qr">{qr_svg}<div class="txt"><strong>{t['qr_t']}</strong><br>{url if url else ''}<br>{t['qr_n']}</div></div>

  <div class="foot">{t['foot_b']}</div>
</div>"""

    return ("<!DOCTYPE html>\n<html lang=\"" + t["lang"] + "\"><head><meta charset=\"UTF-8\">\n"
            "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
            f"<title>{t['h1']} · {esc(rid)}</title>\n<style>{CSS}</style></head><body>\n"
            + body + "\n</body></html>\n")


def main():
    ap = argparse.ArgumentParser(description="生成 WDY 交付包（自包含 HTML，中/英）")
    ap.add_argument("--record", required=True)
    ap.add_argument("--lang", choices=["zh", "en", "both"], default="both")
    ap.add_argument("--out", default=None, help="仅 --lang 单语时可用自定义输出路径")
    a = ap.parse_args()
    m, mp, has_page, qr = load(a.record)
    rid = m.get("record_id", a.record)
    OUTDIR.mkdir(parents=True, exist_ok=True)
    langs = ["zh", "en"] if a.lang == "both" else [a.lang]
    for lang in langs:
        if a.out and len(langs) == 1:
            out = Path(a.out)
        else:
            suffix = "" if lang == "zh" else ".en"
            out = OUTDIR / f"{rid}-delivery{suffix}.html"
        out.write_text(render(m, rid, has_page, inline_qr(qr), lang), encoding="utf-8")
        anchors = [k for k in ("zhixin", "tsa", "opentimestamps") if (m.get("anchors") or {}).get(k)]
        print(f"交付包已生成：{out}")
        print(f"  记录 {rid}｜锚定 {('、'.join(anchors) or '无')}｜验证页 {'有' if has_page else '无'}｜二维码 {'内联' if qr else '缺'}")


if __name__ == "__main__":
    main()
