#!/usr/bin/env python3
"""
工作手机 SDK - 完整HTTP REST API服务
版本: v9-complete
功能: 微信全量控制接口，基于Frida无感注入
"""

import frida
import json
import time
import threading
import subprocess
import os
import sys
from flask import Flask, request, jsonify
from functools import wraps
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logger = logging.getLogger(__name__)

app = Flask(__name__)

# 配置
API_KEY = os.environ.get('API_KEY', 'workphone-sdk-2026')
PHONE_IP = os.environ.get('PHONE_IP', '192.168.110.80')
PHONE_PORT = int(os.environ.get('PHONE_PORT', 27042))
JS_FILE = os.path.join(os.path.dirname(__file__), 'sdk/agent/hook/wechat_api_v8_fix.js')

# 全局状态
class SDKState:
    device = None
    session = None
    script = None
    connected = False
    wechat_pid = None
    lock = threading.Lock()
    last_heartbeat = 0

state = SDKState()

def get_js_code():
    """读取JS脚本"""
    js_paths = [
        JS_FILE,
        os.path.join(os.path.dirname(__file__), 'wechat_api_v8_fix.js'),
        '/Users/karuo/Documents/开发/2、私域银行/工作手机/sdk/agent/hook/wechat_api_v8_fix.js',
    ]
    for p in js_paths:
        if os.path.exists(p):
            with open(p, 'r', encoding='utf-8') as f:
                return f.read()
    raise FileNotFoundError(f"JS文件未找到，已尝试: {js_paths}")

def get_wechat_pid():
    """获取微信主进程PID"""
    try:
        result = subprocess.run(
            ['adb', '-s', f'{PHONE_IP}:5555', 'shell', 'ps -A | grep com.tencent.mm'],
            capture_output=True, text=True, timeout=5
        )
        for line in result.stdout.split('\n'):
            if 'com.tencent.mm' in line and ':push' not in line and ':sandbox' not in line and ':tools' not in line:
                parts = line.split()
                if len(parts) >= 2:
                    return int(parts[1])
    except Exception as e:
        logger.error(f"获取微信PID失败: {e}")
    return None

def ensure_frida_forward():
    """确保ADB端口转发"""
    try:
        subprocess.run(
            ['adb', '-s', f'{PHONE_IP}:5555', 'forward', 'tcp:27042', 'tcp:27042'],
            capture_output=True, timeout=5
        )
    except Exception as e:
        logger.warning(f"端口转发失败: {e}")

def connect_frida():
    """连接Frida并注入脚本"""
    with state.lock:
        try:
            ensure_frida_forward()
            
            # 获取设备
            dm = frida.get_device_manager()
            state.device = dm.add_remote_device('localhost:27042')
            
            # 获取微信PID
            pid = get_wechat_pid()
            if not pid:
                # 尝试启动微信
                subprocess.run(
                    ['adb', '-s', f'{PHONE_IP}:5555', 'shell', 
                     'monkey -p com.tencent.mm -c android.intent.category.LAUNCHER 1'],
                    capture_output=True, timeout=10
                )
                time.sleep(3)
                pid = get_wechat_pid()
            
            if not pid:
                raise Exception("无法获取微信PID")
            
            state.wechat_pid = pid
            logger.info(f"微信PID: {pid}")
            
            # Attach
            state.session = state.device.attach(pid)
            
            # 加载脚本
            js_code = get_js_code()
            state.script = state.session.create_script(js_code)
            
            def on_message(message, data):
                if message.get('type') == 'send':
                    payload = message.get('payload', {})
                    if isinstance(payload, dict):
                        logger.debug(f"Frida: {payload.get('type', '')} - {str(payload.get('data', ''))[:100]}")
                elif message.get('type') == 'error':
                    logger.error(f"Frida错误: {message.get('description', '')}")
            
            state.script.on('message', on_message)
            state.script.load()
            
            # 初始化DB
            time.sleep(1)
            try:
                state.script.exports.initDbs()
            except Exception as e:
                logger.warning(f"initDbs: {e}")
            
            state.connected = True
            state.last_heartbeat = time.time()
            logger.info("✅ Frida连接成功")
            return True
            
        except Exception as e:
            logger.error(f"Frida连接失败: {e}")
            state.connected = False
            return False

def ensure_connected():
    """确保Frida连接有效"""
    if not state.connected or not state.script:
        return connect_frida()
    
    # 心跳检测（每30秒）
    if time.time() - state.last_heartbeat > 30:
        try:
            state.script.exports.ping()
            state.last_heartbeat = time.time()
        except Exception:
            logger.warning("Frida心跳失败，重新连接...")
            state.connected = False
            return connect_frida()
    
    return True

def call_rpc(method, *args):
    """调用Frida RPC方法"""
    if not ensure_connected():
        raise Exception("Frida未连接")
    
    try:
        func = getattr(state.script.exports, method)
        result = func(*args) if args else func()
        state.last_heartbeat = time.time()
        return result
    except Exception as e:
        logger.error(f"RPC调用失败 {method}: {e}")
        # 尝试重连一次
        if 'session' in str(e).lower() or 'detach' in str(e).lower():
            state.connected = False
            if connect_frida():
                func = getattr(state.script.exports, method)
                return func(*args) if args else func()
        raise

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get('X-API-Key') or request.args.get('api_key')
        if key != API_KEY:
            return jsonify({'error': '无效的API Key'}), 401
        return f(*args, **kwargs)
    return decorated

def success(data=None, **kwargs):
    resp = {'success': True, 'timestamp': int(time.time() * 1000)}
    if data is not None:
        resp['data'] = data
    resp.update(kwargs)
    return jsonify(resp)

def error(msg, code=500):
    return jsonify({'success': False, 'error': str(msg)}), code

# ==================== 系统接口 ====================

@app.route('/api/v1/ping', methods=['GET'])
def ping():
    return jsonify({
        'status': 'ok',
        'version': 'v9-complete',
        'frida_connected': state.connected,
        'wechat_pid': state.wechat_pid,
        'timestamp': int(time.time() * 1000)
    })

@app.route('/api/v1/init', methods=['POST'])
@require_api_key
def init():
    result = connect_frida()
    return success({'connected': result, 'pid': state.wechat_pid})

@app.route('/api/v1/dbs', methods=['GET'])
@require_api_key
def list_dbs():
    try:
        result = call_rpc('getDbInfo')
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 账号接口 ====================

@app.route('/api/v1/account', methods=['GET'])
@require_api_key
def get_account():
    try:
        result = call_rpc('getAccountInfo')
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 联系人接口 ====================

@app.route('/api/v1/contacts', methods=['GET'])
@require_api_key
def get_contacts():
    limit = int(request.args.get('limit', 100))
    offset = int(request.args.get('offset', 0))
    try:
        result = call_rpc('getContacts', limit, offset)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/contacts/count', methods=['GET'])
@require_api_key
def get_contacts_count():
    try:
        result = call_rpc('getContactStats')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/contacts/search', methods=['GET'])
@require_api_key
def search_contacts():
    keyword = request.args.get('keyword', '')
    limit = int(request.args.get('limit', 50))
    try:
        result = call_rpc('searchContacts', keyword, limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/contacts/<wxid>', methods=['GET'])
@require_api_key
def get_contact(wxid):
    try:
        result = call_rpc('getContactDetail', wxid)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 消息接口 ====================

@app.route('/api/v1/messages/send', methods=['POST'])
@require_api_key
def send_message():
    data = request.get_json()
    to_user = data.get('to_user', 'filehelper')
    content = data.get('content', '')
    msg_type = data.get('type', 1)
    
    if not content:
        return error('content不能为空', 400)
    
    try:
        result = call_rpc('sendMessage', to_user, content, msg_type)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/messages/mass', methods=['POST'])
@require_api_key
def mass_send():
    data = request.get_json()
    targets = data.get('targets', [])
    content = data.get('content', '')
    
    if not targets or not content:
        return error('targets和content不能为空', 400)
    
    try:
        result = call_rpc('massMessage', targets, content)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/messages/<talker>', methods=['GET'])
@require_api_key
def get_messages(talker):
    limit = int(request.args.get('limit', 50))
    try:
        result = call_rpc('getMessages', talker, limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/messages/recent', methods=['GET'])
@require_api_key
def get_recent_messages():
    hours = int(request.args.get('hours', 24))
    try:
        result = call_rpc('getRecentMessages', hours)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 会话接口 ====================

@app.route('/api/v1/conversations', methods=['GET'])
@require_api_key
def get_conversations():
    limit = int(request.args.get('limit', 30))
    try:
        result = call_rpc('getConversations', limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 群组接口 ====================

@app.route('/api/v1/groups', methods=['GET'])
@require_api_key
def get_groups():
    limit = int(request.args.get('limit', 50))
    try:
        result = call_rpc('getGroups', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/groups/count', methods=['GET'])
@require_api_key
def get_groups_count():
    try:
        result = call_rpc('getGroupStats')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/groups/<chatroom_id>/members', methods=['GET'])
@require_api_key
def get_group_members(chatroom_id):
    try:
        result = call_rpc('getGroupMembers', chatroom_id)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/groups/<chatroom_id>/remark', methods=['PUT'])
@require_api_key
def set_group_remark(chatroom_id):
    data = request.get_json()
    remark = data.get('remark', '')
    try:
        result = call_rpc('setGroupRemark', chatroom_id, remark)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/groups/<chatroom_id>/announcement', methods=['PUT'])
@require_api_key
def set_group_announcement(chatroom_id):
    data = request.get_json()
    announcement = data.get('announcement', '')
    try:
        result = call_rpc('setGroupAnnouncement', chatroom_id, announcement)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 标签接口 ====================

@app.route('/api/v1/labels', methods=['GET'])
@require_api_key
def get_labels():
    try:
        result = call_rpc('getLabels')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/labels/<label_id>/contacts', methods=['GET'])
@require_api_key
def get_label_contacts(label_id):
    limit = int(request.args.get('limit', 100))
    try:
        result = call_rpc('getContactsByLabel', label_id, limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 朋友圈接口 ====================

@app.route('/api/v1/moments', methods=['GET'])
@require_api_key
def get_moments():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getMoments', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/moments', methods=['POST'])
@require_api_key
def post_moment():
    data = request.get_json()
    content = data.get('content', '')
    if not content:
        return error('content不能为空', 400)
    try:
        result = call_rpc('postMoment', content)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/moments/dbinfo', methods=['GET'])
@require_api_key
def moments_dbinfo():
    try:
        result = call_rpc('getMomentsDbInfo')
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 视频号接口 ====================

@app.route('/api/v1/finder/info', methods=['GET'])
@require_api_key
def finder_info():
    try:
        result = call_rpc('getFinderInfo')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/finder/contacts', methods=['GET'])
@require_api_key
def finder_contacts():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getFinderContacts', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/finder/accounts', methods=['GET'])
@require_api_key
def finder_accounts():
    try:
        result = call_rpc('getFinderAccounts')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/finder/media', methods=['GET'])
@require_api_key
def finder_media():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getFinderMedia', limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 收藏接口 ====================

@app.route('/api/v1/favorites', methods=['GET'])
@require_api_key
def get_favorites():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getFavorites', limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 钱包接口 ====================

@app.route('/api/v1/wallet/info', methods=['GET'])
@require_api_key
def wallet_info():
    try:
        result = call_rpc('getWalletInfo')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/wallet/ledger', methods=['GET'])
@require_api_key
def wallet_ledger():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getWalletLedger', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/wallet/lucky-money', methods=['GET'])
@require_api_key
def lucky_money():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getLuckyMoney', limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 媒体接口 ====================

@app.route('/api/v1/media/images', methods=['GET'])
@require_api_key
def get_images():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getImages', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/media/images/count', methods=['GET'])
@require_api_key
def images_count():
    try:
        result = call_rpc('getImageCount')
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/media/videos', methods=['GET'])
@require_api_key
def get_videos():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getVideos', limit)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/media/files', methods=['GET'])
@require_api_key
def get_files():
    limit = int(request.args.get('limit', 20))
    try:
        result = call_rpc('getFiles', limit)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 小程序接口 ====================

@app.route('/api/v1/appbrand', methods=['GET'])
@require_api_key
def get_appbrand():
    try:
        result = call_rpc('getAppBrandInfo')
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 搜索接口 ====================

@app.route('/api/v1/search', methods=['GET'])
@require_api_key
def search():
    keyword = request.args.get('keyword', '')
    if not keyword:
        return error('keyword不能为空', 400)
    try:
        result = call_rpc('globalSearch', keyword)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 原始SQL接口 ====================

@app.route('/api/v1/raw/query', methods=['POST'])
@require_api_key
def raw_query():
    data = request.get_json()
    db = data.get('db', 'main')
    sql = data.get('sql', '')
    if not sql:
        return error('sql不能为空', 400)
    try:
        result = call_rpc('rawSql', db, sql)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/raw/write', methods=['POST'])
@require_api_key
def raw_write():
    data = request.get_json()
    db = data.get('db', 'main')
    sql = data.get('sql', '')
    if not sql:
        return error('sql不能为空', 400)
    # 安全检查
    sql_upper = sql.strip().upper()
    if sql_upper.startswith('DROP') or sql_upper.startswith('TRUNCATE'):
        return error('不允许DROP/TRUNCATE操作', 403)
    try:
        result = call_rpc('rawSqlWrite', db, sql)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/raw/tables', methods=['GET'])
@require_api_key
def list_tables():
    db = request.args.get('db', 'main')
    try:
        result = call_rpc('listTables', db)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v1/raw/table', methods=['GET'])
@require_api_key
def table_info():
    db = request.args.get('db', 'main')
    table = request.args.get('table', '')
    if not table:
        return error('table不能为空', 400)
    try:
        result = call_rpc('getTableInfo', db, table)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 存客宝对接接口 ====================

@app.route('/api/v3/cunke-bao/contacts/sync', methods=['POST'])
@require_api_key
def ckb_sync_contacts():
    """批量同步联系人到存客宝"""
    data = request.get_json()
    limit = data.get('limit', 100)
    offset = data.get('offset', 0)
    try:
        contacts = call_rpc('getContacts', limit, offset)
        # 格式化为存客宝格式
        ckb_contacts = []
        for c in (contacts.get('contacts', []) if isinstance(contacts, dict) else []):
            ckb_contacts.append({
                'wxid': c.get('username', ''),
                'nickname': c.get('nickname', ''),
                'remark': c.get('remark', ''),
                'avatar': c.get('headImgUrl', ''),
                'labels': c.get('contactLabelIds', ''),
                'sex': c.get('sex', 0),
                'region': f"{c.get('province', '')} {c.get('city', '')}".strip(),
            })
        return success({'contacts': ckb_contacts, 'count': len(ckb_contacts), 'total': contacts.get('total', 0) if isinstance(contacts, dict) else 0})
    except Exception as e:
        return error(e)

@app.route('/api/v3/cunke-bao/message/send', methods=['POST'])
@require_api_key
def ckb_send_message():
    """存客宝发送消息"""
    data = request.get_json()
    to_user = data.get('wxid', data.get('to_user', ''))
    content = data.get('content', data.get('message', ''))
    if not to_user or not content:
        return error('wxid和content不能为空', 400)
    try:
        result = call_rpc('sendMessage', to_user, content, 1)
        return success(result)
    except Exception as e:
        return error(e)

@app.route('/api/v3/cunke-bao/groups', methods=['GET'])
@require_api_key
def ckb_get_groups():
    """存客宝获取群列表"""
    try:
        result = call_rpc('getGroups', 100)
        return success(result)
    except Exception as e:
        return error(e)

# ==================== 健康检查和自动重连 ====================

def heartbeat_thread():
    """后台心跳线程，保持Frida连接"""
    while True:
        try:
            time.sleep(60)
            if state.connected:
                try:
                    call_rpc('ping')
                    logger.debug("心跳正常")
                except Exception as e:
                    logger.warning(f"心跳失败: {e}，尝试重连...")
                    state.connected = False
                    connect_frida()
        except Exception as e:
            logger.error(f"心跳线程异常: {e}")

if __name__ == '__main__':
    print("=" * 60)
    print("工作手机 SDK API 服务 v9-complete")
    print(f"手机IP: {PHONE_IP}")
    print(f"API Key: {API_KEY}")
    print("=" * 60)
    
    # 初始连接
    logger.info("正在连接Frida...")
    if connect_frida():
        logger.info("✅ Frida连接成功")
    else:
        logger.warning("⚠️ Frida初始连接失败，将在请求时重试")
    
    # 启动心跳线程
    t = threading.Thread(target=heartbeat_thread, daemon=True)
    t.start()
    
    # 启动API服务
    app.run(host='0.0.0.0', port=8899, debug=False, threaded=True)
