"""
API模块

提供API路由和处理函数
"""

from fastapi import APIRouter, WebSocket, WebSocketDisconnect
import asyncio

import yaml

from unilabos.app.web.controller import (
    devices,
    job_add,
    job_info,
    get_online_devices,
    get_device_actions,
    get_action_schema,
    get_all_available_actions,
)
from unilabos.app.model import (
    Resp,
    RespCode,
    JobStatusResp,
    JobAddResp,
    JobAddReq,
    JobData,
)
from unilabos.app.web.utils.host_utils import get_host_node_info
from unilabos.registry.registry import lab_registry
from unilabos.utils.type_check import NoAliasDumper

# 创建API路由器
api = APIRouter()
admin = APIRouter()

# 存储所有活动的WebSocket连接
active_connections: set[WebSocket] = set()
# 存储注册表编辑器的WebSocket连接
registry_editor_connections: set[WebSocket] = set()
# 存储状态页面的WebSocket连接
status_page_connections: set[WebSocket] = set()

# 状态跟踪变量，用于差异检测
_static_data_sent_connections: set[WebSocket] = set()
_previous_host_node_info: dict = {}
_previous_local_devices: dict = {}


def compute_host_node_diff(current: dict, previous: dict) -> dict:
    """计算主机节点信息的差异，只返回有变化的部分"""
    diff = {}

    # 检查可用性变化
    if current.get("available") != previous.get("available"):
        diff["available"] = current.get("available")

    # 检查设备列表变化
    current_devices = current.get("devices", {})
    previous_devices = previous.get("devices", {})
    if current_devices != previous_devices:
        diff["devices"] = current_devices

    # 检查动作客户端变化
    current_action_clients = current.get("action_clients", {})
    previous_action_clients = previous.get("action_clients", {})
    if current_action_clients != previous_action_clients:
        diff["action_clients"] = current_action_clients

    # 检查订阅主题变化
    current_topics = current.get("subscribed_topics", [])
    previous_topics = previous.get("subscribed_topics", [])
    if current_topics != previous_topics:
        diff["subscribed_topics"] = current_topics

    # 设备状态始终包含（因为需要实时更新）
    if "device_status" in current:
        diff["device_status"] = current["device_status"]
        diff["device_status_timestamps"] = current.get("device_status_timestamps", {})

    return diff


async def broadcast_device_status():
    """广播设备状态到所有连接的客户端"""
    while True:
        try:
            # 获取最新的设备状态
            host_info = get_host_node_info()
            if host_info["available"]:
                # 准备要发送的数据
                status_data = {
                    "type": "device_status",
                    "data": {
                        "device_status": host_info["device_status"],
                        "device_status_timestamps": host_info["device_status_timestamps"],
                    },
                }
                # 发送到所有连接的客户端
                for connection in active_connections:
                    try:
                        await connection.send_json(status_data)
                    except Exception as e:
                        print(f"Error sending to client: {e}")
                        active_connections.remove(connection)
            await asyncio.sleep(1)  # 每秒更新一次
        except Exception as e:
            print(f"Error in broadcast: {e}")
            await asyncio.sleep(1)


async def broadcast_status_page_data():
    """广播状态页面数据到所有连接的客户端（优化版：增量更新）"""
    global _previous_local_devices, _static_data_sent_connections, _previous_host_node_info

    while True:
        try:
            if status_page_connections:
                from unilabos.app.web.utils.host_utils import get_host_node_info
                from unilabos.app.web.utils.ros_utils import get_ros_node_info
                from unilabos.app.web.utils.device_utils import get_registry_info
                from unilabos.config.config import BasicConfig
                from unilabos.registry.registry import lab_registry
                from unilabos.ros.msgs.message_converter import msg_converter_manager
                from unilabos.utils.type_check import TypeEncoder
                import json

                # 获取当前数据
                host_node_info = get_host_node_info()
                ros_node_info = get_ros_node_info()

                # 检查需要发送静态数据的新连接
                new_connections = status_page_connections - _static_data_sent_connections

                # 向新连接发送静态数据（Device Types、Resource Types、Converter Modules）
                if new_connections:
                    devices = []
                    resources = []
                    modules = {"names": [], "classes": [], "displayed_count": 0, "total_count": 0}

                    if lab_registry:
                        devices = json.loads(
                            json.dumps(lab_registry.obtain_registry_device_info(), ensure_ascii=False, cls=TypeEncoder)
                        )
                        # 资源类型
                        for resource_id, resource_info in lab_registry.resource_type_registry.items():
                            resources.append(
                                {
                                    "id": resource_id,
                                    "name": resource_info.get("name", "未命名"),
                                    "file_path": resource_info.get("file_path", ""),
                                }
                            )

                    # 获取导入的模块
                    if msg_converter_manager:
                        modules["names"] = msg_converter_manager.list_modules()
                        all_classes = [i for i in msg_converter_manager.list_classes() if "." in i]
                        modules["total_count"] = len(all_classes)
                        modules["classes"] = all_classes

                    # 静态数据
                    registry_info = get_registry_info()
                    static_data = {
                        "type": "static_data_init",
                        "data": {
                            "devices": devices,
                            "resources": resources,
                            "modules": modules,
                            "registry_info": registry_info,
                            "is_host_mode": BasicConfig.is_host_mode,
                            "host_node_info": host_node_info,  # 添加主机节点初始信息
                            "ros_node_info": ros_node_info,  # 添加本地设备初始信息
                        },
                    }

                    # 发送到新连接
                    disconnected_new_connections = set()
                    for connection in new_connections:
                        try:
                            await connection.send_json(static_data)
                            _static_data_sent_connections.add(connection)
                        except Exception as e:
                            print(f"Error sending static data to new client: {e}")
                            disconnected_new_connections.add(connection)

                    # 清理断开的新连接
                    for conn in disconnected_new_connections:
                        status_page_connections.discard(conn)
                        _static_data_sent_connections.discard(conn)

                # 检查主机节点信息是否有变更
                host_node_diff = compute_host_node_diff(host_node_info, _previous_host_node_info)
                host_changed = bool(host_node_diff)

                # 检查Local Devices是否有变更
                current_devices = ros_node_info.get("registered_devices", {})
                devices_changed = current_devices != _previous_local_devices

                # 只有当有真正的变化时才发送更新
                if host_changed or devices_changed:
                    # 发送增量更新数据
                    update_data = {
                        "type": "incremental_update",
                        "data": {
                            "timestamp": asyncio.get_event_loop().time(),
                        },
                    }

                    # 只包含有变化的主机节点信息
                    if host_changed:
                        update_data["data"]["host_node_info"] = host_node_diff

                    # 如果Local Devices发生变更，添加到更新数据中
                    if devices_changed:
                        update_data["data"]["ros_node_info"] = ros_node_info
                        _previous_local_devices = current_devices.copy()

                    # 更新主机节点状态
                    if host_changed:
                        _previous_host_node_info = host_node_info.copy()

                    # 发送增量更新到所有连接
                    disconnected_connections = set()
                    for connection in status_page_connections:
                        try:
                            await connection.send_json(update_data)
                        except Exception as e:
                            print(f"Error sending incremental update to client: {e}")
                            disconnected_connections.add(connection)

                    # 清理断开的连接
                    for conn in disconnected_connections:
                        status_page_connections.discard(conn)
                        _static_data_sent_connections.discard(conn)

            await asyncio.sleep(1)  # 每秒检查一次更新
        except Exception as e:
            print(f"Error in status page broadcast: {e}")
            await asyncio.sleep(1)


@api.websocket("/ws/device_status")
async def websocket_device_status(websocket: WebSocket):
    """WebSocket端点，用于实时获取设备状态"""
    await websocket.accept()
    active_connections.add(websocket)
    try:
        while True:
            # 保持连接活跃
            await websocket.receive_text()
    except WebSocketDisconnect:
        active_connections.remove(websocket)
    except Exception as e:
        print(f"WebSocket error: {e}")
        active_connections.remove(websocket)


@api.websocket("/ws/registry_editor")
async def websocket_registry_editor(websocket: WebSocket):
    """WebSocket端点，用于注册表编辑器"""
    await websocket.accept()
    registry_editor_connections.add(websocket)

    try:
        while True:
            # 接收来自客户端的消息
            message = await websocket.receive_text()
            import json

            data = json.loads(message)

            if data.get("type") == "import_file":
                await handle_file_import(websocket, data["data"])
            elif data.get("type") == "analyze_file":
                await handle_file_analysis(websocket, data["data"])
            elif data.get("type") == "analyze_file_content":
                await handle_file_content_analysis(websocket, data["data"])
            elif data.get("type") == "import_file_content":
                await handle_file_content_import(websocket, data["data"])

    except WebSocketDisconnect:
        registry_editor_connections.remove(websocket)
    except Exception as e:
        print(f"Registry Editor WebSocket error: {e}")
        if websocket in registry_editor_connections:
            registry_editor_connections.remove(websocket)


@api.websocket("/ws/status_page")
async def websocket_status_page(websocket: WebSocket):
    """WebSocket端点，用于状态页面实时数据更新"""
    await websocket.accept()
    status_page_connections.add(websocket)

    try:
        while True:
            # 接收来自客户端的消息（用于保持连接活跃）
            message = await websocket.receive_text()
            # 状态页面通常只需要接收数据，不需要发送复杂指令

    except WebSocketDisconnect:
        status_page_connections.remove(websocket)
    except Exception as e:
        print(f"Status Page WebSocket error: {e}")
        if websocket in status_page_connections:
            status_page_connections.remove(websocket)


async def handle_file_analysis(websocket: WebSocket, request_data: dict):
    """处理文件分析请求，获取文件中的类列表"""
    import json
    import os
    import sys
    import inspect
    import traceback
    from pathlib import Path
    from unilabos.config.config import BasicConfig

    file_path = request_data.get("file_path")

    async def send_log(message: str, level: str = "info"):
        """发送日志消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "log", "message": message, "level": level}))
        except Exception as e:
            print(f"Failed to send log: {e}")

    async def send_analysis_result(result_data: dict):
        """发送分析结果到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "file_analysis_result", "data": result_data}))
        except Exception as e:
            print(f"Failed to send analysis result: {e}")

    try:
        # 验证文件路径参数
        if not file_path:
            await send_analysis_result({"success": False, "error": "文件路径为空", "file_path": ""})
            return

        # 获取工作目录并构建完整路径
        working_dir_str = getattr(BasicConfig, "working_dir", None) or os.getcwd()
        working_dir = Path(working_dir_str)
        full_file_path = working_dir / file_path

        # 验证文件路径
        if not full_file_path.exists():
            await send_analysis_result(
                {"success": False, "error": f"文件路径不存在: {file_path}", "file_path": file_path}
            )
            return

        await send_log(f"开始分析文件: {file_path}")

        # 验证文件是Python文件
        if not file_path.endswith(".py"):
            await send_analysis_result({"success": False, "error": "请选择Python文件 (.py)", "file_path": file_path})
            return

        full_file_path = full_file_path.absolute()
        await send_log(f"文件绝对路径: {full_file_path}")

        # 添加文件目录到sys.path
        file_dir = str(full_file_path.parent)
        if file_dir not in sys.path:
            sys.path.insert(0, file_dir)
            await send_log(f"已添加路径到sys.path: {file_dir}")

        # 确定模块名
        module_name = full_file_path.stem
        await send_log(f"使用模块名: {module_name}")

        # 导入模块进行分析
        try:
            # 如果模块已经导入，先删除以便重新导入
            if module_name in sys.modules:
                del sys.modules[module_name]
                await send_log(f"已删除旧模块: {module_name}")

            import importlib.util

            spec = importlib.util.spec_from_file_location(module_name, full_file_path)
            if spec is None or spec.loader is None:
                await send_analysis_result(
                    {"success": False, "error": "无法创建模块规范", "file_path": str(full_file_path)}
                )
                return

            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module
            spec.loader.exec_module(module)

            await send_log(f"成功导入模块用于分析: {module_name}")

        except Exception as e:
            await send_analysis_result(
                {"success": False, "error": f"导入模块失败: {str(e)}", "file_path": str(full_file_path)}
            )
            return

        # 分析模块中的类
        classes = []
        for name in dir(module):
            try:
                obj = getattr(module, name)
                if isinstance(obj, type) and obj.__module__ == module_name:
                    # 获取类的文档字符串
                    docstring = inspect.getdoc(obj) or ""
                    # 只取第一行作为简短描述
                    short_desc = docstring.split("\n")[0] if docstring else ""

                    classes.append({"name": name, "docstring": short_desc, "full_docstring": docstring})
            except Exception as e:
                await send_log(f"分析类 {name} 时出错: {str(e)}", "warning")
                continue

        if not classes:
            await send_analysis_result(
                {
                    "success": False,
                    "error": "模块中未找到任何类定义",
                    "file_path": str(full_file_path),
                    "module_name": module_name,
                }
            )
            return

        await send_log(f"找到 {len(classes)} 个类: {[cls['name'] for cls in classes]}")

        # 发送分析结果
        await send_analysis_result(
            {"success": True, "file_path": str(full_file_path), "module_name": module_name, "classes": classes}
        )

    except Exception as e:
        await send_analysis_result(
            {
                "success": False,
                "error": f"分析过程中发生错误: {str(e)}",
                "file_path": file_path,
                "traceback": traceback.format_exc(),
            }
        )


async def handle_file_content_analysis(websocket: WebSocket, request_data: dict):
    """处理文件内容分析请求，直接分析上传的文件内容"""
    import json
    import os
    import sys
    import inspect
    import traceback
    import tempfile
    from pathlib import Path

    file_name = request_data.get("file_name")
    file_content = request_data.get("file_content")
    file_size = request_data.get("file_size", 0)

    async def send_log(message: str, level: str = "info"):
        """发送日志消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "log", "message": message, "level": level}))
        except Exception as e:
            print(f"Failed to send log: {e}")

    async def send_analysis_result(result_data: dict):
        """发送分析结果到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "file_analysis_result", "data": result_data}))
        except Exception as e:
            print(f"Failed to send analysis result: {e}")

    try:
        # 验证文件内容
        if not file_name or not file_content:
            await send_analysis_result({"success": False, "error": "文件名或文件内容为空", "file_name": file_name})
            return

        await send_log(f"开始分析文件: {file_name} ({file_size} 字节)")

        # 验证文件是Python文件
        if not file_name.endswith(".py"):
            await send_analysis_result({"success": False, "error": "请选择Python文件 (.py)", "file_name": file_name})
            return

        # 创建临时文件
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, encoding="utf-8") as temp_file:
            temp_file.write(file_content)
            temp_file_path = temp_file.name

        await send_log(f"创建临时文件: {temp_file_path}")

        try:
            # 添加临时文件目录到sys.path
            temp_dir = str(Path(temp_file_path).parent)
            if temp_dir not in sys.path:
                sys.path.insert(0, temp_dir)
                await send_log(f"已添加临时目录到sys.path: {temp_dir}")

            # 确定模块名（去掉.py扩展名）
            module_name = file_name.replace(".py", "").replace("-", "_").replace(" ", "_")
            await send_log(f"使用模块名: {module_name}")

            # 导入模块进行分析
            try:
                # 如果模块已经导入，先删除以便重新导入
                if module_name in sys.modules:
                    del sys.modules[module_name]
                    await send_log(f"已删除旧模块: {module_name}")

                import importlib.util

                spec = importlib.util.spec_from_file_location(module_name, temp_file_path)
                if spec is None or spec.loader is None:
                    raise Exception("无法创建模块规范")

                module = importlib.util.module_from_spec(spec)
                sys.modules[module_name] = module
                spec.loader.exec_module(module)

                await send_log(f"成功导入模块用于分析: {module_name}")

            except Exception as e:
                await send_analysis_result(
                    {"success": False, "error": f"导入模块失败: {str(e)}", "file_name": file_name}
                )
                return

            # 分析模块中的类
            classes = []
            for name in dir(module):
                try:
                    obj = getattr(module, name)
                    if isinstance(obj, type) and obj.__module__ == module_name:
                        # 获取类的文档字符串
                        docstring = inspect.getdoc(obj) or ""
                        # 只取第一行作为简短描述
                        short_desc = docstring.split("\n")[0] if docstring else "无描述"

                        classes.append({"name": name, "docstring": short_desc, "full_docstring": docstring})
                except Exception as e:
                    await send_log(f"分析类 {name} 时出错: {str(e)}", "warning")
                    continue

            if not classes:
                await send_analysis_result(
                    {
                        "success": False,
                        "error": "模块中未找到任何类定义",
                        "file_name": file_name,
                        "module_name": module_name,
                    }
                )
                return

            await send_log(f"找到 {len(classes)} 个类: {[cls['name'] for cls in classes]}")

            # 发送分析结果
            await send_analysis_result(
                {
                    "success": True,
                    "file_name": file_name,
                    "module_name": module_name,
                    "classes": classes,
                    "temp_file_path": temp_file_path,  # 保存临时文件路径供后续使用
                }
            )

        finally:
            # 清理临时文件（在导入完成后再删除）
            try:
                if os.path.exists(temp_file_path):
                    # 延迟删除，给导入操作留出时间
                    import threading

                    def delayed_cleanup():
                        import time

                        time.sleep(60)  # 等待60秒后删除
                        try:
                            os.unlink(temp_file_path)
                        except OSError:
                            pass

                    threading.Thread(target=delayed_cleanup, daemon=True).start()
            except Exception as e:
                await send_log(f"清理临时文件时出错: {str(e)}", "warning")

    except Exception as e:
        await send_analysis_result(
            {
                "success": False,
                "error": f"分析过程中发生错误: {str(e)}",
                "file_name": file_name,
                "traceback": traceback.format_exc(),
            }
        )


async def handle_file_content_import(websocket: WebSocket, request_data: dict):
    """处理基于文件内容的导入请求"""
    import json
    import os
    import sys
    import traceback
    import tempfile
    from pathlib import Path

    file_name = request_data.get("file_name")
    file_content = request_data.get("file_content")
    file_size = request_data.get("file_size", 0)
    registry_type = request_data.get("registry_type", "device")
    class_name = request_data.get("class_name")
    module_prefix = request_data.get("module_prefix", "")

    async def send_log(message: str, level: str = "info"):
        """发送日志消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "log", "message": message, "level": level}))
        except Exception as e:
            print(f"Failed to send log: {e}")

    async def send_progress(message: str):
        """发送进度消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "progress", "message": message}))
        except Exception as e:
            print(f"Failed to send progress: {e}")

    async def send_error(message: str):
        """发送错误消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "error", "message": message}))
        except Exception as e:
            print(f"Failed to send error: {e}")

    async def send_result(result_data: dict):
        """发送结果数据到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "result", "data": result_data}))
        except Exception as e:
            print(f"Failed to send result: {e}")

    try:
        # 验证输入参数
        if not file_name or not file_content or not class_name:
            await send_error("文件名、文件内容或类名为空")
            return

        await send_log(f"开始处理文件: {file_name} ({file_size} 字节)")
        await send_progress("正在创建临时文件...")

        # 创建临时文件
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, encoding="utf-8") as temp_file:
            temp_file.write(file_content)
            temp_file_path = temp_file.name

        await send_log(f"创建临时文件: {temp_file_path}")

        # 添加临时文件目录到sys.path
        temp_dir = str(Path(temp_file_path).parent)
        if temp_dir not in sys.path:
            sys.path.insert(0, temp_dir)
            await send_log(f"已添加临时目录到sys.path: {temp_dir}")

        # 确定模块名
        module_name = file_name.replace(".py", "").replace("-", "_").replace(" ", "_")

        # 如果有 module_prefix，则使用完整的模块路径
        full_module_name = f"{module_prefix}.{module_name}" if module_prefix else module_name
        await send_log(f"使用模块名: {module_name}")
        if module_prefix:
            await send_log(f"完整模块路径: {full_module_name}")

        # 导入模块
        try:
            # 如果模块已经导入，先删除以便重新导入
            if module_name in sys.modules:
                del sys.modules[module_name]
                await send_log(f"已删除旧模块: {module_name}")

            import importlib.util

            spec = importlib.util.spec_from_file_location(module_name, temp_file_path)
            if spec is None or spec.loader is None:
                await send_error("无法创建模块规范")
                return

            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module
            spec.loader.exec_module(module)

            await send_log(f"成功导入模块: {module_name}")

        except Exception as e:
            await send_error(f"导入模块失败: {str(e)}")
            return

        # 验证类存在
        if not hasattr(module, class_name):
            await send_error(f"模块中未找到类: {class_name}")
            return

        target_class = getattr(module, class_name)
        await send_log(f"找到目标类: {class_name}")

        # 使用registry.py的增强类信息功能进行分析
        await send_progress("正在生成注册表信息...")

        try:
            from unilabos.utils.import_manager import get_enhanced_class_info

            # 分析类信息
            enhanced_info = get_enhanced_class_info(f"{full_module_name}:{class_name}", use_dynamic=True)

            if not enhanced_info.get("dynamic_import_success", False):
                await send_error("动态导入类信息失败")
                return

            await send_log("成功分析类信息")

            # 根据注册表类型生成不同的schema
            if registry_type == "resource":
                # 资源类型的简单结构
                category_name = file_name.replace(".py", "") if file_name else "unknown"
                registry_schema = {
                    "description": enhanced_info.get("class_docstring", ""),
                    "category": [category_name],
                    "class": {
                        "module": f"{full_module_name}:{class_name}",
                        "type": "python",
                    },
                    "handles": [],
                    "icon": "",
                    "init_param_schema": {},
                    "registry_type": "resource",
                    "version": "1.0.0",
                    "file_path": f"uploaded_file://{file_name}",
                }
            else:
                # 设备类型的复杂结构
                registry_schema = {
                    "description": enhanced_info.get("class_docstring", ""),
                    "class": {
                        "module": f"{full_module_name}:{class_name}",
                        "type": "python",
                        "status_types": {k: v["return_type"] for k, v in enhanced_info["status_methods"].items()},
                        "action_value_mappings": {},
                    },
                    "version": "1.0.0",
                    "handles": [],
                    "init_param_schema": {},
                    "registry_type": "device",
                    "file_path": f"uploaded_file://{file_name}",
                }

                # 处理动作方法（仅对设备类型）
                for method_name, method_info in enhanced_info["action_methods"].items():
                    registry_schema["class"]["action_value_mappings"][f"auto-{method_name}"] = {
                        "type": "UniLabJsonCommandAsync" if method_info["is_async"] else "UniLabJsonCommand",
                        "goal": {},
                        "feedback": {},
                        "result": {},
                        "args": method_info["args"],
                        "description": method_info.get("docstring", ""),
                    }

            await send_log("成功生成注册表schema")

            # 格式化状态方法信息
            status_info = {}
            for status_name, status_data in enhanced_info.get("status_methods", {}).items():
                status_info[status_name] = {
                    "return_type": status_data.get("return_type", "未知类型"),
                    "docstring": status_data.get("docstring", "无描述"),
                    "is_property": status_data.get("is_property", False),
                }

            # 格式化动作方法信息
            action_info = {}
            for action_name, action_data in enhanced_info.get("action_methods", {}).items():
                args = action_data.get("args", [])
                action_info[action_name] = {
                    "param_count": len(args),
                    "params": [
                        {"name": arg.get("name", ""), "type": arg.get("type", ""), "default": arg.get("default")}
                        for arg in args
                    ],
                    "is_async": action_data.get("is_async", False),
                    "docstring": action_data.get("docstring", "无描述"),
                    "return_suggestion": "建议返回字典类型 (dict) 以便更好地结构化结果数据",
                }

            # 准备结果数据
            result = {
                "class_info": {
                    "class_name": class_name,
                    "module_name": module_name,
                    "module_prefix": module_prefix,
                    "full_module_name": full_module_name,
                    "file_name": file_name,
                    "file_size": file_size,
                    "docstring": enhanced_info.get("class_docstring", ""),
                    "dynamic_import_success": enhanced_info.get("dynamic_import_success", False),
                    "registry_type": registry_type,
                },
                "registry_schema": registry_schema,
                "class_analysis": {
                    "status_methods": status_info,
                    "action_methods": action_info,
                    "init_params": enhanced_info.get("init_params", []),
                    "status_methods_count": len(status_info),
                    "action_methods_count": len(action_info),
                },
                # 保持向后兼容
                "action_methods": enhanced_info.get("action_methods", {}),
                "status_methods": enhanced_info.get("status_methods", {}),
            }

            # 发送结果
            await send_result(result)
            await send_log("分析完成")

        except Exception as e:
            await send_error(f"分析类信息时发生错误: {str(e)}")
            await send_log(f"详细错误信息: {traceback.format_exc()}")
            return

        finally:
            # 清理临时文件
            try:
                if os.path.exists(temp_file_path):
                    import threading

                    def delayed_cleanup():
                        import time

                        time.sleep(30)  # 等待30秒后删除
                        try:
                            os.unlink(temp_file_path)
                        except OSError:
                            pass

                    threading.Thread(target=delayed_cleanup, daemon=True).start()
            except Exception as e:
                await send_log(f"清理临时文件时出错: {str(e)}", "warning")

    except Exception as e:
        await send_error(f"处理过程中发生错误: {str(e)}")
        await send_log(f"详细错误信息: {traceback.format_exc()}")


async def handle_file_import(websocket: WebSocket, request_data: dict):
    """处理文件导入请求"""
    import json
    import os
    import sys
    import traceback
    from pathlib import Path
    from unilabos.config.config import BasicConfig

    file_path = request_data.get("file_path")
    registry_type = request_data.get("registry_type", "device")
    class_name = request_data.get("class_name")
    module_name = request_data.get("module_name")
    description = request_data.get("description", "")
    safe_class_name = request_data.get("safe_class_name", "")
    icon = request_data.get("icon", "")
    module_prefix = request_data.get("module_prefix", "")
    handles = request_data.get("handles", [])

    async def send_log(message: str, level: str = "info"):
        """发送日志消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "log", "message": message, "level": level}))
        except Exception as e:
            print(f"Failed to send log: {e}")

    async def send_progress(message: str):
        """发送进度消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "progress", "message": message}))
        except Exception as e:
            print(f"Failed to send progress: {e}")

    async def send_error(message: str):
        """发送错误消息到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "error", "message": message}))
        except Exception as e:
            print(f"Failed to send error: {e}")

    async def send_result(result_data: dict):
        """发送结果数据到客户端"""
        try:
            await websocket.send_text(json.dumps({"type": "result", "data": result_data}))
        except Exception as e:
            print(f"Failed to send result: {e}")

    try:
        # 验证文件路径参数
        if not file_path:
            await send_error("文件路径为空")
            return

        # 获取工作目录并构建完整路径
        working_dir_str = getattr(BasicConfig, "working_dir", None) or os.getcwd()
        working_dir = Path(working_dir_str)
        full_file_path = working_dir / file_path

        # 验证文件路径
        if not full_file_path.exists():
            await send_error(f"文件路径不存在: {file_path}")
            return

        await send_log(f"开始处理文件: {file_path}")
        await send_progress("正在验证文件...")

        # 验证文件是Python文件
        if not file_path.endswith(".py"):
            await send_error("请选择Python文件 (.py)")
            return

        full_file_path = full_file_path.absolute()
        await send_log(f"文件绝对路径: {full_file_path}")

        # 动态导入模块
        await send_progress("正在导入模块...")

        # 添加文件目录到sys.path
        file_dir = str(full_file_path.parent)
        if file_dir not in sys.path:
            sys.path.insert(0, file_dir)
            await send_log(f"已添加路径到sys.path: {file_dir}")

        # 确定模块名
        if not module_name:
            module_name = full_file_path.stem

        # 如果有 module_prefix，则使用完整的模块路径
        full_module_name = f"{module_prefix}.{module_name}" if module_prefix else module_name
        await send_log(f"使用模块名: {module_name}")
        if module_prefix:
            await send_log(f"完整模块路径: {full_module_name}")

        # 导入模块
        try:
            # 如果模块已经导入，先删除以便重新导入
            if module_name in sys.modules:
                del sys.modules[module_name]
                await send_log(f"已删除旧模块: {module_name}")

            import importlib.util

            spec = importlib.util.spec_from_file_location(module_name, full_file_path)
            if spec is None or spec.loader is None:
                await send_error("无法创建模块规范")
                return

            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module
            spec.loader.exec_module(module)

            await send_log(f"成功导入模块: {module_name}")

        except Exception as e:
            await send_error(f"导入模块失败: {str(e)}")
            return

        # 分析模块
        await send_progress("正在分析模块...")

        # 获取模块中的所有类
        classes = []
        for name in dir(module):
            obj = getattr(module, name)
            if isinstance(obj, type) and obj.__module__ == module_name:
                classes.append((name, obj))

        if not classes:
            await send_error("模块中未找到任何类定义")
            return

        await send_log(f"找到 {len(classes)} 个类: {[name for name, _ in classes]}")

        # 确定要分析的类
        target_class = None
        target_class_name = None

        if class_name:
            # 用户指定了类名
            for name, cls in classes:
                if name == class_name:
                    target_class = cls
                    target_class_name = name
                    break
            if not target_class:
                await send_error(f"未找到指定的类: {class_name}")
                return
        else:
            # 自动选择第一个类
            target_class_name, target_class = classes[0]
            await send_log(f"自动选择类: {target_class_name}")

        # 使用registry.py的增强类信息功能进行分析
        await send_progress("正在生成注册表信息...")

        try:
            from unilabos.utils.import_manager import get_enhanced_class_info

            # 分析类信息
            enhanced_info = get_enhanced_class_info(f"{full_module_name}:{target_class_name}", use_dynamic=True)

            if not enhanced_info.get("dynamic_import_success", False):
                await send_error("动态导入类信息失败")
                return

            await send_log("成功分析类信息")

            # 根据注册表类型生成不同的schema
            if registry_type == "resource":
                # 资源类型的简单结构
                category_name = Path(file_path).stem if file_path else "unknown"
                registry_schema = {
                    "description": description or enhanced_info.get("class_docstring", ""),
                    "category": [category_name],
                    "class": {
                        "module": f"{full_module_name}:{target_class_name}",
                        "type": "python",
                    },
                    "handles": handles,
                    "icon": icon,
                    "init_param_schema": {},
                    "registry_type": "resource",
                    "version": "1.0.0",
                }
            else:
                # 设备类型的复杂结构
                registry_schema = {
                    "description": description or enhanced_info.get("class_docstring", ""),
                    "class": {
                        "module": f"{full_module_name}:{target_class_name}",
                        "type": "python",
                        "status_types": {k: v["return_type"] for k, v in enhanced_info["status_methods"].items()},
                        "action_value_mappings": {
                            f"auto-{k}": {
                                "type": "UniLabJsonCommandAsync" if v["is_async"] else "UniLabJsonCommand",
                                "goal": {},
                                "feedback": {},
                                "result": {},
                                "schema": lab_registry._generate_unilab_json_command_schema(v["args"], k),
                                "goal_default": {i["name"]: i["default"] for i in v["args"]},
                                "handles": {},
                            }
                            # 不生成已配置action的动作
                            for k, v in enhanced_info["action_methods"].items()
                        },
                    },
                    "version": "1.0.0",
                    "handles": handles,
                    "icon": icon,
                    "init_param_schema": {
                        "config": lab_registry._generate_unilab_json_command_schema(
                            enhanced_info["init_params"], "__init__"
                        )["properties"]["goal"],
                        "data": lab_registry._generate_status_types_schema(enhanced_info["status_methods"]),
                    },
                    "registry_type": "device",
                }

            await send_log("成功生成注册表schema")

            # 创建最终的YAML配置（使用ID作为根键）
            if safe_class_name:
                item_id = safe_class_name
            else:
                class_name_safe = (target_class_name or "unknown").lower()
                if registry_type == "resource":
                    # 资源ID通常直接使用类名，不加后缀
                    item_id = class_name_safe
                else:
                    # 设备ID使用类名加_device后缀
                    item_id = f"{class_name_safe}_device"
            final_config = {item_id: registry_schema}

            yaml_content = yaml.dump(
                final_config, allow_unicode=True, default_flow_style=False, Dumper=NoAliasDumper, sort_keys=True
            )

            # 格式化状态方法信息
            status_info = {}
            for status_name, status_data in enhanced_info.get("status_methods", {}).items():
                status_info[status_name] = {
                    "return_type": status_data.get("return_type", "未知类型"),
                    "docstring": status_data.get("docstring", "无描述"),
                    "is_property": status_data.get("is_property", False),
                }

            # 格式化动作方法信息
            action_info = {}
            for action_name, action_data in enhanced_info.get("action_methods", {}).items():
                args = action_data.get("args", [])
                action_info[action_name] = {
                    "param_count": len(args),
                    "params": [
                        {"name": arg.get("name", ""), "type": arg.get("type", ""), "default": arg.get("default")}
                        for arg in args
                    ],
                    "is_async": action_data.get("is_async", False),
                    "docstring": action_data.get("docstring", "无描述"),
                    "return_suggestion": "建议返回字典类型 (dict) 以便更好地结构化结果数据",
                }

            # 准备结果数据（包含详细的类分析信息）
            result = {
                "registry_schema": yaml_content,
                "item_id": item_id,
                "registry_type": registry_type,
                "class_name": target_class_name,
                "module_name": module_name,
                "file_path": file_path,
                "config_params": {
                    "safe_class_name": safe_class_name or item_id,
                    "description": description,
                    "icon": icon,
                    "module_prefix": module_prefix,
                    "full_module_name": full_module_name,
                    "handles_count": len(handles),
                    "handles": handles,
                },
                "class_analysis": {
                    "class_docstring": enhanced_info.get("class_docstring", ""),
                    "status_methods": status_info,
                    "action_methods": action_info,
                    "init_params": enhanced_info.get("init_params", []),
                    "dynamic_import_success": enhanced_info.get("dynamic_import_success", False),
                },
            }

            # 发送结果
            await send_result(result)
            await send_log("注册表生成完成")

        except Exception as e:
            await send_error(f"分析类信息时发生错误: {str(e)}")
            await send_log(f"详细错误信息: {traceback.format_exc()}")
            return

    except Exception as e:
        await send_error(f"处理过程中发生错误: {str(e)}")
        await send_log(f"详细错误信息: {traceback.format_exc()}")


@api.get("/file-browser", summary="Browse files and directories", response_model=Resp)
def get_file_browser_data(path: str = ""):
    """获取文件浏览器数据"""
    import os
    from pathlib import Path
    from unilabos.config.config import BasicConfig

    try:
        # 获取工作目录
        working_dir_str = getattr(BasicConfig, "working_dir", None) or os.getcwd()
        working_dir = Path(working_dir_str)

        # 如果提供了相对路径，则在工作目录下查找
        if path:
            target_path = working_dir / path
        else:
            target_path = working_dir

        # 确保路径在工作目录内（安全检查）
        target_path = target_path.resolve()

        if not target_path.exists():
            return Resp(code=RespCode.ErrorInvalidReq, message=f"路径不存在: {path}")

        if not target_path.is_dir():
            return Resp(code=RespCode.ErrorInvalidReq, message=f"不是目录: {path}")

        # 获取目录内容
        items = []

        parent_path = target_path.parent
        items.append(
            {
                "name": "..",
                "type": "directory",
                "path": str(parent_path),
                "size": 0,
                "is_parent": True,
            }
        )

        # 获取子目录和文件
        try:
            for item in sorted(target_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())):
                item_type = "directory" if item.is_dir() else "file"

                item_info = {
                    "name": item.name,
                    "type": item_type,
                    "path": str(item),
                    "size": item.stat().st_size if item.is_file() else 0,
                    "is_python": item.suffix == ".py" if item.is_file() else False,
                    "is_parent": False,
                }
                items.append(item_info)
        except PermissionError:
            return Resp(code=RespCode.ErrorInvalidReq, message="无权限访问此目录")

        return Resp(
            data={
                "current_path": str(target_path),
                "working_dir": str(working_dir),
                "items": items,
            }
        )

    except Exception as e:
        return Resp(code=RespCode.ErrorInvalidReq, message=f"获取目录信息失败: {str(e)}")


@api.get("/resources", summary="Resource list", response_model=Resp)
def get_resources():
    """获取资源列表"""
    isok, data = devices()
    if not isok:
        return Resp(code=RespCode.ErrorHostNotInit, message=str(data))

    return Resp(data=dict(data))


@api.get("/devices", summary="Device list", response_model=Resp)
def get_devices():
    """获取设备列表"""
    isok, data = devices()
    if not isok:
        return Resp(code=RespCode.ErrorHostNotInit, message=str(data))

    return Resp(data=dict(data))


@api.get("/online-devices", summary="Online devices list", response_model=Resp)
def api_get_online_devices():
    """获取在线设备列表

    返回当前在线的设备列表，包含设备ID、命名空间、机器名等信息
    """
    isok, data = get_online_devices()
    if not isok:
        return Resp(code=RespCode.ErrorHostNotInit, message=data.get("error", "Unknown error"))

    return Resp(data=data)


@api.get("/devices/{device_id}/actions", summary="Device actions list", response_model=Resp)
def api_get_device_actions(device_id: str):
    """获取设备可用的动作列表

    Args:
        device_id: 设备ID

    返回指定设备的所有可用动作，包含动作名称、类型、是否繁忙等信息
    """
    isok, data = get_device_actions(device_id)
    if not isok:
        return Resp(code=RespCode.ErrorInvalidReq, message=data.get("error", "Unknown error"))

    return Resp(data=data)


@api.get("/devices/{device_id}/actions/{action_name}/schema", summary="Action schema", response_model=Resp)
def api_get_action_schema(device_id: str, action_name: str):
    """获取动作的Schema详情

    Args:
        device_id: 设备ID
        action_name: 动作名称

    返回动作的参数Schema、默认值、类型等详细信息
    """
    isok, data = get_action_schema(device_id, action_name)
    if not isok:
        return Resp(code=RespCode.ErrorInvalidReq, message=data.get("error", "Unknown error"))

    return Resp(data=data)


@api.get("/actions", summary="All available actions", response_model=Resp)
def api_get_all_actions():
    """获取所有设备的可用动作

    返回所有已注册设备的动作列表，包含设备信息和各动作的状态
    """
    isok, data = get_all_available_actions()
    if not isok:
        return Resp(code=RespCode.ErrorHostNotInit, message=data.get("error", "Unknown error"))

    return Resp(data=data)


@api.get("/job/{id}/status", summary="Job status", response_model=JobStatusResp)
def job_status(id: str):
    """获取任务状态"""
    data = job_info(id)
    return JobStatusResp(data=data)


@api.post("/job/add", summary="Create job", response_model=JobAddResp)
def post_job_add(req: JobAddReq):
    """创建任务"""
    # 检查必要参数：device_id 和 action
    if not req.device_id:
        return JobAddResp(
            data=JobData(jobId="", status=6),
            code=RespCode.ErrorInvalidReq,
            message="device_id is required",
        )

    action_name = req.data.get("action", req.action) if req.data else req.action
    if not action_name:
        return JobAddResp(
            data=JobData(jobId="", status=6),
            code=RespCode.ErrorInvalidReq,
            message="action is required",
        )

    data = job_add(req)
    return JobAddResp(data=data)


def setup_api_routes(app):
    """设置API路由"""
    app.include_router(admin, prefix="/admin/v1", tags=["admin"])
    app.include_router(api, prefix="/api/v1", tags=["api"])

    # 启动广播任务
    @app.on_event("startup")
    async def startup_event():
        asyncio.create_task(broadcast_device_status(), name="web-api-startup-device")
        asyncio.create_task(broadcast_status_page_data(), name="web-api-startup-status")
