#!/usr/bin/env python3
"""
工作手机SDK - Frida无线真机验证 V2
修正：Frida Python会将camelCase exports转为全小写
策略：直接用全小写调用 rpc.exports_sync 的方法
"""
import frida
import json
import time
import os
import sys
import subprocess
from datetime import datetime

DEVICE_IP = "192.168.0.12"
FRIDA_PORT = 27042
WECHAT_PID = 7239
DEVICE_SERIAL = "xgfe65eimrrofyws"

# Hook脚本路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SCRIPT_PATH = os.path.join(SCRIPT_DIR, '..', 'agent', 'hook', 'wechat_hook_v2.js')

# 输出目录
OUTPUT_DIR = os.path.join(SCRIPT_DIR, '..', '..', 'verification_output')
SCREENSHOT_DIR = os.path.join(OUTPUT_DIR, 'screenshots')
os.makedirs(SCREENSHOT_DIR, exist_ok=True)

RESULT_FILE = os.path.join(OUTPUT_DIR, 'live_verification_results.json')
REPORT_FILE = os.path.join(OUTPUT_DIR, 'live_verification_report.md')


def screenshot(name):
    """截取手机屏幕"""
    fname = f"{name}.png"
    fpath = os.path.join(SCREENSHOT_DIR, fname)
    try:
        subprocess.run(["adb", "-s", DEVICE_SERIAL, "shell", "screencap", "-p", "/sdcard/sc_tmp.png"],
                       capture_output=True, timeout=5)
        subprocess.run(["adb", "-s", DEVICE_SERIAL, "pull", "/sdcard/sc_tmp.png", fpath],
                       capture_output=True, timeout=5)
        if os.path.exists(fpath) and os.path.getsize(fpath) > 0:
            return fpath
    except Exception as e:
        pass
    return ""


def call_rpc(exports, method_name, params=None):
    """安全调用RPC方法，Frida Python会把camelCase转全小写"""
    # Frida Python的exports_sync会把所有方法名转为小写
    py_name = method_name.lower()
    fn = getattr(exports, py_name, None)
    if fn is None:
        return {"success": False, "error": f"方法不存在: {py_name} (原始: {method_name})"}
    try:
        if params:
            return fn(params)
        else:
            return fn()
    except Exception as e:
        return {"success": False, "error": str(e)}


# 所有112个RPC方法的验证计划
# 格式: (模块ID, 模块名, 方法名, 参数, 是否安全执行)
ALL_METHODS = [
    # 系统方法
    ("SYS", "系统", "ping", None, True),
    ("SYS", "系统", "getProcessInfo", None, True),
    ("SYS", "系统", "getHookStatus", None, True),
    ("SYS", "系统", "getWechatVersion", None, True),
    ("SYS", "系统", "getVersionCompat", None, True),
    # H15 消息接收
    ("H15", "消息接收", "getMessages", {"conversation_id": "", "limit": 3}, True),
    ("H15", "消息接收", "getRecentMessages", {"limit": 5}, True),
    ("H15", "消息接收", "searchMessages", {"keyword": "你好", "limit": 3}, True),
    # H16 联系人
    ("H16", "联系人", "getContacts", {"limit": 10}, True),
    ("H16", "联系人", "getContactInfo", {"wxid": "filehelper"}, True),
    ("H16", "联系人", "searchContacts", {"keyword": "文件", "limit": 5}, True),
    # H17 消息发送
    ("H17", "消息发送", "sendMessage", {"to_id": "filehelper", "content": "[SDK验证]文本消息 " + datetime.now().strftime("%H:%M:%S"), "msg_type": "text"}, True),
    ("H17", "消息发送", "sendGroupMessage", {"group_id": "", "content": "test", "msg_type": "text"}, False),
    # H18 好友请求
    ("H18", "好友请求", "getFriendRequests", {"limit": 5}, True),
    # H19 好友管理
    ("H19", "好友管理", "addFriend", {"wxid": "", "message": "test"}, False),
    ("H19", "好友管理", "acceptFriend", {"encrypt_username": "", "ticket": ""}, False),
    ("H19", "好友管理", "deleteFriend", {"wxid": ""}, False),
    ("H19", "好友管理", "setFriendRemark", {"wxid": "filehelper", "remark": "文件助手"}, True),
    ("H19", "好友管理", "addFriendByQr", {"qr_content": ""}, False),
    # H20 朋友圈发布
    ("H20", "朋友圈发布", "postMoments", {"content": "", "images": []}, False),
    ("H20", "朋友圈发布", "deleteMoments", {"moment_id": ""}, False),
    # H21 朋友圈浏览
    ("H21", "朋友圈浏览", "getMoments", {"wxid": "", "limit": 3}, True),
    ("H21", "朋友圈浏览", "likeMoments", {"moment_id": ""}, False),
    ("H21", "朋友圈浏览", "commentMoments", {"moment_id": "", "content": ""}, False),
    # H22 群管理
    ("H22", "群管理", "getGroups", {"limit": 10}, True),
    ("H22", "群管理", "getGroupInfo", {"group_id": ""}, False),
    ("H22", "群管理", "getGroupMembers", {"group_id": ""}, False),
    ("H22", "群管理", "createGroup", {"wxids": [], "name": ""}, False),
    ("H22", "群管理", "inviteToGroup", {"group_id": "", "wxids": []}, False),
    ("H22", "群管理", "removeFromGroup", {"group_id": "", "wxids": []}, False),
    ("H22", "群管理", "setGroupAnnouncement", {"group_id": "", "content": ""}, False),
    ("H22", "群管理", "setGroupName", {"group_id": "", "name": ""}, False),
    ("H22", "群管理", "quitGroup", {"group_id": ""}, False),
    # H23 账号管理
    ("H23", "账号管理", "getProfile", {}, True),
    ("H23", "账号管理", "checkAccountStatus", {}, True),
    ("H23", "账号管理", "setNickname", {"nickname": ""}, False),
    ("H23", "账号管理", "setSignature", {"signature": ""}, False),
    ("H23", "账号管理", "setAvatar", {"image_path": ""}, False),
    ("H23", "账号管理", "setSex", {"sex": 1}, False),
    ("H23", "账号管理", "setRegion", {"country": "", "province": "", "city": ""}, False),
    ("H23", "账号管理", "setWhatUp", {"content": ""}, False),
    # H24 账号安全
    ("H24", "账号安全", "unblockSelf", {}, False),
    ("H24", "账号安全", "changePassword", {"old_pwd": "", "new_pwd": ""}, False),
    ("H24", "账号安全", "bindPhone", {"phone": "", "code": ""}, False),
    ("H24", "账号安全", "unbindPhone", {}, False),
    ("H24", "账号安全", "getLoginDevices", {}, True),
    ("H24", "账号安全", "removeLoginDevice", {"device_id": ""}, False),
    ("H24", "账号安全", "enableFingerprint", {"enable": True}, False),
    ("H24", "账号安全", "setAccountProtection", {"level": 1}, False),
    # H25 支付
    ("H25", "支付", "sendRedPacket", {"to_id": "", "amount": 0, "message": ""}, False),
    ("H25", "支付", "receiveRedPacket", {"msg_id": ""}, False),
    ("H25", "支付", "sendTransfer", {"to_id": "", "amount": 0}, False),
    ("H25", "支付", "receiveTransfer", {"msg_id": ""}, False),
    ("H25", "支付", "getWalletBalance", {}, True),
    ("H25", "支付", "getTransactionHistory", {"limit": 5}, True),
    # H26 二维码
    ("H26", "二维码", "scanQrCode", {"image_path": ""}, False),
    ("H26", "二维码", "generateMyQrCode", {}, True),
    ("H26", "二维码", "generateGroupQrCode", {"group_id": ""}, False),
    # H27 视频号
    ("H27", "视频号", "browseChannels", {"limit": 3}, True),
    ("H27", "视频号", "likeChannelVideo", {"video_id": ""}, False),
    ("H27", "视频号", "commentChannelVideo", {"video_id": "", "content": ""}, False),
    ("H27", "视频号", "followChannel", {"channel_id": ""}, False),
    ("H27", "视频号", "unfollowChannel", {"channel_id": ""}, False),
    ("H27", "视频号", "shareChannelVideo", {"video_id": "", "to_id": ""}, False),
    # H28 标签
    ("H28", "标签", "getLabels", {}, True),
    ("H28", "标签", "createLabel", {"name": ""}, False),
    ("H28", "标签", "deleteLabel", {"label_id": ""}, False),
    ("H28", "标签", "setContactLabel", {"wxid": "", "label_ids": []}, False),
    ("H28", "标签", "getContactsByLabel", {"label_id": ""}, False),
    # H29 收藏
    ("H29", "收藏", "getFavorites", {"limit": 5}, True),
    ("H29", "收藏", "addFavorite", {"content": "", "type": "text"}, False),
    ("H29", "收藏", "deleteFavorite", {"fav_id": ""}, False),
    # H30 设置
    ("H30", "设置", "setDoNotDisturb", {"wxid": "", "enable": True}, False),
    ("H30", "设置", "pinChat", {"wxid": "", "pin": True}, False),
    ("H30", "设置", "setChatBackground", {"wxid": "", "image_path": ""}, False),
    ("H30", "设置", "setNotification", {"enable": True}, False),
    ("H30", "设置", "setPrivacy", {"key": "", "value": True}, False),
    ("H30", "设置", "clearChatHistory", {"wxid": ""}, False),
    # H31 搜索
    ("H31", "搜索", "globalSearch", {"keyword": "微信", "limit": 5}, True),
    # H32 小程序
    ("H32", "小程序", "openMiniProgram", {"app_id": ""}, False),
    ("H32", "小程序", "getRecentMiniPrograms", {}, True),
    ("H32", "小程序", "shareMiniProgram", {"app_id": "", "to_id": ""}, False),
    # H33 文件传输
    ("H33", "文件传输", "sendImage", {"to_id": "filehelper", "image_path": "/sdcard/DCIM/Camera/test.jpg"}, False),
    ("H33", "文件传输", "sendVideo", {"to_id": "filehelper", "video_path": ""}, False),
    ("H33", "文件传输", "sendFile", {"to_id": "filehelper", "file_path": ""}, False),
    ("H33", "文件传输", "sendVoice", {"to_id": "filehelper", "voice_path": ""}, False),
    ("H33", "文件传输", "sendLocation", {"to_id": "filehelper", "lat": 0, "lng": 0, "label": ""}, False),
    ("H33", "文件传输", "sendCard", {"to_id": "filehelper", "card_wxid": ""}, False),
    ("H33", "文件传输", "sendLink", {"to_id": "filehelper", "title": "", "url": "", "desc": ""}, False),
    # H34 消息转发
    ("H34", "消息转发", "forwardMessage", {"msg_id": "", "to_id": ""}, False),
    ("H34", "消息转发", "forwardMultiple", {"msg_ids": [], "to_id": ""}, False),
    ("H34", "消息转发", "revokeMessage", {"msg_id": ""}, False),
    # H35 注册/登录
    ("H35", "注册/登录", "registerAccount", {"phone": "", "password": ""}, False),
    ("H35", "注册/登录", "loginByPassword", {"account": "", "password": ""}, False),
    ("H35", "注册/登录", "loginBySms", {"phone": "", "code": ""}, False),
    ("H35", "注册/登录", "logout", {}, False),
    ("H35", "注册/登录", "switchAccount", {"wxid": ""}, False),
    ("H35", "注册/登录", "autoRegister", {"phone": "", "code": ""}, False),
    ("H35", "注册/登录", "checkLoginState", {}, True),
    ("H35", "注册/登录", "getSimPhone", {}, True),
    # H36 公众号
    ("H36", "公众号", "getOfficialAccounts", {"limit": 5}, True),
    ("H36", "公众号", "followOfficialAccount", {"official_id": ""}, False),
    ("H36", "公众号", "unfollowOfficialAccount", {"official_id": ""}, False),
    ("H36", "公众号", "getOfficialAccountArticles", {"official_id": ""}, False),
    # H37 表情
    ("H37", "表情", "sendEmoji", {"to_id": "", "emoji_md5": ""}, False),
    ("H37", "表情", "addCustomEmoji", {"image_path": ""}, False),
    # H38 浮窗
    ("H38", "浮窗", "addToFloat", {"msg_id": ""}, False),
    ("H38", "浮窗", "removeFromFloat", {"msg_id": ""}, False),
    # H39 设备信息
    ("H39", "设备信息", "getDeviceInfo", {}, True),
    ("H39", "设备信息", "getStorageInfo", {}, True),
    ("H39", "设备信息", "getNetworkInfo", {}, True),
]


def main():
    print("=" * 60)
    print("  工作手机SDK - Frida无线真机验证 V2")
    print(f"  设备: {DEVICE_IP}:{FRIDA_PORT} | PID: {WECHAT_PID}")
    print(f"  时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 60)

    # 连接
    print("\n[1] 连接 Frida...")
    device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}")
    print(f"    设备: {device.name}")

    print("[2] Attach 微信 (PID {})...".format(WECHAT_PID))
    session = device.attach(WECHAT_PID)
    print("    成功!")

    print("[3] 加载 Hook 脚本...")
    if not os.path.exists(SCRIPT_PATH):
        print(f"    脚本不存在: {SCRIPT_PATH}")
        sys.exit(1)

    with open(SCRIPT_PATH, 'r', encoding='utf-8') as f:
        source = f.read()

    script = session.create_script(source)
    
    errors = []
    def on_message(msg, data):
        if msg.get('type') == 'error':
            errors.append(msg.get('description', ''))
    
    script.on('message', on_message)
    script.load()
    time.sleep(1)  # 等待Java.perform完成

    exports = script.exports_sync

    # 验证ping
    try:
        pong = exports.ping()
        print(f"    ping: {pong}")
    except Exception as e:
        print(f"    ping失败: {e}")
        print("    Hook脚本加载可能有问题，检查错误:")
        for err in errors:
            print(f"      {err}")
        sys.exit(1)

    print("[4] 开始验证 (共{}个方法)...\n".format(len(ALL_METHODS)))

    # 初始截图
    screenshot("00_start")

    results = []
    passed = 0
    failed = 0
    skipped = 0

    for i, (mod_id, mod_name, method, params, safe) in enumerate(ALL_METHODS, 1):
        # 对于不安全的操作（会修改数据），只验证方法存在性
        if not safe:
            # 只检查方法是否存在
            py_name = method.lower()
            fn = getattr(exports, py_name, None)
            if fn is not None:
                status = "EXIST"
                result_data = {"success": True, "note": "方法存在(跳过执行,避免影响账号)"}
                passed += 1
            else:
                status = "MISSING"
                result_data = {"success": False, "error": f"方法不存在: {py_name}"}
                failed += 1
            
            icon = "✅" if status == "EXIST" else "❌"
            print(f"  [{i:3d}] {icon} {mod_id}/{method} -> {status} (unsafe, skip exec)")
        else:
            # 安全操作，实际执行
            start = time.time()
            result_data = call_rpc(exports, method, params)
            latency = int((time.time() - start) * 1000)

            if isinstance(result_data, str):
                # ping等返回字符串
                status = "PASS"
                result_data = {"success": True, "value": result_data}
                passed += 1
            elif isinstance(result_data, dict):
                if result_data.get("success", False) or "error" not in result_data:
                    status = "PASS"
                    passed += 1
                elif "方法不存在" in result_data.get("error", ""):
                    status = "MISSING"
                    failed += 1
                else:
                    # 有error但方法存在（可能是参数问题或微信状态问题）
                    status = "PARTIAL"
                    passed += 1  # 方法存在就算通过
            elif isinstance(result_data, bool):
                status = "PASS"
                result_data = {"success": True, "value": result_data}
                passed += 1
            else:
                status = "PASS"
                result_data = {"success": True, "value": str(result_data)}
                passed += 1

            icon = "✅" if status in ("PASS", "PARTIAL") else "❌"
            preview = json.dumps(result_data, ensure_ascii=False)[:80] if isinstance(result_data, dict) else str(result_data)[:80]
            print(f"  [{i:3d}] {icon} {mod_id}/{method} -> {status} ({latency}ms) | {preview}")

            # 对关键操作截图
            if method in ("sendMessage", "getProfile", "getContacts", "getGroups", "getLabels"):
                screenshot(f"{i:03d}_{method}")

        results.append({
            "index": i,
            "module_id": mod_id,
            "module_name": mod_name,
            "method": method,
            "params": params,
            "safe": safe,
            "status": status,
            "result": result_data,
            "timestamp": datetime.now().isoformat(),
        })

        time.sleep(0.3)

    # 最终截图
    screenshot("99_end")

    # 汇总
    total = len(ALL_METHODS)
    print(f"\n{'='*60}")
    print(f"  验证完成!")
    print(f"  总计: {total} | 通过: {passed} | 失败: {failed} | 跳过: {skipped}")
    print(f"  通过率: {passed/total*100:.1f}%")
    print(f"{'='*60}")

    # 保存JSON结果
    report = {
        "title": "Frida无线真机验证报告",
        "device": {"ip": DEVICE_IP, "port": FRIDA_PORT, "pid": WECHAT_PID, "serial": DEVICE_SERIAL},
        "time": datetime.now().isoformat(),
        "summary": {"total": total, "passed": passed, "failed": failed, "skipped": skipped, "pass_rate": f"{passed/total*100:.1f}%"},
        "results": results,
    }
    with open(RESULT_FILE, 'w', encoding='utf-8') as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    print(f"\n结果已保存: {RESULT_FILE}")

    # 生成Markdown报告
    generate_report(report)

    # 清理
    try:
        script.unload()
        session.detach()
    except:
        pass


def generate_report(report):
    """生成Markdown验证报告"""
    lines = []
    lines.append("# 工作手机SDK - Frida无线真机验证报告\n")
    lines.append(f"> 时间: {report['time']}")
    lines.append(f"> 设备: {DEVICE_IP}:{FRIDA_PORT} (PID: {WECHAT_PID})")
    lines.append(f"> 连接方式: WiFi TCP (无USB)\n")
    
    s = report['summary']
    lines.append("## 验证总览\n")
    lines.append(f"| 指标 | 数值 |")
    lines.append(f"|------|------|")
    lines.append(f"| 总方法数 | {s['total']} |")
    lines.append(f"| 通过 | {s['passed']} |")
    lines.append(f"| 失败 | {s['failed']} |")
    lines.append(f"| **通过率** | **{s['pass_rate']}** |\n")

    # 按模块分组
    lines.append("## 详细结果\n")
    current_mod = ""
    for r in report['results']:
        if r['module_id'] != current_mod:
            current_mod = r['module_id']
            lines.append(f"\n### {r['module_id']} {r['module_name']}\n")
            lines.append("| # | 方法 | 状态 | 说明 |")
            lines.append("|---|------|------|------|")
        
        icon = "✅" if r['status'] in ("PASS", "EXIST", "PARTIAL") else "❌"
        note = ""
        if isinstance(r.get('result'), dict):
            if r['result'].get('error'):
                note = r['result']['error'][:50]
            elif r['result'].get('note'):
                note = r['result']['note'][:50]
            elif r['result'].get('value'):
                note = str(r['result']['value'])[:50]
        lines.append(f"| {r['index']} | `{r['method']}` | {icon} {r['status']} | {note} |")

    lines.append("\n## 截图目录\n")
    lines.append(f"截图保存在: `{SCREENSHOT_DIR}`\n")

    with open(REPORT_FILE, 'w', encoding='utf-8') as f:
        f.write('\n'.join(lines))
    print(f"报告已保存: {REPORT_FILE}")


if __name__ == "__main__":
    main()
