#!/usr/bin/env python3
"""中英文档一致性守护工作流（doc-sync-automation）。

采集 → 检测 → 报告 → 回写（可选）：
1. 采集：通过 gitlink-cli 发现双语文档对并拉取两版内容
2. 检测：确定性结构比对（章节大纲 / 代码块 / 表格行数 / 版本号）
3. 报告：输出分级（严重/中等/轻微）Markdown 漂移报告
4. 回写：--apply 时把报告作为 tracking issue 提交到 GitLink

纯标准库实现（Python >= 3.9），gitlink-cli 为唯一外部依赖。
默认 dry-run，不写远端。
"""

import argparse
import json
import re
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path

# 翻译文档命名约定：主文档 X.md -> X{后缀}
TRANSLATION_SUFFIXES = [".zh-CN.md", "_zh.md", ".zh.md", "-zh.md"]

SEVERITY_ORDER = {"严重": 0, "中等": 1, "轻微": 2}
SEVERITY_ICON = {"严重": "🔴", "中等": "🟡", "轻微": "🟢"}


@dataclass
class Finding:
    severity: str  # 严重 | 中等 | 轻微
    category: str
    location: str
    detail: str


@dataclass
class DocStructure:
    headings: list = field(default_factory=list)  # [(level, text)]
    code_blocks: int = 0
    code_lines: int = 0
    table_rows: int = 0
    versions: list = field(default_factory=list)  # 形如 Go 1.26 / v0.2.0 的版本号


def run_cli(args, cli="gitlink-cli"):
    """调用 gitlink-cli 并返回 stdout 文本。"""
    result = subprocess.run(
        [cli, *args], capture_output=True, text=True, check=False
    )
    if result.returncode != 0:
        raise RuntimeError(
            f"gitlink-cli {' '.join(args)} 失败: {result.stderr.strip() or result.stdout.strip()}"
        )
    return result.stdout


def fetch_file(owner, repo, path, ref, cli="gitlink-cli"):
    args = ["file", "+view", "--owner", owner, "--repo", repo, "--path", path, "--raw"]
    if ref:
        args += ["--ref", ref]
    return run_cli(args, cli=cli)


def list_entries(owner, repo, ref, path="", cli="gitlink-cli"):
    args = ["repo", "+tree", "--owner", owner, "--repo", repo, "--format", "json"]
    if ref:
        args += ["--ref", ref]
    if path:
        args += ["--path", path]
    out = run_cli(args, cli=cli)
    payload = json.loads(out)
    data = payload.get("data", payload)
    if isinstance(data, str):
        data = json.loads(data)
    entries = data.get("entries", data) if isinstance(data, dict) else data
    names = []
    if isinstance(entries, list):
        for e in entries:
            if isinstance(e, dict) and e.get("name"):
                names.append(e["name"])
    return names


def discover_pairs(names):
    """按命名约定从文件名（可含路径前缀）列表中发现文档对。"""
    nameset = set(names)
    pairs = []
    for name in sorted(nameset):
        if not name.endswith(".md") or any(name.endswith(s) for s in TRANSLATION_SUFFIXES):
            continue
        stem = name[: -len(".md")]
        for suffix in TRANSLATION_SUFFIXES:
            translation = stem + suffix
            if translation in nameset:
                pairs.append((name, translation))
                break
    return pairs


VERSION_RE = re.compile(r"\b(?:go|node(?:\.js)?|python|v)\s?(\d+\.\d+(?:\.\d+)?)\b", re.I)


def parse_structure(text):
    """提取文档结构：标题大纲、代码块、表格行、版本号。"""
    s = DocStructure()
    in_code = False
    code_lines = 0
    for line in text.splitlines():
        stripped = line.strip()
        if stripped.startswith("```"):
            if in_code:
                s.code_blocks += 1
                s.code_lines += code_lines
                code_lines = 0
            in_code = not in_code
            continue
        if in_code:
            code_lines += 1
            continue
        m = re.match(r"^(#{1,6})\s+(.*)$", stripped)
        if m:
            s.headings.append((len(m.group(1)), m.group(2).strip()))
            continue
        if stripped.startswith("|") and stripped.endswith("|") and not re.match(r"^\|[\s:|-]+\|$", stripped):
            s.table_rows += 1
        s.versions.extend(v for v in VERSION_RE.findall(line))
    return s


def compare_structures(base_path, trans_path, base, trans):
    """确定性漂移检测，返回 Finding 列表。"""
    findings = []

    # 1. 章节数量漂移（结构不对齐 = 严重信号）
    b_top = [h for h in base.headings if h[0] <= 2]
    t_top = [h for h in trans.headings if h[0] <= 2]
    if len(b_top) != len(t_top):
        more, fewer = (base_path, trans_path) if len(b_top) > len(t_top) else (trans_path, base_path)
        findings.append(Finding(
            "严重", "章节数不一致", "一级/二级标题",
            f"{more} 有 {max(len(b_top), len(t_top))} 节，{fewer} 只有 {min(len(b_top), len(t_top))} 节，疑似缺失章节",
        ))

    # 2. 代码块漂移（示例不同步 = 中等）
    if base.code_blocks != trans.code_blocks:
        findings.append(Finding(
            "中等", "代码块数不一致", "全文代码示例",
            f"{base_path} 有 {base.code_blocks} 个代码块，{trans_path} 有 {trans.code_blocks} 个",
        ))
    elif abs(base.code_lines - trans.code_lines) > max(5, base.code_lines // 20):
        findings.append(Finding(
            "中等", "代码行数漂移", "全文代码示例",
            f"代码行数 {base.code_lines} vs {trans.code_lines}，差异超过 5%",
        ))

    # 3. 表格行数漂移（功能表滞后 = 中等）
    if base.table_rows != trans.table_rows:
        findings.append(Finding(
            "中等", "表格行数不一致", "全文表格",
            f"{base_path} 共 {base.table_rows} 行表格，{trans_path} 共 {trans.table_rows} 行，疑似功能表滞后",
        ))

    # 4. 版本号漂移（轻微）
    b_ver, t_ver = sorted(set(base.versions)), sorted(set(trans.versions))
    if b_ver != t_ver:
        only_b = [v for v in b_ver if v not in t_ver]
        only_t = [v for v in t_ver if v not in b_ver]
        findings.append(Finding(
            "轻微", "版本号不一致", "安装/依赖说明",
            f"仅 {base_path} 出现: {only_b or '无'}；仅 {trans_path} 出现: {only_t or '无'}",
        ))

    findings.sort(key=lambda f: SEVERITY_ORDER[f.severity])
    return findings


def render_report(owner, repo, ref, results):
    lines = [f"# 文档一致性报告：{owner}/{repo}（ref: {ref or '默认分支'}）", ""]
    total = sum(len(f) for _, _, f in results)
    if total == 0:
        lines.append("✅ 所有文档对结构一致，未检测到漂移。")
    for base_path, trans_path, findings in results:
        lines.append(f"## {base_path} ⇄ {trans_path}")
        lines.append("")
        if not findings:
            lines.append("✅ 无漂移。")
            lines.append("")
            continue
        lines.append("| 等级 | 类型 | 位置 | 说明 |")
        lines.append("|------|------|------|------|")
        for f in findings:
            lines.append(f"| {SEVERITY_ICON[f.severity]} {f.severity} | {f.category} | {f.location} | {f.detail} |")
        lines.append("")
    lines.append("---")
    lines.append("*由 doc-sync-automation 工作流生成（确定性结构比对，同输入同输出）。*")
    return "\n".join(lines)


def create_tracking_issue(owner, repo, report, cli="gitlink-cli"):
    out = run_cli([
        "issue", "+create", "--owner", owner, "--repo", repo,
        "--title", "[doc-sync] 中英文档漂移报告",
        "--body", report,
        "--format", "json",
    ], cli=cli)
    return out


def main():
    parser = argparse.ArgumentParser(description="中英文档一致性守护工作流")
    parser.add_argument("--owner", required=True)
    parser.add_argument("--repo", required=True)
    parser.add_argument("--ref", default="")
    parser.add_argument("--pair", action="append", default=[],
                        help="手动指定文档对，格式 base.md:translation.md，可多次")
    parser.add_argument("--apply", action="store_true",
                        help="把漂移报告作为 tracking issue 回写到 GitLink（默认 dry-run）")
    parser.add_argument("--output-dir", default="outputs")
    parser.add_argument("--cli", default="gitlink-cli")
    args = parser.parse_args()

    if args.pair:
        pairs = [tuple(p.split(":", 1)) for p in args.pair]
    else:
        names = list_entries(args.owner, args.repo, args.ref, cli=args.cli)
        if "docs" in names:
            names += [
                f"docs/{n}"
                for n in list_entries(args.owner, args.repo, args.ref, path="docs", cli=args.cli)
            ]
        pairs = discover_pairs(names)
        if not pairs:
            print("未发现双语文档对（可用 --pair 手动指定）", file=sys.stderr)
            return 1

    results = []
    for base_path, trans_path in pairs:
        base_text = fetch_file(args.owner, args.repo, base_path, args.ref, cli=args.cli)
        trans_text = fetch_file(args.owner, args.repo, trans_path, args.ref, cli=args.cli)
        findings = compare_structures(
            base_path, trans_path, parse_structure(base_text), parse_structure(trans_text)
        )
        results.append((base_path, trans_path, findings))

    report = render_report(args.owner, args.repo, args.ref, results)
    out_dir = Path(args.output_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    report_path = out_dir / f"doc-sync-{args.owner}-{args.repo}.md"
    report_path.write_text(report, encoding="utf-8")
    print(report)
    print(f"\n报告已保存：{report_path}", file=sys.stderr)

    severe = sum(1 for _, _, fs in results for f in fs if f.severity == "严重")
    if args.apply and any(fs for _, _, fs in results):
        create_tracking_issue(args.owner, args.repo, report, cli=args.cli)
        print("已创建 tracking issue。", file=sys.stderr)

    return 2 if severe else 0


if __name__ == "__main__":
    sys.exit(main())
