"""
Hook 模块管理 API 集成测试 (H32)
测试 hook_module_service 的 CRUD、脚本管理和事件总线。
使用临时目录，不依赖 MongoDB 或真实文件。
"""

import sys
import os
import json
import asyncio
import tempfile
import unittest
from pathlib import Path
from unittest.mock import MagicMock, AsyncMock

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))

from services.hook_module_service import HookModuleService


def run_async(coro):
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = None
    if loop and loop.is_running():
        import concurrent.futures
        with concurrent.futures.ThreadPoolExecutor() as pool:
            return pool.submit(asyncio.run, coro).result()
    return asyncio.run(coro)


class TestHookModuleServiceCRUD(unittest.TestCase):
    """模块 CRUD 测试"""

    def setUp(self):
        self.svc = HookModuleService()
        self.tmpdir = tempfile.mkdtemp()
        self.svc.data_dir = Path(self.tmpdir) / "hook"
        self.svc.scripts_dir = self.svc.data_dir / "scripts"
        self.svc.modules_file = self.svc.data_dir / "modules.json"
        self.svc.events_file = self.svc.data_dir / "events.jsonl"
        self.svc.device_state_file = self.svc.data_dir / "device_modules.json"
        self.svc._ensure_store()

    def test_create_module(self):
        module = run_async(self.svc.upsert_module({
            "module_id": "wechat_v2",
            "name": "微信Hook",
            "version": "2.1.0",
            "description": "完整微信Hook模块",
            "scopes": ["com.tencent.mm"],
            "capabilities": ["send_message", "get_contacts"],
        }))
        self.assertEqual(module["module_id"], "wechat_v2")
        self.assertEqual(module["version"], "2.1.0")
        self.assertTrue(module["enabled"])

    def test_get_module(self):
        run_async(self.svc.upsert_module({"module_id": "test1", "name": "Test"}))
        result = run_async(self.svc.get_module("test1"))
        self.assertIsNotNone(result)
        self.assertEqual(result["name"], "Test")

    def test_get_nonexistent_module(self):
        result = run_async(self.svc.get_module("nonexistent"))
        self.assertIsNone(result)

    def test_list_modules(self):
        run_async(self.svc.upsert_module({"module_id": "m1", "name": "M1", "enabled": True}))
        run_async(self.svc.upsert_module({"module_id": "m2", "name": "M2", "enabled": False}))
        all_modules = run_async(self.svc.list_modules())
        self.assertEqual(len(all_modules), 2)
        enabled = run_async(self.svc.list_modules(enabled=True))
        self.assertEqual(len(enabled), 1)
        self.assertEqual(enabled[0]["module_id"], "m1")

    def test_list_modules_by_scope(self):
        run_async(self.svc.upsert_module({"module_id": "wc", "name": "WC", "scopes": ["com.tencent.mm"]}))
        run_async(self.svc.upsert_module({"module_id": "dy", "name": "DY", "scopes": ["com.ss.android.ugc.aweme"]}))
        wechat = run_async(self.svc.list_modules(scope="com.tencent.mm"))
        self.assertEqual(len(wechat), 1)
        self.assertEqual(wechat[0]["module_id"], "wc")

    def test_update_module(self):
        run_async(self.svc.upsert_module({"module_id": "upd", "name": "V1", "version": "1.0.0"}))
        updated = run_async(self.svc.upsert_module({"module_id": "upd", "name": "V2", "version": "2.0.0"}))
        self.assertEqual(updated["name"], "V2")
        self.assertEqual(updated["version"], "2.0.0")

    def test_delete_module(self):
        run_async(self.svc.upsert_module({"module_id": "del", "name": "Del"}))
        ok = run_async(self.svc.delete_module("del"))
        self.assertTrue(ok)
        result = run_async(self.svc.get_module("del"))
        self.assertIsNone(result)

    def test_delete_nonexistent(self):
        ok = run_async(self.svc.delete_module("nope"))
        self.assertFalse(ok)

    def test_set_scope(self):
        run_async(self.svc.upsert_module({"module_id": "sc", "name": "SC", "scopes": ["a"]}))
        updated = run_async(self.svc.set_scope("sc", ["a", "b", "c"]))
        self.assertEqual(updated["scopes"], ["a", "b", "c"])

    def test_set_enabled(self):
        run_async(self.svc.upsert_module({"module_id": "en", "name": "EN", "enabled": True}))
        updated = run_async(self.svc.set_enabled("en", False))
        self.assertFalse(updated["enabled"])


class TestHookModuleServiceScripts(unittest.TestCase):
    """脚本管理测试"""

    def setUp(self):
        self.svc = HookModuleService()
        self.tmpdir = tempfile.mkdtemp()
        self.svc.data_dir = Path(self.tmpdir) / "hook"
        self.svc.scripts_dir = self.svc.data_dir / "scripts"
        self.svc.modules_file = self.svc.data_dir / "modules.json"
        self.svc.events_file = self.svc.data_dir / "events.jsonl"
        self.svc.device_state_file = self.svc.data_dir / "device_modules.json"
        self.svc._ensure_store()

    def test_save_and_list_scripts(self):
        content = b"console.log('hello');"
        saved = run_async(self.svc.save_script("test_script", content))
        self.assertEqual(saved["script_id"], "test_script")
        self.assertIn("sha256:", saved["hash"])
        scripts = run_async(self.svc.list_scripts())
        self.assertEqual(len(scripts), 1)
        self.assertEqual(scripts[0]["script_id"], "test_script")

    def test_get_script_path(self):
        run_async(self.svc.save_script("s1", b"var x = 1;"))
        path = run_async(self.svc.get_script_path("s1"))
        self.assertIsNotNone(path)
        self.assertTrue(path.exists())
        none_path = run_async(self.svc.get_script_path("nonexistent"))
        self.assertIsNone(none_path)


class TestHookModuleServiceEvents(unittest.TestCase):
    """事件总线测试"""

    def setUp(self):
        self.svc = HookModuleService()
        self.tmpdir = tempfile.mkdtemp()
        self.svc.data_dir = Path(self.tmpdir) / "hook"
        self.svc.scripts_dir = self.svc.data_dir / "scripts"
        self.svc.modules_file = self.svc.data_dir / "modules.json"
        self.svc.events_file = self.svc.data_dir / "events.jsonl"
        self.svc.device_state_file = self.svc.data_dir / "device_modules.json"
        self.svc._ensure_store()

    def test_add_and_list_events(self):
        run_async(self.svc.add_event({
            "event_type": "message_received",
            "device_id": "dev1",
            "platform": "wechat",
            "payload": {"content": "hello"},
        }))
        run_async(self.svc.add_event({
            "event_type": "friend_request",
            "device_id": "dev1",
            "platform": "wechat",
        }))
        all_events = run_async(self.svc.list_events())
        self.assertEqual(len(all_events), 2)

    def test_filter_events_by_type(self):
        run_async(self.svc.add_event({"event_type": "msg", "device_id": "d1"}))
        run_async(self.svc.add_event({"event_type": "friend", "device_id": "d1"}))
        msgs = run_async(self.svc.list_events(event_type="msg"))
        self.assertEqual(len(msgs), 1)

    def test_filter_events_by_device(self):
        run_async(self.svc.add_event({"event_type": "a", "device_id": "d1"}))
        run_async(self.svc.add_event({"event_type": "b", "device_id": "d2"}))
        d1_events = run_async(self.svc.list_events(device_id="d1"))
        self.assertEqual(len(d1_events), 1)

    def test_event_limit(self):
        for i in range(10):
            run_async(self.svc.add_event({"event_type": f"ev_{i}", "device_id": "d"}))
        limited = run_async(self.svc.list_events(limit=3))
        self.assertEqual(len(limited), 3)


class TestHookModuleServiceDeviceState(unittest.TestCase):
    """设备模块状态测试"""

    def setUp(self):
        self.svc = HookModuleService()
        self.tmpdir = tempfile.mkdtemp()
        self.svc.data_dir = Path(self.tmpdir) / "hook"
        self.svc.scripts_dir = self.svc.data_dir / "scripts"
        self.svc.modules_file = self.svc.data_dir / "modules.json"
        self.svc.events_file = self.svc.data_dir / "events.jsonl"
        self.svc.device_state_file = self.svc.data_dir / "device_modules.json"
        self.svc._ensure_store()

    def test_update_device_probe(self):
        run_async(self.svc.update_device_probe("dev1", {
            "supports_hook": True,
            "root_status": True,
            "frida_version": "16.2.1",
        }))
        state = run_async(self.svc.get_device_modules("dev1"))
        self.assertTrue(state["supports_hook"])
        self.assertEqual(state["frida_version"], "16.2.1")

    def test_deploy_script_to_device(self):
        result = run_async(self.svc.deploy_script("wechat_v2", ["dev1", "dev2"]))
        self.assertEqual(len(result["deployed"]), 2)
        state = run_async(self.svc.get_device_modules("dev1"))
        self.assertEqual(len(state["modules"]), 1)
        self.assertEqual(state["modules"][0]["module_id"], "wechat_v2")

    def test_reload_device_modules(self):
        run_async(self.svc.deploy_script("m1", ["dev1"]))
        result = run_async(self.svc.reload_device_modules("dev1", ["m1"]))
        self.assertEqual(result["reloaded"], ["m1"])

    def test_device_logs(self):
        run_async(self.svc.add_device_log("dev1", "m1", "Hook loaded"))
        run_async(self.svc.add_device_log("dev1", "m1", "Message intercepted"))
        logs = run_async(self.svc.get_device_logs("dev1", "m1"))
        self.assertEqual(len(logs), 2)


if __name__ == "__main__":
    unittest.main()
