#!/usr/bin/env python3
"""把 branch/tag read-only diff 轉成真相來源分類草案。

此工具只讀取已脫敏的 ref detail diff snapshot，不呼叫遠端 Git、不 fetch、
不 push、不刪 branch/tag。輸出用途是人工 review 與 AwoooP mirror。
"""

from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any


DEFAULT_STILL_FORBIDDEN = [
    "fetch",
    "push refs",
    "force push",
    "delete refs",
    "create GitHub repo",
    "change repo visibility",
    "switch GitHub primary",
    "disable Gitea",
    "move secret values",
]

DEFAULT_ALLOWED_NOW = [
    "mirror_classification",
    "display_review_lane",
    "request_single_ref_owner_decision",
    "update_read_only_evidence",
]


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def risk_for_repo(gitea_repo: str) -> str:
    if gitea_repo == "wooo/awoooi":
        return "HIGH"
    return "MEDIUM"


def short_sha(value: str) -> str:
    return value[:8] if value else "無"


def classification_item(
    *,
    ref_type: str,
    ref_name: str,
    lane: str,
    risk: str,
    proposed_truth_source: str,
    classification: str,
    reason: str,
    next_review: str,
    gitea_sha: str = "",
    github_sha: str = "",
) -> dict[str, Any]:
    return {
        "ref_type": ref_type,
        "ref_name": ref_name,
        "lane": lane,
        "risk": risk,
        "proposed_truth_source": proposed_truth_source,
        "classification": classification,
        "reason": reason,
        "next_review": next_review,
        "gitea_sha": gitea_sha,
        "github_sha": github_sha,
        "allowed_now": DEFAULT_ALLOWED_NOW,
        "forbidden_actions": DEFAULT_STILL_FORBIDDEN,
    }


def classify_branch_only_gitea(item: dict[str, str], repo_risk: str) -> dict[str, Any]:
    name = item["name"]
    sha = item["sha"]
    if name.startswith("drift/adopt-"):
        return classification_item(
            ref_type="branch",
            ref_name=name,
            lane="archive_or_deprecate_candidate",
            risk="LOW",
            proposed_truth_source="deprecated_candidate",
            classification="manual_review_deprecated_candidate",
            reason="drift/adopt 類分支疑似為漂移承接或暫存分支，先標為可能封存/降級候選，但不得自動刪除。",
            next_review="由 repo owner 確認是否仍有部署、PR、回滾或稽核用途；確認前保留。",
            gitea_sha=sha,
        )
    if name == "dev":
        return classification_item(
            ref_type="branch",
            ref_name=name,
            lane="active_branch_truth_required",
            risk=repo_risk,
            proposed_truth_source="manual_required",
            classification="manual_truth_required",
            reason="`dev` 可能是仍在使用的開發分支，GitHub 缺少此 ref 前不得判定 GitHub ready。",
            next_review="確認 dev 是否仍為有效工作流；若有效，再決定單 branch 同步策略。",
            gitea_sha=sha,
        )
    return classification_item(
        ref_type="branch",
        ref_name=name,
        lane="manual_review",
        risk=repo_risk,
        proposed_truth_source="manual_required",
        classification="manual_review",
        reason="此 Gitea-only branch 不符合已知低風險規則，需要人工判定用途。",
        next_review="確認 ref owner、是否 active、是否需保留到 GitHub 或改列封存。",
        gitea_sha=sha,
    )


def classify_branch_only_github(item: dict[str, str], repo_risk: str) -> dict[str, Any]:
    name = item["name"]
    return classification_item(
        ref_type="branch",
        ref_name=name,
        lane="github_only_manual_review",
        risk=repo_risk,
        proposed_truth_source="manual_required",
        classification="manual_review_github_only",
        reason="GitHub-only branch 可能代表 GitHub 端曾有獨立工作，不能用 Gitea 覆蓋或刪除。",
        next_review="確認該 branch 是否為有效 refactor/feature 線，並判定是否回補到 Gitea 或保留 GitHub-only。",
        github_sha=item["sha"],
    )


def classify_tag_only_gitea(item: dict[str, str], repo_risk: str) -> dict[str, Any]:
    return classification_item(
        ref_type="tag",
        ref_name=item["name"],
        lane="release_tag_missing_on_github",
        risk=repo_risk,
        proposed_truth_source="manual_required",
        classification="manual_review_release_tag",
        reason="Gitea-only release tag 可能是正式版本證據；GitHub primary 前需確認是否補 tag。",
        next_review="確認 tag 對應 release / artifact / deploy marker，再以單 repo approval 決定是否同步。",
        gitea_sha=item["sha"],
    )


def classify_tag_only_github(item: dict[str, str], repo_risk: str) -> dict[str, Any]:
    name = item["name"]
    if name.startswith("uat-"):
        lane = "github_only_uat_tag"
        reason = "GitHub-only UAT tag 可能是舊驗收或臨時發布標記，不得自動刪除或搬回 Gitea。"
        next_review = "確認 UAT tag 是否仍需保留為稽核 evidence；若已過期，再列入封存決策。"
    else:
        lane = "github_only_manual_review"
        reason = "GitHub-only tag 可能代表 GitHub 端 release evidence，需要人工判定。"
        next_review = "確認 tag owner、release 用途與是否需要與 Gitea 對齊。"
    return classification_item(
        ref_type="tag",
        ref_name=name,
        lane=lane,
        risk=repo_risk,
        proposed_truth_source="manual_required",
        classification="manual_review_github_only",
        reason=reason,
        next_review=next_review,
        github_sha=item["sha"],
    )


def classify_main_mismatch(item: dict[str, str], repo_risk: str) -> dict[str, Any]:
    return classification_item(
        ref_type="branch",
        ref_name=item["name"],
        lane="main_truth_required",
        risk=repo_risk,
        proposed_truth_source="manual_required",
        classification="manual_truth_required",
        reason="兩端 main SHA 不一致，這是 GitHub primary / deploy control plane 的硬阻塞。",
        next_review="先確認目前 production deploy 真相來源、deploy marker、rollback 點，再決定單 repo reconcile。",
        gitea_sha=item["gitea_sha"],
        github_sha=item["github_sha"],
    )


def classify_repo(repo: dict[str, Any]) -> dict[str, Any]:
    repo_risk = risk_for_repo(str(repo["gitea_repo"]))
    items: list[dict[str, Any]] = []
    branch = repo["branch_diff"]
    tag = repo["tag_diff"]

    for mismatch in branch["sha_mismatch"]:
        if mismatch["name"] == "main":
            items.append(classify_main_mismatch(mismatch, repo_risk))
        else:
            items.append(
                classification_item(
                    ref_type="branch",
                    ref_name=mismatch["name"],
                    lane="manual_review",
                    risk=repo_risk,
                    proposed_truth_source="manual_required",
                    classification="manual_truth_required",
                    reason="Branch 兩端皆存在但 SHA 不一致，需要人工判定哪一端為真相來源。",
                    next_review="確認是否 active、是否有 PR / deploy / rollback 依賴，再進單 branch reconcile。",
                    gitea_sha=mismatch["gitea_sha"],
                    github_sha=mismatch["github_sha"],
                )
            )

    for item in branch["only_gitea"]:
        items.append(classify_branch_only_gitea(item, repo_risk))
    for item in branch["only_github"]:
        items.append(classify_branch_only_github(item, repo_risk))
    for item in tag["only_gitea"]:
        items.append(classify_tag_only_gitea(item, repo_risk))
    for item in tag["only_github"]:
        items.append(classify_tag_only_github(item, repo_risk))

    return {
        "gitea_repo": repo["gitea_repo"],
        "github_repo": repo["github_repo"],
        "risk": repo_risk,
        "awooop_consumption": "approval_candidate",
        "item_count": len(items),
        "items": items,
    }


def build_snapshot(args: argparse.Namespace) -> dict[str, Any]:
    source_path = Path(args.source_snapshot)
    source = load_json(source_path)
    repos = [classify_repo(repo) for repo in source["repos"]]
    all_items = [item for repo in repos for item in repo["items"]]
    return {
        "schema_version": "source_control_ref_truth_classification_v1",
        "status": "draft_blocked",
        "date": args.date,
        "default_mode": "classification_only",
        "source_snapshot": str(source_path),
        "summary": {
            "repo_count": len(repos),
            "total_items": len(all_items),
            "manual_truth_required_count": sum(
                1 for item in all_items if item["classification"] == "manual_truth_required"
            ),
            "deprecated_candidate_count": sum(
                1 for item in all_items if item["classification"] == "manual_review_deprecated_candidate"
            ),
            "release_tag_review_count": sum(
                1 for item in all_items if item["classification"] == "manual_review_release_tag"
            ),
            "github_only_review_count": sum(
                1 for item in all_items if item["classification"] == "manual_review_github_only"
            ),
        },
        "still_forbidden": DEFAULT_STILL_FORBIDDEN,
        "repos": repos,
    }


def write_markdown(snapshot: dict[str, Any], path: Path, *, list_limit: int) -> None:
    summary = snapshot["summary"]
    lines = [
        "# Source Control Ref Truth Classification",
        "",
        "| 項目 | 內容 |",
        "|------|------|",
        f"| 日期 | {snapshot['date']} |",
        f"| 狀態 | `{snapshot['status']}` |",
        f"| 預設模式 | `{snapshot['default_mode']}` |",
        f"| 來源 snapshot | `{snapshot['source_snapshot']}` |",
        f"| repo count | `{summary['repo_count']}` |",
        f"| total items | `{summary['total_items']}` |",
        "",
        "## 0. 核心結論",
        "",
        "本檔把 branch/tag diff 轉成「人工審核分類」：哪些 ref 需要真相來源判定、哪些可能是 deprecated 候選、哪些 release / UAT tags 需要保留判定。它不是同步計畫，也不授權 fetch、push、delete refs 或 GitHub primary 切換。",
        "",
        "## 1. 摘要",
        "",
        "| 指標 | 數量 |",
        "|------|------|",
        f"| 需要人工指定真相來源 | `{summary['manual_truth_required_count']}` |",
        f"| 可能 deprecated / archive 候選 | `{summary['deprecated_candidate_count']}` |",
        f"| release tag 待審核 | `{summary['release_tag_review_count']}` |",
        f"| GitHub-only ref 待審核 | `{summary['github_only_review_count']}` |",
        "",
        "## 2. Repo 分類",
        "",
    ]

    for repo in snapshot["repos"]:
        lines.extend(
            [
                f"### {repo['gitea_repo']} -> {repo['github_repo']}",
                "",
                f"- Risk：`{repo['risk']}`",
                f"- AwoooP consumption：`{repo['awooop_consumption']}`",
                f"- Item count：`{repo['item_count']}`",
                "",
                "| Ref | Type | Lane | Classification | Gitea | GitHub | 下一步 |",
                "|-----|------|------|----------------|-------|--------|--------|",
            ]
        )
        visible_items = repo["items"] if list_limit <= 0 else repo["items"][:list_limit]
        for item in visible_items:
            lines.append(
                "| "
                + " | ".join(
                    [
                        f"`{item['ref_name']}`",
                        f"`{item['ref_type']}`",
                        f"`{item['lane']}`",
                        f"`{item['classification']}`",
                        f"`{short_sha(item['gitea_sha'])}`",
                        f"`{short_sha(item['github_sha'])}`",
                        item["next_review"],
                    ]
                )
                + " |"
            )
        if list_limit > 0 and repo["item_count"] > list_limit:
            lines.append(
                f"| 另有 `{repo['item_count'] - list_limit}` 筆 |  |  | 完整清單見 JSON snapshot |  |  |  |"
            )
        lines.append("")

    lines.extend(
        [
            "## 3. AwoooP 消費方式",
            "",
            "1. 只 mirror `source_control_ref_truth_classification_v1`。",
            "2. 可顯示 review lane 與 owner decision queue。",
            "3. 可產生單 repo / 單 ref approval candidate，但不得自動批准。",
            "4. 不得新增 refs sync、delete、force-push、primary switch action。",
            "",
            "## 4. 仍然禁止",
            "",
        ]
    )
    for value in snapshot["still_forbidden"]:
        lines.append(f"- {value}")
    lines.append("")
    path.write_text("\n".join(lines), encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--date", required=True)
    parser.add_argument("--source-snapshot", required=True)
    parser.add_argument("--output-json", required=True)
    parser.add_argument("--output-md", required=True)
    parser.add_argument("--md-list-limit", type=int, default=40)
    args = parser.parse_args()

    snapshot = build_snapshot(args)
    Path(args.output_json).write_text(
        json.dumps(snapshot, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    write_markdown(snapshot, Path(args.output_md), list_limit=args.md_list_limit)
    print(
        "OK source-control ref truth classification "
        f"repos={snapshot['summary']['repo_count']} items={snapshot['summary']['total_items']}"
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
