#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""wdy_anchor_common.py — WDY 锚定公共件（manifest 回写重签 / 页面区块 upsert / 报告）

供 tsa_anchor.py（RFC 3161 时间戳）与 ots_anchor.py（OpenTimestamps/比特币）共用；
anchor_batch.py（至信链）复用其中的 upsert_block() 完成页面区块幂等刷新。
纯标准库。signature 处理与 zhixin_anchor.merge_into_manifest 完全同规
（“记录不删、字段只加不改名”：重签只更新 signature.created_at）。
"""

import base64
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
from wdy_ed25519 import canonical_bytes, ed25519_sign  # noqa: E402

ROOT = HERE.parents[3]                          # ...\cloudflare上wdy站点
WORK = ROOT / "wdy-manifest" / "work"
VERIFY = ROOT / "wdy.org" / "certified" / "verify"
INTL = ROOT / "wdy-manifest" / "intl"
REPORT = INTL / "intl-anchor-report.json"

MONO = "word-break:break-all;font:500 13px/1.7 ui-monospace,Menlo,Consolas,monospace"


def now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def read_key_seed(key_path):
    seed = Path(key_path).read_bytes()
    if len(seed) == 64:  # hex seed 兼容
        seed = bytes.fromhex(seed.decode().strip())
    return seed


def merge_manifest_anchor(manifest_path, key_path, anchor_name, anchor_data,
                          key_id="2026-rot1"):
    """把锚定结果写进 manifest.anchors.<anchor_name> 并用 Ed25519 重新联签。

    返回 (manifest_dict, out_path)。签名时间会更新（内容变更即重签）。
    """
    p = Path(manifest_path)
    m = json.loads(p.read_text(encoding="utf-8"))
    m.pop("signature", None)
    anchors = m.setdefault("anchors", {})
    anchors[anchor_name] = anchor_data
    sig = ed25519_sign(read_key_seed(key_path), canonical_bytes(m))
    m["signature"] = {"alg": "Ed25519", "key_id": key_id,
                      "created_at": now_iso(),
                      "value": base64.b64encode(sig).decode()}
    p.write_text(json.dumps(m, ensure_ascii=False, indent=2), encoding="utf-8")
    return m, p


def publish_to_verify(record_id, files):
    """把文件复制到验证页目录（若存在）。files: {源路径: 发布文件名}。返回发布结果列表。"""
    vdir = VERIFY / record_id
    out = []
    if not vdir.is_dir():
        return out
    for src, name in files.items():
        src = Path(src)
        if not src.exists():
            continue
        dst = vdir / name
        dst.write_bytes(src.read_bytes())
        out.append(name)
    return out


def upsert_block(page_path, begin, end, block, insert_after=None):
    """幂等写入页面区块：

    - 有 begin/end 标记 → 替换两标记之间（含标记行）；
    - 无标记：插到 insert_after 标记行之后；再退化为插到 <div class="terms"> 之前。
    block 必须以 `  <!-- XXX:BEGIN -->` 开头、`  <!-- XXX:END -->\n` 结尾。
    """
    s = page_path.read_text(encoding="utf-8")
    if begin in s and end in s:
        s2 = re.sub(re.escape(begin) + r".*?" + re.escape(end) + r"\n", block, s, flags=re.S)
        mode = "replaced"
    else:
        if insert_after and insert_after in s:
            anchor_line = insert_after + "\n"
            s2 = s.replace(anchor_line, anchor_line + block, 1)
            mode = "inserted-after"
        else:
            if '  <div class="terms">' not in s:
                raise RuntimeError("页面里找不到 <div class=\"terms\"> 插入点")
            s2 = s.replace('  <div class="terms">', block + '  <div class="terms">', 1)
            mode = "inserted"
    if s2 != s:
        page_path.write_text(s2, encoding="utf-8")
    return mode


def append_report(entry):
    """追加一条记录到 wdy-manifest/intl/intl-anchor-report.json（追加式留档）。"""
    REPORT.parent.mkdir(parents=True, exist_ok=True)
    hist = json.loads(REPORT.read_text(encoding="utf-8")) if REPORT.exists() else []
    entry = dict(entry)
    entry.setdefault("at", now_iso())
    hist.append(entry)
    REPORT.write_text(json.dumps(hist, ensure_ascii=False, indent=2), encoding="utf-8")
    return REPORT


# ─────────────────────────── 四语页面区块渲染（幂等：数据全来自 manifest） ───────────────────────────
def render_tsa_block(rid, tsa):
    """「可信时间戳（RFC 3161）」区块。"""
    prov = tsa.get("provider", "")
    serial = tsa.get("serial", "")
    gen = tsa.get("gen_time") or tsa.get("timestamp", "")
    imprint = tsa.get("message_imprint", "")
    tsr = tsa.get("tsr_file", "tsa.tsr")
    tsha = (tsa.get("tsr_sha256") or "")[:16]
    ca = tsa.get("ca_hint", "")
    ca_zh = (f'机构根证书：<a href="{ca}" style="color:var(--gold)">下载 →</a>' if ca else "机构根证书见机构官网")
    ca_en = (f'authority root certificate: <a href="{ca}" style="color:var(--gold)">download →</a>' if ca else "the authority root certificate is on the authority\'s website")
    ca_es = (f'certificado raíz: <a href="{ca}" style="color:var(--gold)">descargar →</a>' if ca else "")
    ca_pt = (f'certificado raiz: <a href="{ca}" style="color:var(--gold)">baixar →</a>' if ca else "")
    return f'''  <!-- ANCHOR:TSA:BEGIN -->
  <h2 style="font-size:19px;margin:32px 0 4px"
    ><span data-zh>可信时间戳（RFC 3161）</span><span data-en>Trusted timestamp (RFC 3161)</span
    ><span data-es>Marca de tiempo confiable (RFC 3161)</span><span data-pt>Carimbo de data/hora confiável (RFC 3161)</span></h2>
  <p style="color:var(--muted);font-size:13.5px"
    ><span data-zh>本记录的电子证据哈希（SHA-256）另已由独立时间戳机构按 RFC 3161 标准签发了<strong>时间戳令牌</strong>。令牌带机构签名，可离线独立核验，不依赖 WDY 或任何系统的持续运营。</span
    ><span data-en>This record's evidence hash (SHA-256) has also been timestamped under the RFC 3161 standard by an independent timestamping authority, which issued a <strong>timestamp token</strong>. The token carries the authority's signature and is independently verifiable offline, without relying on WDY.</span
    ><span data-es>El hash de prueba (SHA-256) de este registro también fue sellado conforme a RFC 3161 por una autoridad de sellado independiente, que emitió un token con su firma, verificable sin conexión y sin depender de WDY.</span
    ><span data-pt>O hash da evidência (SHA-256) deste registro também foi carimbado conforme RFC 3161 por uma autoridade independente, que emitiu um token com a sua assinatura, verificável offline e sem depender da WDY.</span></p>
  <dl>
    <div><dt><span data-zh>时间戳机构</span><span data-en>Timestamping authority</span><span data-es>Autoridad</span><span data-pt>Autoridade</span></dt>
      <dd>{prov}</dd></div>
    <div><dt><span data-zh>序列号</span><span data-en>Serial number</span><span data-es>Número de serie</span><span data-pt>Número de série</span></dt>
      <dd style="{MONO}"><code>{serial}</code></dd></div>
    <div><dt><span data-zh>签发时间</span><span data-en>Issued at</span><span data-es>Emitido el</span><span data-pt>Emitido em</span></dt>
      <dd>{gen}</dd></div>
    <div><dt><span data-zh>电子证据哈希（SHA-256）</span><span data-en>Evidence hash (SHA-256)</span><span data-es>Hash de prueba (SHA-256)</span><span data-pt>Hash da evidência (SHA-256)</span></dt>
      <dd style="{MONO}"><code>{imprint}</code></dd></div>
    <div><dt><span data-zh>令牌文件</span><span data-en>Token file</span><span data-es>Archivo del token</span><span data-pt>Arquivo do token</span></dt>
      <dd><a href="{tsr}" download><span data-zh>下载令牌（{tsr}）→</span><span data-en>download token ({tsr}) →</span><span data-es>descargar token →</span><span data-pt>baixar token →</span></a>（sha256 {tsha}…）</dd></div>
  </dl>
  <p style="margin-top:14px;font-size:13.5px"
    ><span data-zh><strong>核验（离线即可）：</strong><code>openssl ts -verify -in {tsr} -data &lt;原件&gt; -CAfile &lt;机构根证书&gt;</code>　{ca_zh}</span
    ><span data-en><strong>Verify offline:</strong> <code>openssl ts -verify -in {tsr} -data &lt;file&gt; -CAfile &lt;authority root&gt;</code>　{ca_en}</span
    ><span data-es><strong>Verificación sin conexión:</strong> <code>openssl ts -verify -in {tsr} -data &lt;archivo&gt; -CAfile &lt;raíz&gt;</code>　{ca_es}</span
    ><span data-pt><strong>Verificação offline:</strong> <code>openssl ts -verify -in {tsr} -data &lt;arquivo&gt; -CAfile &lt;raiz&gt;</code>　{ca_pt}</span></p>
  <!-- ANCHOR:TSA:END -->
'''


def render_ots_block(rid, ots):
    """「比特币时间戳（OpenTimestamps）」区块。"""
    digest = ots.get("message_digest", "")
    submitted = ots.get("submitted_at", "")
    cals = ots.get("calendars") or []
    status = ots.get("status", "pending")
    height = ots.get("block_height")
    proof = ots.get("proof_file", "bitcoin.ots")
    psha = (ots.get("proof_sha256") or "")[:16]
    cals_html = "<br>".join(f"<code>{u}</code>" for u in cals)
    if status == "confirmed" and height:
        st_zh = f"已确认 — 比特币区块高度 <strong>{height}</strong>"
        st_en = f"Confirmed — Bitcoin block height <strong>{height}</strong>"
        st_es = f"Confirmado — altura de bloque {height}"
        st_pt = f"Confirmado — altura do bloco {height}"
    else:
        st_zh = "已提交至日历网络 — 待比特币确认（一般数小时内完成；确认后本页更新为区块高度）"
        st_en = "Submitted to the calendar network — pending Bitcoin confirmation (usually within hours; this page will be updated with the block height once confirmed)"
        st_es = "Enviado a la red de calendarios — pendiente de confirmación en Bitcoin (normalmente en unas horas)"
        st_pt = "Enviado à rede de calendários — aguardando confirmação na Bitcoin (normalmente em poucas horas)"
    return f'''  <!-- ANCHOR:OTS:BEGIN -->
  <h2 style="font-size:19px;margin:32px 0 4px"
    ><span data-zh>比特币时间戳（OpenTimestamps）</span><span data-en>Bitcoin timestamp (OpenTimestamps)</span
    ><span data-es>Sello de tiempo en Bitcoin (OpenTimestamps)</span><span data-pt>Carimbo de tempo na Bitcoin (OpenTimestamps)</span></h2>
  <p style="color:var(--muted);font-size:13.5px"
    ><span data-zh>本记录的电子证据哈希（SHA-256）另已通过 OpenTimestamps 协议提交至公开日历网络（协议目标：锚定至比特币区块链）。证明文件随本页提供；任何人可对照比特币区块链独立核验，无需信任 WDY 或任何机构。</span
    ><span data-en>This record's evidence hash (SHA-256) has also been submitted to the public calendar network under the OpenTimestamps protocol (whose target chain is Bitcoin). The proof file is served with this page; anyone can verify it against Bitcoin independently — no trust in WDY required.</span
    ><span data-es>El hash de prueba (SHA-256) también fue enviado a la red pública de calendarios (protocolo OpenTimestamps) y comprometido en la blockchain de Bitcoin. El archivo de prueba se ofrece en esta página; cualquiera puede verificarlo contra Bitcoin.</span
    ><span data-pt>O hash da evidência (SHA-256) também foi enviado à rede pública de calendários (protocolo OpenTimestamps) e comprometido na blockchain da Bitcoin. O arquivo de prova acompanha esta página; qualquer pessoa pode verificá-lo.</span></p>
  <dl>
    <div><dt><span data-zh>电子证据哈希（SHA-256）</span><span data-en>Evidence hash (SHA-256)</span><span data-es>Hash de prueba (SHA-256)</span><span data-pt>Hash da evidência (SHA-256)</span></dt>
      <dd style="{MONO}"><code>{digest}</code></dd></div>
    <div><dt><span data-zh>提交时间</span><span data-en>Submitted at</span><span data-es>Enviado el</span><span data-pt>Enviado em</span></dt>
      <dd>{submitted}</dd></div>
    <div><dt><span data-zh>日历服务器</span><span data-en>Calendar servers</span><span data-es>Servidores de calendario</span><span data-pt>Servidores de calendário</span></dt>
      <dd style="font-size:12px;color:var(--muted)">{cals_html}</dd></div>
    <div><dt><span data-zh>状态</span><span data-en>Status</span><span data-es>Estado</span><span data-pt>Status</span></dt>
      <dd><span data-zh>{st_zh}</span><span data-en>{st_en}</span><span data-es>{st_es}</span><span data-pt>{st_pt}</span></dd></div>
    <div><dt><span data-zh>证明文件</span><span data-en>Proof file</span><span data-es>Archivo de prueba</span><span data-pt>Arquivo de prova</span></dt>
      <dd><a href="{proof}" download><span data-zh>下载证明（{proof}）→</span><span data-en>download proof ({proof}) →</span><span data-es>descargar prueba →</span><span data-pt>baixar prova →</span></a>（sha256 {psha}…）</dd></div>
  </dl>
  <p style="margin-top:14px;font-size:13.5px"
    ><span data-zh><strong>核验（无需比特币全节点）：</strong><code>python rules/tools/ots_anchor.py --verify {proof} -f &lt;原件&gt;</code>——内置公共区块浏览器多源对照 Merkle 根；官方工具 <code>ots verify</code> 亦可。</span
    ><span data-en><strong>Verify (no Bitcoin node required):</strong> <code>python rules/tools/ots_anchor.py --verify {proof} -f &lt;file&gt;</code> — cross-checks the Merkle root against multiple public block explorers; the official <code>ots verify</code> also works.</span
    ><span data-es><strong>Verificación (sin nodo):</strong> <code>python rules/tools/ots_anchor.py --verify {proof} -f &lt;archivo&gt;</code></span
    ><span data-pt><strong>Verificação (sem nó):</strong> <code>python rules/tools/ots_anchor.py --verify {proof} -f &lt;arquivo&gt;</code></span></p>
  <!-- ANCHOR:OTS:END -->
'''
