#!/usr/bin/env python3
"""
IwoooS 高價值配置 owner response packet 產生器。

本工具讀取 high-value-config-change-gate 的 JSON 報告，為 impacted
categories 產出 owner response packet 草案。它只產生欄位、補件規則與
禁止事項，不送件、不收回覆、不建立 action button、不執行 runtime。
"""

from __future__ import annotations

import argparse
import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any


TAIPEI = timezone(timedelta(hours=8))

CANONICAL_OWNER_FIELDS = [
    "owner_role_or_team",
    "decision",
    "decision_reason",
    "affected_scope",
    "redacted_evidence_refs",
    "followup_owner",
    "rollback_owner",
    "maintenance_window",
    "validation_plan",
]

ALLOWED_DECISIONS = ["confirm", "defer", "reject", "request_more_evidence"]

FIELD_INSTRUCTIONS = {
    "owner_role_or_team": "填角色或團隊，不填私人帳號、密碼、token 或私人聯絡資訊。",
    "decision": "只能填 confirm、defer、reject、request_more_evidence；不得附帶 runtime 執行批准。",
    "decision_reason": "填脫敏短理由；不可貼 raw log、raw API body、未脫敏截圖或內部對話。",
    "affected_scope": "填受影響 repo、host、domain、namespace、route、service、secret name 或產品邊界。",
    "redacted_evidence_refs": "只填文件路徑、snapshot id、ticket id、commit、hash 或脫敏 metadata pointer。",
    "followup_owner": "填後續補證、審查或決策負責角色 / 團隊。",
    "rollback_owner": "填回滾負責角色 / 團隊；不是直接執行授權。",
    "maintenance_window": "填維護窗口或明確寫 deferred / not scheduled；不得用口頭同意代替。",
    "validation_plan": "填 preflight、post-check、rollback check；若只讀文件變更，寫 guard / json / doc secret sanity。",
}

FALSE_FLAGS = [
    "request_sent",
    "response_received",
    "response_accepted",
    "runtime_execution_authorized",
    "host_write_authorized",
    "nginx_reload_authorized",
    "dns_tls_change_authorized",
    "workflow_modification_authorized",
    "runner_change_authorized",
    "refs_sync_authorized",
    "force_push_authorized",
    "secret_value_collection_allowed",
    "active_scan_authorized",
    "action_buttons_allowed",
]

BLOCKED_REQUESTS = [
    "repo_create",
    "visibility_change",
    "refs_sync",
    "refs_delete",
    "force_push",
    "workflow_modify",
    "runner_enable",
    "secret_value_submit",
    "ssh_host_modify",
    "nginx_reload",
    "dns_tls_modify",
    "argocd_sync",
    "kubectl_apply",
    "active_scan",
    "agent_bounty_runtime_execute",
    "payout_or_withdrawal",
]

OUTCOME_LANES = [
    "keep_waiting_owner_response",
    "request_more_evidence",
    "quarantine_sensitive_payload",
    "reject_execution_request",
    "ready_for_reviewer_validation",
]


def git_short_sha(root: Path) -> str:
    try:
        result = subprocess.run(
            ["git", "rev-parse", "--short", "HEAD"],
            cwd=root,
            check=True,
            capture_output=True,
            text=True,
        )
        return result.stdout.strip()
    except Exception:
        return "unknown"


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


def field_templates() -> list[dict[str, Any]]:
    return [
        {
            "field": field,
            "required": True,
            "instruction": FIELD_INSTRUCTIONS[field],
        }
        for field in CANONICAL_OWNER_FIELDS
    ]


def false_flag_map() -> dict[str, bool]:
    return {flag: False for flag in FALSE_FLAGS}


def files_for_category(gate_report: dict[str, Any], category_id: str) -> list[str]:
    paths: list[str] = []
    for item in gate_report.get("changed_files", []):
        if any(category.get("category_id") == category_id for category in item.get("categories", [])):
            paths.append(item["path"])
    return sorted(paths)


def category_inventory_by_id(gate_report: dict[str, Any]) -> dict[str, dict[str, Any]]:
    return {
        item["category_id"]: item
        for item in gate_report.get("control_category_inventory", [])
    }


def packet_for_category(gate_report: dict[str, Any], category: dict[str, Any]) -> dict[str, Any]:
    inventory = category_inventory_by_id(gate_report).get(category["category_id"], {})
    affected_files = files_for_category(gate_report, category["category_id"])
    return {
        "packet_id": f"high_value_config_owner_packet:{category['category_id']}",
        "status": "draft_waiting_owner_response",
        "category_id": category["category_id"],
        "label": category["label"],
        "priority": category["priority"],
        "control_tier": category["control_tier"],
        "required_gate": category["required_gate"],
        "affected_files": affected_files,
        "allowed_decisions": ALLOWED_DECISIONS,
        "field_templates": field_templates(),
        "required_validation": category.get("required_validation") or inventory.get("required_validation", []),
        "redaction_rules": [
            "只收 redacted evidence refs，不收 secret value。",
            "疑似 token、cookie、authorization header、private key、runner token 或 webhook secret 一律 quarantine。",
            "內部工作視窗對話、抱怨、口頭同意不得進產品文案或 LOGBOOK raw text。",
        ],
        "blocked_requests": BLOCKED_REQUESTS,
        "outcome_lanes": OUTCOME_LANES,
        "false_flags": false_flag_map(),
        "reviewer_checklist": [
            "canonical owner fields 全部存在。",
            "decision 只使用允許值。",
            "affected scope 可映射到 repo / host / domain / route / service / secret name。",
            "redacted evidence refs 不含 raw payload。",
            "沒有夾帶執行要求。",
            "C0 / C1 若要進 runtime，需獨立人工批准與維護窗口。",
        ],
    }


def build_report(root: Path, gate_report: dict[str, Any], generated_at: str | None) -> dict[str, Any]:
    report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
    impacted_categories = gate_report.get("impacted_categories", [])
    packets = [packet_for_category(gate_report, category) for category in impacted_categories]
    c0_packets = [packet for packet in packets if packet["control_tier"] == "C0"]
    c1_packets = [packet for packet in packets if packet["control_tier"] == "C1"]

    return {
        "schema_version": "high_value_config_owner_packet_v1",
        "generated_at": report_time,
        "git_commit": git_short_sha(root),
        "source_gate_schema_version": gate_report.get("schema_version"),
        "source_gate_summary": gate_report.get("summary", {}),
        "status": "draft_waiting_owner_response",
        "canonical_owner_fields": CANONICAL_OWNER_FIELDS,
        "allowed_decisions": ALLOWED_DECISIONS,
        "execution_boundaries": {
            "request_sent": False,
            "response_received": False,
            "response_accepted": False,
            "runtime_execution_authorized": False,
            "host_write_authorized": False,
            "secret_value_collected": False,
            "action_buttons_allowed": False,
        },
        "summary": {
            "packet_count": len(packets),
            "c0_packet_count": len(c0_packets),
            "c1_packet_count": len(c1_packets),
            "request_sent_count": 0,
            "received_response_count": 0,
            "accepted_response_count": 0,
            "runtime_gate_count": 0,
        },
        "universal_owner_response_template": {
            "allowed_decisions": ALLOWED_DECISIONS,
            "field_templates": field_templates(),
            "false_flags": false_flag_map(),
        },
        "packets": packets,
        "control_category_inventory": gate_report.get("control_category_inventory", []),
        "next_steps": [
            "若 packet_count > 0，將 packet 交給 owner 補 canonical 欄位；不得把草案視為已送件。",
            "若 owner 回覆含 secret 或執行要求，先 quarantine 或 reject_execution_request。",
            "只有 reviewer checklist 完成後才可進 accepted；accepted 仍不開 runtime gate。",
        ],
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="IwoooS 高價值配置 owner response packet 產生器")
    parser.add_argument("--root", default=".", help="repo root")
    parser.add_argument(
        "--gate-report",
        default="docs/security/high-value-config-change-gate.snapshot.json",
        help="high-value-config-change-gate.py 輸出的 JSON",
    )
    parser.add_argument("--output", help="寫出 JSON 報告")
    parser.add_argument("--generated-at", help="固定報告時間，供 committed snapshot 使用")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    gate_report = load_json(root / args.gate_report)
    report = build_report(root, gate_report, args.generated_at)
    payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)

    if args.output:
        output = Path(args.output)
        output.parent.mkdir(parents=True, exist_ok=True)
        output.write_text(payload + "\n", encoding="utf-8")
    else:
        print(payload)

    summary = report["summary"]
    print(
        "HIGH_VALUE_CONFIG_OWNER_PACKET_OK "
        f"packets={summary['packet_count']} "
        f"c0={summary['c0_packet_count']} "
        f"c1={summary['c1_packet_count']} "
        f"runtime_gate={summary['runtime_gate_count']}",
        file=sys.stderr,
    )
    return 0


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