"""rendering/ golden 基线:四 profile 渲染同一份富 fixture,docx 内部 XML 逐字节对账。

目的:给 md 解析路径的重构(块收集器下沉 common 等)上一道「前后字节一致」的硬闸——
上次 rendering 抽取(§8.6)就是靠同款验收零回归的,这里把它固化成测试。

对账面:word/document.xml(正文全部段落/run/表格/超链/书签,无时间戳,确定性)
+ word/footer*.xml(brief 页脚页码域)。core.xml 的创建时间等元数据刻意不比。

golden 更新:行为**有意**变化时(改样式/改解析语义)设 `ZCBOT_REGEN_GOLDENS=1`
重跑本文件再生成,并在 commit message 里说明为什么变;重构类改动 golden 必须零 diff。
fixture 覆盖:H1 信息带(时间窗)/TL;DR 卡片/判断 callout/blockquote/表格/代码块
(含 mermaid 回退)/缺图占位/多种列表模式/参考文献 DOI·URL 条目/引文上标 [1][2] 相邻
/化学式下标/粗斜体行内代码/HR/软换行并合/refs 段外的 [n] 行(并合语义)。
"""
from __future__ import annotations

import difflib
import os
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from rendering import docx_brief, docx_manuscript  # noqa: E402

GOLDEN_DIR = Path(__file__).parent / "golden" / "rendering"
_REGEN = os.getenv("ZCBOT_REGEN_GOLDENS", "").strip() in ("1", "true", "yes")

_FIXTURE = """# 2026 低碳水泥月度简报

方向:低碳胶凝材料 | 时间窗:2026-06 | 深度:精读 | 数据源:院库+Web | 受众:课题组

## 一句话要点

- **LC3 体系**中试放大取得进展,C3S 活性提升 12%[1][2]
- 碳化养护路线成本下降,见 *Nature* 报道[W1]

## 背景与判断

面向**低碳水泥**,重点关注 CO2 矿化与 C3S/Na2SO4 激发体系,不误伤 LC3、EN 197-5。
这一行是软换行,
应当与上一行并合成同一段。

**判断**:碳化养护是当前性价比最高的路线,建议排期中试。

> 取舍纪律:只收录有 DOI 的一手文献;
> 预印本单独标注。

---

### 数据表

| 体系 | 抗压 MPa | 备注 |
|---|---|---|
| LC3-50 | 42.5 | `EN 197-5` |
| CO2 养护 | 48.0 | **中试** |

### 代码与图

```python
def f(x):
    return x + 1  # 缩进必须保留
```

```mermaid
graph TD
%% caption: 工艺流程
A-->B
```

![工艺示意](figures/missing_process.png)

1. 有序列表一
(2) 括号序号
① 圈号
第一条 申报书条款样式(仅 proposal 列表模式命中)

[9] 这行在参考文献段之外,应按普通段落并合语义处理
[10] 与上一行并成同一段

## 参考文献

[1] 10.1016/j.cemconres.2026.107891
[2] 低碳水泥进展. 张三, 水泥学报, 2026-05. DOI: 10.1016/j.jclepro.2026.135790.
[W1] Nature 报道 www.nature.com/articles/x123 碳化养护
[W2] 纯文本条目,无链接可挂
"""


def _write_sections(d: Path) -> Path:
    sec = d / "sections"
    sec.mkdir()
    (sec / "00_main.md").write_text(_FIXTURE, encoding="utf-8")
    return sec


def _extract_xml(docx_path: Path) -> str:
    """document.xml + footer*.xml 拼成一份对账文本(部件名做分隔头)。"""
    parts: list[str] = []
    with zipfile.ZipFile(docx_path) as z:
        names = [n for n in sorted(z.namelist())
                 if n == "word/document.xml" or (n.startswith("word/footer") and n.endswith(".xml"))]
        for n in names:
            parts.append(f"===== {n} =====")
            parts.append(z.read(n).decode("utf-8"))
    return "\n".join(parts)


def _check_golden(test: unittest.TestCase, name: str, actual: str) -> None:
    golden_path = GOLDEN_DIR / f"{name}.xml"
    if _REGEN:
        golden_path.parent.mkdir(parents=True, exist_ok=True)
        golden_path.write_text(actual, encoding="utf-8", newline="\n")
        return
    if not golden_path.is_file():
        test.fail(f"golden 缺失:{golden_path}(首次生成:ZCBOT_REGEN_GOLDENS=1 重跑)")
    expected = golden_path.read_text(encoding="utf-8")
    if actual != expected:
        diff = "\n".join(difflib.unified_diff(
            expected.splitlines(), actual.splitlines(),
            fromfile=f"golden/{name}", tofile="actual", lineterm="", n=2,
        ))
        test.fail(
            f"{name} 渲染输出与 golden 不一致(前 4000 字符 diff):\n{diff[:4000]}\n"
            f"—— 若为有意的样式/语义变更,ZCBOT_REGEN_GOLDENS=1 重生成并在 commit 里说明;"
            f"重构类改动必须零 diff。"
        )


class RenderingGoldenTests(unittest.TestCase):
    def _render_all(self, d: Path) -> dict[str, Path]:
        sec = _write_sections(d)
        outs = {
            "brief_color": d / "brief_color.docx",
            "brief_bw": d / "brief_bw.docx",
            "paper_zh": d / "paper_zh.docx",
            "proposal": d / "proposal.docx",
            "report_toc": d / "report_toc.docx",
        }
        docx_brief.render_sections(sec, outs["brief_color"], color=True)
        docx_brief.render_sections(sec, outs["brief_bw"], color=False)
        docx_manuscript.render_sections("paper", sec, outs["paper_zh"], lang="zh")
        docx_manuscript.render_sections("proposal", sec, outs["proposal"], fund_type="key_rd")
        docx_manuscript.render_sections("report", sec, outs["report_toc"], toc=True)
        return outs

    def test_all_profiles_match_golden(self):
        with tempfile.TemporaryDirectory() as td:
            outs = self._render_all(Path(td))
            for name, path in outs.items():
                with self.subTest(profile=name):
                    _check_golden(self, name, _extract_xml(path))


if __name__ == "__main__":
    unittest.main()
