#!/usr/bin/env python3
from __future__ import annotations

import csv
import html
import json
from collections import Counter, defaultdict
from pathlib import Path


OUT = Path("work/2026-06-01-yunxiao-wedrive-traceability")


def read_csv(name: str) -> list[dict]:
    with (OUT / name).open(encoding="utf-8", newline="") as f:
        return list(csv.DictReader(f))


def write_csv(name: str, fieldnames: list[str], rows: list[dict]) -> None:
    with (OUT / name).open("w", encoding="utf-8", newline="") as f:
        w = csv.DictWriter(f, fieldnames=fieldnames)
        w.writeheader()
        w.writerows(rows)


def table(rows: list[dict], cols: list[tuple[str, str]], limit: int | None = None) -> str:
    use_rows = rows if limit is None else rows[:limit]
    head = "".join(f"<th>{html.escape(label)}</th>" for key, label in cols)
    body = []
    for row in use_rows:
        cells = "".join(f"<td>{html.escape(str(row.get(key, '')))}</td>" for key, label in cols)
        body.append(f"<tr>{cells}</tr>")
    return f"<table><thead><tr>{head}</tr></thead><tbody>{''.join(body)}</tbody></table>"


def main() -> None:
    projects = read_csv("yunxiao-projects.csv")
    workitems = read_csv("yunxiao-workitems.csv")
    mapping = read_csv("yunxiao-wedrive-project-map.csv")
    run = json.loads((OUT / "run-summary.json").read_text(encoding="utf-8"))

    by_project = defaultdict(lambda: Counter())
    status_by_project = defaultdict(Counter)
    for row in workitems:
        key = row["yunxiao_project_id"]
        by_project[key]["total"] += 1
        by_project[key][row["category"]] += 1
        status_by_project[key][row["status"]] += 1

    map_by_id = {row["yunxiao_project_id"]: row for row in mapping}
    project_names = {row["yunxiao_project_id"]: row for row in projects}
    summary_rows = []
    for pid, counts in by_project.items():
        p = project_names.get(pid, {})
        m = map_by_id.get(pid, {})
        top_status = "; ".join(f"{k}:{v}" for k, v in status_by_project[pid].most_common(5))
        summary_rows.append(
            {
                "custom_code": p.get("custom_code", ""),
                "project_name": p.get("project_name", ""),
                "yunxiao_project_id": pid,
                "workitem_total": counts["total"],
                "requirement_count": counts["Req"],
                "task_count": counts["Task"],
                "top_statuses": top_status,
                "wedrive_match_status": m.get("match_status", ""),
                "wedrive_project_or_topic": m.get("wedrive_project_or_topic", ""),
                "wedrive_folder_path": m.get("wedrive_folder_path", ""),
            }
        )
    summary_rows.sort(key=lambda r: int(r["workitem_total"]), reverse=True)

    write_csv(
        "project-workitem-folder-summary.csv",
        [
            "custom_code",
            "project_name",
            "yunxiao_project_id",
            "workitem_total",
            "requirement_count",
            "task_count",
            "top_statuses",
            "wedrive_match_status",
            "wedrive_project_or_topic",
            "wedrive_folder_path",
        ],
        summary_rows,
    )

    status_counter = Counter(row["status"] for row in workitems)
    category_counter = Counter(row["category"] for row in workitems)
    match_counter = Counter(row["match_status"] for row in mapping)
    weak_or_unmatched = [r for r in mapping if r["match_status"] != "strong"]

    md = f"""# 云效项目与微盘文件夹追溯关联

生成时间：{run["generated_at"]}

## 本次范围

- 云效组织：`60069db88deaa14d9e02b875`
- 云效读取方式：`aliyun devops`，profile `ai-nutrition-audit`
- 云效项目数：{run["yunxiao_project_count"]}
- 云效工作项数：{run["yunxiao_workitem_count"]}
- 需求数：{run["requirement_count"]}
- 任务数：{run["task_count"]}
- 微盘索引来源：`work_company_knowledge/indexes/project-index.tsv`
- 关联口径：项目级自动候选，需求/任务继承其所属云效项目的微盘文件夹路径

## 结果概览

- 强候选项目：{match_counter.get("strong", 0)}
- 弱候选待人工确认项目：{match_counter.get("weak_review_needed", 0)}
- 未匹配项目：{match_counter.get("unmatched", 0)}

## 交付文件

- `yunxiao-projects.csv`：云效项目清单。
- `yunxiao-workitems.csv`：云效需求/任务清单，并带上所属项目的微盘候选文件夹。
- `yunxiao-wedrive-project-map.csv`：每个云效项目的最佳微盘文件夹候选。
- `yunxiao-wedrive-match-candidates.csv`：每个云效项目的前 5 个微盘候选，供人工复核。
- `project-workitem-folder-summary.csv`：按云效项目汇总工作项数量、状态和微盘文件夹。
- `raw/yunxiao-projects.json`、`raw/yunxiao-workitems-by-project.json`：CLI 只读回读证据，不含 token。
- `yunxiao-wedrive-traceability.html`：给人审的可视化页面。

## 注意事项

1. `strong` 仍然只是自动强候选，不等于人工确认；正式作为项目资料真源前建议人工过一遍。
2. `weak_review_needed` 和 `unmatched` 不应自动写回云效或微盘。
3. `食安系统`、`进销存系统`、`项目售后问题`、`运维工作` 这类泛项目名容易跨项目误配，本次已降级为弱候选或未匹配。
4. 原始微盘文件仍以企业微信微盘为真源，本目录只保存索引和追溯导航。

## 状态分布 Top 10

"""
    for status, count in status_counter.most_common(10):
        md += f"- {status}: {count}\n"

    md += "\n## 工作项最多的项目 Top 15\n\n"
    for row in summary_rows[:15]:
        md += f"- {row['custom_code']} {row['project_name']}: {row['workitem_total']} 条，微盘匹配 `{row['wedrive_match_status']}` -> {row['wedrive_project_or_topic'] or 'n/a'}\n"

    md += "\n## 待人工确认\n\n"
    for row in weak_or_unmatched[:30]:
        md += f"- {row['custom_code']} {row['project_name']}: {row['match_status']} {row['match_score']} -> {row['wedrive_project_or_topic'] or 'n/a'}\n"

    (OUT / "summary.md").write_text(md, encoding="utf-8")

    status_rows = [{"status": k, "count": v} for k, v in status_counter.most_common(12)]
    top_project_rows = summary_rows[:20]
    strong_rows = [r for r in mapping if r["match_status"] == "strong"][:25]

    css = """
    body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f6f7f9;color:#20242a}
    header{background:#17324d;color:white;padding:28px 36px}
    main{padding:28px 36px;max-width:1440px;margin:auto}
    h1{margin:0;font-size:28px}
    h2{margin:28px 0 12px;font-size:20px}
    .sub{opacity:.84;margin-top:8px}
    .grid{display:grid;grid-template-columns:repeat(4,minmax(160px,1fr));gap:12px}
    .card{background:white;border:1px solid #d9dee5;border-radius:8px;padding:16px}
    .num{font-size:28px;font-weight:700;margin-top:6px}
    table{width:100%;border-collapse:collapse;background:white;border:1px solid #d9dee5;border-radius:8px;overflow:hidden}
    th,td{padding:9px 10px;border-bottom:1px solid #e9edf2;text-align:left;vertical-align:top;font-size:13px}
    th{background:#eef2f6;color:#26313d;font-weight:650}
    tr:last-child td{border-bottom:0}
    .note{background:#fff8e5;border:1px solid #ead49a;border-radius:8px;padding:14px 16px;line-height:1.6}
    .files code{display:block;background:#fff;border:1px solid #d9dee5;border-radius:6px;padding:8px;margin:6px 0}
    @media(max-width:900px){.grid{grid-template-columns:repeat(2,minmax(140px,1fr))}main,header{padding-left:18px;padding-right:18px}}
    """

    html_text = f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>云效项目与微盘文件夹追溯关联</title>
  <style>{css}</style>
</head>
<body>
  <header>
    <h1>云效项目与微盘文件夹追溯关联</h1>
    <div class="sub">生成时间：{html.escape(run["generated_at"])} · 只读来源：aliyun devops + 微盘派生索引</div>
  </header>
  <main>
    <section class="grid">
      <div class="card">云效项目<div class="num">{run["yunxiao_project_count"]}</div></div>
      <div class="card">需求<div class="num">{run["requirement_count"]}</div></div>
      <div class="card">任务<div class="num">{run["task_count"]}</div></div>
      <div class="card">强候选<div class="num">{match_counter.get("strong", 0)}</div></div>
    </section>

    <h2>结论边界</h2>
    <div class="note">
      本页是追溯导航，不是最终人工确认表。强候选可优先使用；弱候选和未匹配项目需要人工复核后再写回云效、微盘或项目规范。
      需求和任务继承所属云效项目的微盘文件夹候选，后续可在具体工作项层面继续补附件、会议纪要、MR 和验收材料。
    </div>

    <h2>状态分布</h2>
    {table(status_rows, [("status", "状态"), ("count", "数量")])}

    <h2>工作项最多的项目</h2>
    {table(top_project_rows, [("custom_code", "编号"), ("project_name", "云效项目"), ("workitem_total", "工作项"), ("requirement_count", "需求"), ("task_count", "任务"), ("wedrive_match_status", "匹配"), ("wedrive_project_or_topic", "微盘项目/主题"), ("wedrive_folder_path", "微盘文件夹")])}

    <h2>强候选项目</h2>
    {table(strong_rows, [("custom_code", "编号"), ("project_name", "云效项目"), ("match_score", "分数"), ("wedrive_project_or_topic", "微盘项目/主题"), ("wedrive_folder_path", "微盘文件夹")])}

    <h2>待人工确认</h2>
    {table(weak_or_unmatched, [("custom_code", "编号"), ("project_name", "云效项目"), ("match_status", "状态"), ("match_score", "分数"), ("wedrive_project_or_topic", "微盘候选"), ("wedrive_folder_path", "候选文件夹")], limit=40)}

    <h2>输出文件</h2>
    <div class="files">
      <code>yunxiao-projects.csv</code>
      <code>yunxiao-workitems.csv</code>
      <code>yunxiao-wedrive-project-map.csv</code>
      <code>yunxiao-wedrive-match-candidates.csv</code>
      <code>project-workitem-folder-summary.csv</code>
      <code>summary.md</code>
    </div>
  </main>
</body>
</html>
"""
    (OUT / "yunxiao-wedrive-traceability.html").write_text(html_text, encoding="utf-8")


if __name__ == "__main__":
    main()
