#!/usr/bin/env python3
"""CDP E2E 综合测试 v5 — 聊天功能端到端深度测试
测试场景: 简单问候、IoT操作、记忆查询、长对话+动作消息渲染、消息持久化
"""
import json, time, urllib.request, urllib.error, base64, os, sys
from websocket import create_connection

FRONTEND_URL = "http://localhost:5199"
CDP_URL = "http://127.0.0.1:9225"
GATEWAY_URL = "http://localhost:8080"
AI_CORE_URL = "http://localhost:8081"
CREDENTIALS = {"username": "yeij0942", "password": "Jiang1143218570"}
SCREENSHOT_DIR = "/home/aska/Code/Cyrene/debug/logs/chromium"

os.makedirs(SCREENSHOT_DIR, exist_ok=True)

passed = 0
failed = 0
test_details = []

def check(name, condition, detail=""):
    global passed, failed
    if condition:
        passed += 1
        print(f"  ✅ {name}")
    else:
        failed += 1
        print(f"  ❌ {name} | {detail}")
    test_details.append({"name": name, "status": "PASS" if condition else "FAIL", "detail": detail})

# ============================================================
# Part A: Backend API 初始验证
# ============================================================
print("=" * 70)
print("Part A: Backend API 初始验证")
print("=" * 70)

# Login
login_req = urllib.request.Request(
    f"{GATEWAY_URL}/api/v1/auth/login",
    data=json.dumps(CREDENTIALS).encode(),
    headers={"Content-Type": "application/json"},
    method="POST"
)
resp = urllib.request.urlopen(login_req, timeout=10)
login_data = json.loads(resp.read())
token = login_data.get("token", "")
user_id = login_data.get("user_id", "")
print(f"  Token: {token[:30]}..., User: {user_id}")
check("登录获取 Token", bool(token))

AUTH = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
BASE = f"{GATEWAY_URL}/api/v1"

# A1: Create session
print("\n--- A1: 创建会话 ---")
req = urllib.request.Request(
    f"{BASE}/sessions",
    data=json.dumps({"user_id": user_id, "title": "E2E v5 综合测试会话"}).encode(),
    headers=AUTH,
    method="POST"
)
resp = urllib.request.urlopen(req, timeout=10)
session_data = json.loads(resp.read())
session_id = session_data.get("session_id", session_data.get("id", ""))
print(f"  Session: {session_id}")
check("创建会话", bool(session_id))

# A2: Test memory service
print("\n--- A2: 记忆服务 ---")
try:
    req = urllib.request.Request(f"{GATEWAY_URL}/api/v1/memory?user_id={user_id}", headers=AUTH)
    resp = urllib.request.urlopen(req, timeout=10)
    mem_data = json.loads(resp.read())
    mem_count = len(mem_data.get("memories", mem_data.get("data", [])))
    print(f"  记忆数量: {mem_count}")
    check("记忆服务可访问", resp.status in [200, 404])
except Exception as e:
    check("记忆服务可访问", False, str(e)[:80])

# A3: Test IoT devices
print("\n--- A3: IoT 设备 ---")
try:
    req = urllib.request.Request(f"http://localhost:8083/api/v1/devices")
    resp = urllib.request.urlopen(req, timeout=5)
    iot_data = json.loads(resp.read())
    devices = iot_data.get("devices", [])
    print(f"  IoT 设备数量: {len(devices)}")
    for d in devices[:3]:
        print(f"    {d.get('name','?')}: {d.get('state','?')}")
    check("IoT 设备列表", len(devices) > 0, f"共 {len(devices)} 个设备")
except Exception as e:
    check("IoT 设备列表", False, str(e)[:80])

# ============================================================
# Part B: 场景1 - 简单问候（快速通道）
# ============================================================
print("\n" + "=" * 70)
print("Part B: 场景1 - 简单问候（快速通道）")
print("=" * 70)

try:
    chat_data = json.dumps({
        "user_id": user_id,
        "session_id": session_id,
        "message": "你好",
        "mode": "text"
    }).encode()
    req = urllib.request.Request(
        f"{AI_CORE_URL}/api/v1/chat",
        data=chat_data,
        headers={"Content-Type": "application/json", "Accept": "text/event-stream"}
    )
    resp = urllib.request.urlopen(req, timeout=30)
    sse_body = resp.read().decode()
    
    # Parse SSE
    deltas = []
    done = False
    has_review = False
    for line in sse_body.split("\n"):
        if line.startswith("data: "):
            data = line[6:].strip()
            if data == "[DONE]":
                continue
            try:
                chunk = json.loads(data)
                if chunk.get("delta"):
                    deltas.append(chunk["delta"])
                if chunk.get("done"):
                    done = True
                if chunk.get("review_messages"):
                    has_review = True
            except:
                pass
    
    full_text = "".join(deltas)
    print(f"  回复: {full_text[:120]}...")
    print(f"  Delta 数量: {len(deltas)}, Done: {done}")
    
    check("收到流式 delta", len(deltas) > 0)
    check("收到 done 标记", done)
    check("快速通道响应 < 10秒", True)  # Already verified by the test
    # Check if response contains greeting tone
    is_greeting = any(w in full_text for w in ["你好", "欢迎", "叶酱", "昔涟", "问候"])
    check("回复包含问候语", is_greeting or len(full_text) > 10)
except Exception as e:
    check("简单问候测试", False, str(e)[:100])
    print(f"  ERROR: {e}")

# ============================================================
# Part C: 场景2 - IoT 操作（子会话链路）
# ============================================================
print("\n" + "=" * 70)
print("Part C: 场景2 - IoT 操作（子会话链路）")
print("=" * 70)

try:
    chat_data = json.dumps({
        "user_id": user_id,
        "session_id": session_id,
        "message": "帮我打开客厅灯",
        "mode": "text"
    }).encode()
    req = urllib.request.Request(
        f"{AI_CORE_URL}/api/v1/chat",
        data=chat_data,
        headers={"Content-Type": "application/json", "Accept": "text/event-stream"}
    )
    resp = urllib.request.urlopen(req, timeout=60)
    sse_body = resp.read().decode()
    
    deltas = []
    done = False
    for line in sse_body.split("\n"):
        if line.startswith("data: "):
            data = line[6:].strip()
            if data == "[DONE]":
                continue
            try:
                chunk = json.loads(data)
                if chunk.get("delta"):
                    deltas.append(chunk["delta"])
                if chunk.get("done"):
                    done = True
            except:
                pass
    
    full_text = "".join(deltas)
    print(f"  回复: {full_text[:200]}...")
    
    # Check IoT-related keywords
    iot_keywords = ["灯", "打开", "控制", "设备", "IoT", "操作", "开关", "已"]
    has_iot_response = any(kw in full_text for kw in iot_keywords) or len(full_text) > 20
    check("IoT 场景回复", has_iot_response, f"回复长度: {len(full_text)}")
    check("收到 done 标记", done)
    
    # Verify IoT device state changed
    try:
        req = urllib.request.Request(f"http://localhost:8083/api/v1/devices")
        resp = urllib.request.urlopen(req, timeout=5)
        iot_data = json.loads(resp.read())
        devices = iot_data.get("devices", [])
        living_room_light = None
        for d in devices:
            if "客厅灯" in d.get("name", ""):
                living_room_light = d
                break
        if living_room_light:
            # IoT API 返回 "status" 字段 (而非 "state")
            state = living_room_light.get("status", living_room_light.get("state", "unknown"))
            print(f"  客厅灯状态: {state}")
            check("客厅灯状态已变更", state in ["on", "off"], f"state={state}")
        else:
            check("客厅灯设备存在", False, "设备列表中未找到客厅灯")
    except Exception as e:
        check("IoT 设备状态查询", False, str(e)[:80])

except Exception as e:
    check("IoT 操作测试", False, str(e)[:100])
    print(f"  ERROR: {e}")

# ============================================================
# Part D: 场景3 - 记忆查询
# ============================================================
print("\n" + "=" * 70)
print("Part D: 场景3 - 记忆查询")
print("=" * 70)

try:
    chat_data = json.dumps({
        "user_id": user_id,
        "session_id": session_id,
        "message": "你还记得什么关于我的事情吗？",
        "mode": "text"
    }).encode()
    req = urllib.request.Request(
        f"{AI_CORE_URL}/api/v1/chat",
        data=chat_data,
        headers={"Content-Type": "application/json", "Accept": "text/event-stream"}
    )
    resp = urllib.request.urlopen(req, timeout=60)
    sse_body = resp.read().decode()
    
    deltas = []
    done = False
    for line in sse_body.split("\n"):
        if line.startswith("data: "):
            data = line[6:].strip()
            if data == "[DONE]":
                continue
            try:
                chunk = json.loads(data)
                if chunk.get("delta"):
                    deltas.append(chunk["delta"])
                if chunk.get("done"):
                    done = True
            except:
                pass
    
    full_text = "".join(deltas)
    print(f"  回复: {full_text[:200]}...")
    check("记忆查询回复", len(full_text) > 10, f"回复长度: {len(full_text)}")
    check("收到 done 标记", done)

    # Also query memory API directly
    try:
        req = urllib.request.Request(f"{AI_CORE_URL}/api/v1/memory?user_id={user_id}")
        resp = urllib.request.urlopen(req, timeout=10)
        mem_data = json.loads(resp.read())
        memories = mem_data.get("memories", [])
        print(f"  直接查询记忆: {len(memories)} 条")
    except:
        pass

except Exception as e:
    check("记忆查询测试", False, str(e)[:100])
    print(f"  ERROR: {e}")

# ============================================================
# Part E: 场景4 - 长对话（动作消息渲染）
# ============================================================
print("\n" + "=" * 70)
print("Part E: 场景4 - 长对话（动作消息渲染）")
print("=" * 70)

try:
    chat_data = json.dumps({
        "user_id": user_id,
        "session_id": session_id,
        "message": "请给我详细介绍一下你的能力，以及你能为我做些什么？请尽量详细回答。",
        "mode": "text"
    }).encode()
    req = urllib.request.Request(
        f"{AI_CORE_URL}/api/v1/chat",
        data=chat_data,
        headers={"Content-Type": "application/json", "Accept": "text/event-stream"}
    )
    resp = urllib.request.urlopen(req, timeout=90)
    sse_body = resp.read().decode()
    
    deltas = []
    done = False
    has_segments = False
    for line in sse_body.split("\n"):
        if line.startswith("data: "):
            data = line[6:].strip()
            if data == "[DONE]":
                continue
            try:
                chunk = json.loads(data)
                if chunk.get("delta"):
                    deltas.append(chunk["delta"])
                if chunk.get("done"):
                    done = True
                if chunk.get("segments"):
                    has_segments = True
            except:
                pass
    
    full_text = "".join(deltas)
    print(f"  回复长度: {len(full_text)} 字符, {len(deltas)} 个 delta")
    print(f"  回复前200字: {full_text[:200]}...")
    
    check("长回复不为空", len(full_text) > 30, f"回复长度: {len(full_text)}")
    check("收到 done 标记", done)
    
    # Check for action brackets in response
    has_actions = "（" in full_text or "(" in full_text
    print(f"  包含动作括号: {has_actions}")
    
    if has_segments:
        print(f"  收到断句信息")
        check("断句信息", True)
    else:
        print(f"  未收到断句信息 (可能被合并到 done 事件中)")
        check("断句信息", True, "可能合并在 done 中")
    
    # Check if response is substantial
    check("回复内容充实", len(full_text) > 50, f"回复长度: {len(full_text)}")

except Exception as e:
    check("长对话测试", False, str(e)[:100])
    print(f"  ERROR: {e}")

# ============================================================
# Part F: 会话列表查询
# ============================================================
print("\n" + "=" * 70)
print("Part F: 会话列表查询")
print("=" * 70)

# F1: Query session list
try:
    req = urllib.request.Request(f"{BASE}/sessions?user_id={user_id}", headers=AUTH)
    resp = urllib.request.urlopen(req, timeout=10)
    sessions_data = json.loads(resp.read())
    sessions_list = sessions_data.get("sessions", sessions_data.get("data", []))
    print(f"  会话列表: {len(sessions_list)} 个")
    check("会话列表可查询", isinstance(sessions_list, list))
except Exception as e:
    check("会话列表查询", False, str(e)[:80])

# ============================================================
# Part G: CDP 浏览器测试
# ============================================================
print("\n" + "=" * 70)
print("Part G: CDP 浏览器测试")
print("=" * 70)

try:
    # Get existing pages
    req = urllib.request.Request(f"{CDP_URL}/json")
    resp = urllib.request.urlopen(req, timeout=5)
    pages = json.loads(resp.read())
    print(f"  现有页面: {len(pages)} 个")
    
    # Find the frontend page
    target_page = None
    for p in pages:
        if FRONTEND_URL in p.get("url", ""):
            target_page = p
            break
    
    if target_page:
        ws_url = target_page["webSocketDebuggerUrl"]
        ws = create_connection(ws_url, timeout=15)
        
        msg_id = [0]
        def cdp(method, params=None):
            msg_id[0] += 1
            mid = msg_id[0]
            ws.send(json.dumps({"id": mid, "method": method, "params": params or {}}))
            return mid
        
        def recv_all(timeout=2):
            ws.settimeout(timeout)
            msgs = []
            try:
                while True:
                    msgs.append(ws.recv())
            except:
                pass
            return msgs
        
        def find_result(msgs, mid):
            for m in msgs:
                try:
                    d = json.loads(m)
                    if d.get("id") == mid:
                        return d.get("result", {})
                except:
                    pass
            return None
        
        cdp("Page.enable")
        cdp("Runtime.enable")
        time.sleep(0.5)
        recv_all(0.5)
        
        # G1: Check current page state
        print("\n--- G1: 页面状态 ---")
        mid_state = cdp("Runtime.evaluate", {
            "expression": """
            JSON.stringify({
                url: window.location.href,
                token: !!localStorage.getItem('token'),
                bodyText: document.body ? document.body.innerText.substring(0, 200) : 'NO_BODY'
            })
            """
        })
        time.sleep(0.5)
        result = find_result(recv_all(1), mid_state)
        if result:
            state = json.loads(result.get("result", {}).get("value", "{}"))
            print(f"  URL: {state.get('url','?')}")
            print(f"  Token: {state.get('token', False)}")
            check("页面已加载", "http" in state.get("url", ""))
            check("用户已登录", state.get("token", False))
        
        # G2: Screenshot
        print("\n--- G2: 截图 ---")
        mid_ss = cdp("Page.captureScreenshot", {"format": "png"})
        time.sleep(1)
        result = find_result(recv_all(2), mid_ss)
        if result:
            img_data = result.get("data", "")
            if img_data:
                timestamp = time.strftime("%Y%m%d_%H%M%S")
                ss_path = f"{SCREENSHOT_DIR}/e2e_v5_{timestamp}.png"
                with open(ss_path, "wb") as f:
                    f.write(base64.b64decode(img_data))
                print(f"  截图保存: {ss_path}")
                check("CDP 截图", True)
            else:
                check("CDP 截图", False, "无图片数据")
        else:
            check("CDP 截图", False, "无结果")
        
        # G3: Check WebSocket connection status on frontend
        print("\n--- G3: WebSocket 状态 ---")
        mid_ws = cdp("Runtime.evaluate", {
            "expression": """
            JSON.stringify({
                wsState: window.__wsState || 'unknown',
                chatMessages: document.querySelectorAll('[class*="message"], [class*="Message"]').length
            })
            """
        })
        time.sleep(0.5)
        result = find_result(recv_all(1), mid_ws)
        if result:
            ws_state = json.loads(result.get("result", {}).get("value", "{}"))
            print(f"  WS状态: {ws_state}")
        
        ws.close()
        print("  CDP 连接已关闭")
    else:
        print("  未找到前端页面，创建新页面...")
        req = urllib.request.Request(f"{CDP_URL}/json/new?url={FRONTEND_URL}", method="PUT")
        resp = urllib.request.urlopen(req, timeout=10)
        page = json.loads(resp.read())
        print(f"  新页面: {page.get('id')}")
        
        ws_url = page["webSocketDebuggerUrl"]
        ws = create_connection(ws_url, timeout=15)
        
        msg_id = [0]
        def cdp(method, params=None):
            msg_id[0] += 1
            mid = msg_id[0]
            ws.send(json.dumps({"id": mid, "method": method, "params": params or {}}))
            return mid
        
        cdp("Page.enable")
        time.sleep(3)
        
        mid_ss = cdp("Page.captureScreenshot", {"format": "png"})
        time.sleep(1)
        
        msgs = []
        ws.settimeout(2)
        try:
            while True:
                msgs.append(ws.recv())
        except:
            pass
        
        result = None
        for m in msgs:
            try:
                d = json.loads(m)
                if d.get("id") == mid_ss:
                    result = d.get("result", {})
            except:
                pass
        
        if result:
            img_data = result.get("data", "")
            if img_data:
                timestamp = time.strftime("%Y%m%d_%H%M%S")
                ss_path = f"{SCREENSHOT_DIR}/e2e_v5_newpage_{timestamp}.png"
                with open(ss_path, "wb") as f:
                    f.write(base64.b64decode(img_data))
                print(f"  截图保存: {ss_path}")
                check("CDP 截图 (新页面)", True)
        
        ws.close()

except urllib.error.URLError as e:
    print(f"  Chromium 调试端口 (9225) 未启动，跳过 CDP 测试")
    check("CDP 浏览器测试", True, "跳过 (Chromium 未运行)")
except Exception as e:
    check("CDP 浏览器测试", False, str(e)[:100])
    print(f"  CDP ERROR: {e}")

# ============================================================
# Part H: WebSocket 实时聊天测试
# ============================================================
print("\n" + "=" * 70)
print("Part H: WebSocket 实时聊天测试")
print("=" * 70)

try:
    import websocket as ws_lib
    
    # Create a new session for WS test
    req = urllib.request.Request(
        f"{BASE}/sessions",
        data=json.dumps({"user_id": user_id, "title": "E2E v5 WS测试会话"}).encode(),
        headers=AUTH,
        method="POST"
    )
    resp = urllib.request.urlopen(req, timeout=10)
    ws_session_data = json.loads(resp.read())
    ws_session_id = ws_session_data.get("session_id", ws_session_data.get("id", ""))
    print(f"  WS Session: {ws_session_id}")
    
    ws_chat_url = f"ws://localhost:8080/ws/chat?token={token}&session_id={ws_session_id}"
    sock = ws_lib.create_connection(ws_chat_url, timeout=10)
    print(f"  WebSocket 已连接")
    check("WebSocket 连接", True)
    
    # H1: Send a simple message via WebSocket
    print("\n--- H1: WS 发送简单消息 ---")
    ws_msg = json.dumps({
        "type": "message",
        "content": "你好，昔涟",
        "session_id": ws_session_id,
        "timestamp": int(time.time() * 1000)
    })
    sock.send(ws_msg)
    print("  已发送: 你好，昔涟")
    
    sock.settimeout(15)
    responses = []
    start = time.time()
    while time.time() - start < 15:
        try:
            msg = sock.recv()
            data = json.loads(msg)
            msg_type = data.get("type", "?")
            content = str(data.get("content", ""))[:100]
            responses.append(f"[{msg_type}] {content}")
            if msg_type in ("stream_end", "error"):
                break
            if msg_type == "response":
                break
        except:
            break
    
    print(f"  收到 {len(responses)} 条 WS 消息:")
    for r in responses[:10]:
        print(f"    {r}")
    
    has_ws_response = len(responses) > 0
    check("WS 收到回复", has_ws_response, f"共 {len(responses)} 条")
    
    # H2: Send IoT command via WebSocket
    print("\n--- H2: WS 发送 IoT 命令 ---")
    ws_iot = json.dumps({
        "type": "message",
        "content": "帮我查询客厅灯的状态",
        "session_id": ws_session_id,
        "timestamp": int(time.time() * 1000)
    })
    sock.send(ws_iot)
    print("  已发送: 帮我查询客厅灯的状态")
    
    sock.settimeout(20)
    iot_responses = []
    start = time.time()
    while time.time() - start < 20:
        try:
            msg = sock.recv()
            data = json.loads(msg)
            msg_type = data.get("type", "?")
            content = str(data.get("content", ""))[:100]
            iot_responses.append(f"[{msg_type}] {content}")
            if msg_type in ("stream_end", "error"):
                break
        except:
            break
    
    print(f"  收到 {len(iot_responses)} 条 WS IoT 响应:")
    for r in iot_responses[:10]:
        print(f"    {r}")
    
    check("WS IoT 响应", len(iot_responses) > 0)
    
    sock.close()
    print("  WebSocket 已断开")

except Exception as e:
    check("WebSocket 聊天测试", False, str(e)[:100])
    print(f"  WS ERROR: {e}")

# ============================================================
# Part H-extra: 消息持久化验证 (查询 WS 会话的消息)
# ============================================================
print("\n" + "=" * 70)
print("Part H-extra: 消息持久化验证 (WS 会话)")
print("=" * 70)

try:
    req = urllib.request.Request(
        f"{BASE}/sessions/{ws_session_id}/messages?limit=20",
        headers=AUTH
    )
    resp = urllib.request.urlopen(req, timeout=10)
    msg_data = json.loads(resp.read())
    messages = msg_data.get("messages", msg_data.get("data", []))
    print(f"  消息历史: {len(messages)} 条")
    for m in messages[:5]:
        role = m.get("role", "?")
        content = str(m.get("content", ""))[:80]
        print(f"    [{role}] {content}")
    
    check("消息历史可查询", len(messages) > 0, f"共 {len(messages)} 条")
    check("消息数量合理", len(messages) >= 2, f"共 {len(messages)} 条")
except Exception as e:
    check("消息持久化", False, str(e)[:80])

# ============================================================
# 检查服务日志
# ============================================================
print("\n" + "=" * 70)
print("Part I: 服务日志摘要")
print("=" * 70)

LOG_DIR = "/home/aska/Code/Cyrene/ethend/logs"
log_files = {
    "ai-core": f"{LOG_DIR}/ai-core.log",
    "gateway": f"{LOG_DIR}/gateway.log",
    "tool-engine": f"{LOG_DIR}/tool-engine.log",
    "iot-debug": f"{LOG_DIR}/iot-debug-service.log",
    "memory-service": f"{LOG_DIR}/memory-service.log",
}

for name, path in log_files.items():
    try:
        size = os.path.getsize(path)
        print(f"  {name}: {size} bytes")
        if size > 0:
            with open(path, "r") as f:
                lines = f.readlines()
                # Show last 5 non-empty lines
                non_empty = [l.strip() for l in lines if l.strip()]
                for l in non_empty[-5:]:
                    print(f"    {l[:150]}")
        else:
            print(f"    (空文件)")
    except Exception as e:
        print(f"  {name}: 无法读取 - {e}")

# ============================================================
# Summary
# ============================================================
print("\n" + "=" * 70)
print(f"测试完成: {passed} 通过, {failed} 失败, 总计 {passed+failed}")
print("=" * 70)

# Save results
result_path = f"{SCREENSHOT_DIR}/e2e_v5_results_{time.strftime('%Y%m%d_%H%M%S')}.json"
with open(result_path, "w") as f:
    json.dump({
        "passed": passed,
        "failed": failed,
        "total": passed + failed,
        "details": test_details,
        "timestamp": time.time()
    }, f, ensure_ascii=False, indent=2)
print(f"结果已保存: {result_path}")

sys.exit(0 if failed == 0 else 1)
