#!/usr/bin/env python3
"""无线微信安全能力矩阵验收。

默认只执行低风险读类与 filehelper 白名单消息；高风险、资金、账号破坏类动作只登记 gated。
"""

from __future__ import annotations

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

import requests


ROOT = Path(__file__).resolve().parents[2]
EVID_ROOT = ROOT / "开发文档" / "8、部署" / "05-测试验收"

READ_ACTIONS: dict[str, dict[str, Any]] = {
    "ping": {},
    "get_hook_status": {},
    "get_wechat_version": {},
    "get_profile": {},
    "get_device_info": {},
    "get_storage_info": {},
    "get_network_info": {},
    "get_process_info": {},
    "check_login_state": {},
    "get_contacts": {"limit": 10},
    "search_contacts": {"keyword": "文件", "limit": 5},
    "get_messages": {"conversation_id": "filehelper", "limit": 5},
    "get_recent_messages": {"limit": 5},
    "search_messages": {"keyword": "验收", "limit": 5},
    "get_groups": {"limit": 5},
    "get_labels": {},
    "get_moments": {"limit": 5},
    "get_friend_requests": {},
    "get_favorites": {"limit": 5},
    "get_official_accounts": {"limit": 5},
}

SAFE_WRITE_ACTIONS: dict[str, dict[str, Any]] = {
    "send_message": {
        "to_id": "filehelper",
        "content": f"[工作手机安全矩阵] {datetime.now().strftime('%Y%m%d-%H%M%S')}",
        "msg_type": "text",
    },
}

GATED_ACTIONS: dict[str, str] = {
    "batch_send": "批量触达：需要 WORKPHONE_TEST_TO_IDS 白名单，默认 dry-run，真实发送按 65s+。",
    "mass_send": "群发：需要白名单测试联系人、内容去重、限流与停止条件。",
    "add_friend": "主动加好友：需要测试 wxid/手机号白名单，默认 180s+，新号限制更严。",
    "batch_add_friend": "批量加好友：需要白名单与用户确认，不自动对真实客户执行。",
    "accept_friend": "通过好友：需要真实 pending 请求样本。",
    "create_group": "建群：需要白名单成员，避免真实客户被拉群。",
    "invite_to_group": "邀请入群：需要测试群与测试成员。",
    "send_group_message": "群消息：需要测试群 group_id。",
    "post_moments": "发朋友圈：受 2h+ 限流，本轮已有 P0 验收，不重复发布。",
    "like_moments": "朋友圈点赞：需要真实 sns_id，按 15-45s 节律。",
    "comment_moments": "朋友圈评论：需要真实 sns_id，按 60-180s 节律。",
    "send_red_packet": "资金类：测试号、小额、二次确认、审计；默认禁自动跑。",
    "send_transfer": "资金类：测试号、小额、二次确认、审计；默认禁自动跑。",
    "receive_red_packet": "资金类：需要真实红包消息样本与人工确认。",
    "receive_transfer": "资金类：需要真实转账消息样本与人工确认。",
    "receive_payment": "资金类：需要测试场景与人工确认。",
    "show_payment_code": "付款码：敏感 UI，默认不自动展示。",
    "logout": "破坏性账号动作：默认不执行。",
    "switch_account": "账号切换：默认不执行。",
    "login_by_password": "登录动作：需要测试账号凭证与人工确认。",
}


def hook_execute(base: str, device: str, action: str, params: dict[str, Any]) -> dict[str, Any]:
    started = time.time()
    try:
        resp = requests.post(
            f"{base}/api/v3/hook/execute",
            json={
                "device_id": device,
                "platform": "wechat",
                "action": action,
                "params": params,
                "hook_only": True,
            },
            timeout=90,
        )
        body = resp.json()
    except Exception as exc:
        return {
            "action": action,
            "status": "failed",
            "elapsed_ms": int((time.time() - started) * 1000),
            "error": str(exc),
        }

    data = body.get("data") if isinstance(body.get("data"), dict) else body
    success = bool(data.get("success")) if isinstance(data, dict) and "success" in data else body.get("code") == 200
    return {
        "action": action,
        "status": "passed" if success else "failed",
        "elapsed_ms": int((time.time() - started) * 1000),
        "channel": body.get("channel_used") or data.get("channel") if isinstance(data, dict) else body.get("channel_used"),
        "code": body.get("code"),
        "error": "" if success else str(data.get("error") or data.get("message") or body)[:300],
        "summary": summarize_data(data),
    }


def summarize_data(data: Any) -> dict[str, Any]:
    if not isinstance(data, dict):
        return {"type": type(data).__name__}
    summary: dict[str, Any] = {}
    for key in ("success", "wechat_version", "version", "channel", "action_resolved"):
        if key in data:
            summary[key] = data[key]
    for key in ("contacts", "messages", "groups", "labels", "moments", "requests", "favorites", "accounts"):
        val = data.get(key)
        if isinstance(val, list):
            summary[f"{key}_count"] = len(val)
    if isinstance(data.get("profile"), dict):
        summary["profile_present"] = True
    if data.get("message_id"):
        summary["message_id_present"] = True
    return summary


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--device-id", default="xgfe65eimrrofyws")
    parser.add_argument("--base", default="http://127.0.0.1:8899")
    parser.add_argument("--include-safe-write", action="store_true")
    args = parser.parse_args()

    stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    out_dir = EVID_ROOT / f"{datetime.now().strftime('%Y%m%d')}_微信安全矩阵验收"
    out_dir.mkdir(parents=True, exist_ok=True)

    probe = requests.get(f"{args.base}/api/v3/hook/probe/{args.device_id}", timeout=60).json()
    rows: list[dict[str, Any]] = []
    if not probe.get("supports_hook"):
        report = {"timestamp": stamp, "device_id": args.device_id, "probe": probe, "results": []}
        path = out_dir / f"safe_matrix_{stamp}.json"
        path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"Hook 未就绪，报告 {path}")
        return 2

    for action, params in READ_ACTIONS.items():
        row = hook_execute(args.base, args.device_id, action, params)
        rows.append(row)
        print(f"{'✓' if row['status'] == 'passed' else '✗'} {action:<24} {row['status']} {row['elapsed_ms']}ms")
        time.sleep(0.8)

    if args.include_safe_write:
        for action, params in SAFE_WRITE_ACTIONS.items():
            row = hook_execute(args.base, args.device_id, action, params)
            rows.append(row)
            print(f"{'✓' if row['status'] == 'passed' else '✗'} {action:<24} {row['status']} {row['elapsed_ms']}ms")
            time.sleep(1.5)

    for action, reason in GATED_ACTIONS.items():
        rows.append({"action": action, "status": "gated", "reason": reason})

    passed = sum(1 for row in rows if row["status"] == "passed")
    failed = sum(1 for row in rows if row["status"] == "failed")
    gated = sum(1 for row in rows if row["status"] == "gated")
    report = {
        "timestamp": datetime.now().isoformat(),
        "device_id": args.device_id,
        "base": args.base,
        "probe": {
            "supports_hook": probe.get("supports_hook"),
            "wechat_version": probe.get("wechat_version"),
            "transport": probe.get("transport"),
        },
        "summary": {"passed": passed, "failed": failed, "gated": gated, "total": len(rows)},
        "results": rows,
    }
    path = out_dir / f"safe_matrix_{stamp}.json"
    path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"\n安全矩阵: passed={passed} failed={failed} gated={gated} → {path}")
    return 0 if failed == 0 else 1


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