#!/usr/bin/env python3
"""產生 Gitea -> GitHub 逐 repo approval board。

此工具只讀取既有 redacted snapshot，不呼叫 Gitea/GitHub API，不需要 token。
用途是讓 AwoooP / PR reviewer 可以看見每個 repo 的下一個低摩擦決策點。
"""

from __future__ import annotations

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


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


def infer_lane(recommended_action: str) -> str:
    if recommended_action == "hold_refs_reconcile":
        return "refs_reconcile"
    if recommended_action == "confirm_internal_remote_purpose":
        return "internal_remote_purpose"
    if recommended_action == "scope_review_only":
        return "scope_review"
    return "target_creation_or_access"


def required_decision(lane: str) -> str:
    mapping = {
        "refs_reconcile": "決定 Gitea / GitHub refs 真相來源，並批准只產生 reconcile plan。",
        "target_creation_or_access": "決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。",
        "internal_remote_purpose": "決定 110 internal remote 是 active source、legacy mirror 或應降級。",
        "scope_review": "決定此 repo 是否屬於 AWOOOI 資安供應鏈範圍。",
    }
    return mapping[lane]


def low_friction_next_step(lane: str) -> str:
    mapping = {
        "refs_reconcile": "先產生 draft reconcile plan，不 push refs、不切 primary。",
        "target_creation_or_access": "先取得 owner / visibility 決策，不自動建立 repo。",
        "internal_remote_purpose": "先文件化用途與風險，不刪除 remote、不同步 refs。",
        "scope_review": "只標記 scope review，不納入主控切換。",
    }
    return mapping[lane]


def awooop_consumption(lane: str, approval_required: bool) -> str:
    if lane == "scope_review":
        return "scope_review_only"
    if approval_required:
        return "approval_candidate"
    return "mirror_only"


def build_board(args: argparse.Namespace) -> dict[str, Any]:
    decisions = load_json(Path(args.github_target_decision))
    packages = load_json(Path(args.repo_approval_package))
    gitea_inventory = load_json(Path(args.gitea_inventory))

    package_by_repo = {
        str(item.get("github_repo", "")): item
        for item in packages.get("approval_items", [])
        if isinstance(item, dict)
    }

    board_items: list[dict[str, Any]] = []
    pending_count = 0
    for decision in decisions.get("decisions", []):
        if not isinstance(decision, dict):
            continue
        github_repo = str(decision.get("github_repo", ""))
        package = package_by_repo.get(github_repo, {})
        approval_required = bool(decision.get("approval_required", False))
        if approval_required:
            pending_count += 1

        lane = infer_lane(str(decision.get("recommended_action", "")))
        approval_status = str(package.get("approval_status") or ("pending" if approval_required else "not_required"))
        blocked_until = package.get("blocked_until") or decision.get("blocked_until") or []
        evidence_refs = sorted(
            {
                *[str(value) for value in decision.get("evidence_refs", [])],
                *[str(value) for value in package.get("evidence_refs", [])],
            }
        )

        board_items.append(
            {
                "github_repo": github_repo,
                "source_key": str(decision.get("source_key", "")),
                "lane": lane,
                "risk": str(decision.get("risk", "LOW")),
                "probe_status": str(decision.get("probe_status", "")),
                "target_state": str(decision.get("target_state", "")),
                "approval_status": approval_status,
                "required_decision": required_decision(lane),
                "low_friction_next_step": low_friction_next_step(lane),
                "blocked_until": [str(value) for value in blocked_until],
                "allowed_after_approval": [
                    str(value)
                    for value in (package.get("allowed_after_approval") or ["mirror_decision_only"])
                ],
                "still_forbidden": [
                    str(value)
                    for value in (
                        package.get("still_forbidden")
                        or ["auto_execute", "sync_refs", "switch_primary"]
                    )
                ],
                "evidence_refs": evidence_refs,
                "awooop_consumption": awooop_consumption(lane, approval_required),
            }
        )

    inventory_status = str(gitea_inventory.get("status", "blocked"))
    gate_status = "ready" if inventory_status == "ok" else "blocked"
    gate_reason = (
        "Gitea authenticated 或 admin_export inventory 已完成。"
        if gate_status == "ready"
        else "GITEA_READONLY_TOKEN 未提供，且不使用可 push 的既有 remote credential 當 read-only token；server-side private/internal repo list 仍未完成。"
    )

    return {
        "schema_version": "source_control_approval_board_v1",
        "status": "draft",
        "date": args.date,
        "default_mode": "mirror_only",
        "authenticated_inventory_gate": {
            "status": gate_status,
            "reason": gate_reason,
            "allowed_next_step": [
                "提供 read-only token 後重跑 gitea-repo-inventory",
                "或提供 redacted admin export JSON",
                "在 gate 前仍可維護 approval board 與 decision table",
            ],
            "still_forbidden": [
                "使用 write-capable credential 當作 read-only token",
                "建立 GitHub repo",
                "修改 repo visibility",
                "sync refs",
                "switch GitHub primary",
            ],
        },
        "item_count": len(board_items),
        "pending_approval_count": pending_count,
        "board_items": board_items,
    }


def write_markdown(board: dict[str, Any], path: Path) -> None:
    gate = board["authenticated_inventory_gate"]
    lines = [
        "# Source Control Approval Board",
        "",
        "| 項目 | 內容 |",
        "|------|------|",
        f"| 日期 | {board['date']} |",
        f"| 狀態 | `{board['status']}` |",
        f"| 預設模式 | `{board['default_mode']}` |",
        f"| authenticated inventory gate | `{gate['status']}` |",
        f"| gate 原因 | {gate['reason']} |",
        f"| repo items | {board['item_count']} |",
        f"| pending approval | {board['pending_approval_count']} |",
        "",
        "## 0. 核心原則",
        "",
        "本 board 只整理決策，不授權執行。AwoooP 可以 mirror 成 approval candidate，但不得建立 repo、修改 visibility、同步 refs、切 GitHub primary 或保存 credential value。",
        "",
        "## 1. 逐 repo 決策隊列",
        "",
        "| GitHub repo | Lane | Risk | Probe | Approval | 下一步 |",
        "|-------------|------|------|-------|----------|--------|",
    ]
    for item in board["board_items"]:
        lines.append(
            "| "
            + " | ".join(
                [
                    f"`{item['github_repo']}`",
                    f"`{item['lane']}`",
                    f"`{item['risk']}`",
                    f"`{item['probe_status']}`",
                    f"`{item['approval_status']}`",
                    item["low_friction_next_step"],
                ]
            )
            + " |"
        )

    lines.extend(["", "## 2. 詳細阻塞點", ""])
    for item in board["board_items"]:
        lines.extend(
            [
                f"### {item['github_repo']}",
                "",
                f"- Source key：`{item['source_key']}`",
                f"- Required decision：{item['required_decision']}",
                f"- AwoooP consumption：`{item['awooop_consumption']}`",
                "- Blocked until：",
            ]
        )
        for value in item["blocked_until"]:
            lines.append(f"  - {value}")
        lines.append("- Still forbidden：")
        for value in item["still_forbidden"]:
            lines.append(f"  - {value}")
        lines.append("- Evidence refs：")
        for value in item["evidence_refs"]:
            lines.append(f"  - `{value}`")
        lines.append("")

    lines.extend(
        [
            "## 3. Gate 前允許做的事",
            "",
            "1. 更新 read-only evidence。",
            "2. 更新 approval board / decision table。",
            "3. 寫 draft reconcile plan。",
            "4. 把 pending approval mirror 到 AwoooP。",
            "",
            "## 4. Gate 前仍禁止",
            "",
        ]
    )
    for value in gate["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("--github-target-decision", default="docs/security/github-target-decision.snapshot.json")
    parser.add_argument(
        "--repo-approval-package",
        default="docs/security/github-target-repo-approval-package.snapshot.json",
    )
    parser.add_argument("--gitea-inventory", default="docs/security/gitea-repo-inventory.snapshot.json")
    parser.add_argument("--output-json", required=True)
    parser.add_argument("--output-md", required=True)
    args = parser.parse_args()

    board = build_board(args)
    Path(args.output_json).write_text(
        json.dumps(board, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    write_markdown(board, Path(args.output_md))
    print(f"OK source-control approval board items={board['item_count']}")
    return 0


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