#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
换机器人功能辅助函数模块
将switch_robot功能拆分为多个独立的辅助函数
"""

import json
import datetime
import copy
import uuid
from typing import Dict, List, Any, Tuple, Optional
from sqlalchemy import select

from data.models.blockrecord import VWEDBlockRecord
from data.models.tasklog import VWEDTaskLog
from utils.logger import get_logger

logger = get_logger("service.task_record_switch_robot_helpers")


async def terminate_running_blocks(session, task_record_id: str, switch_reason: str) -> Tuple[int, Dict]:
    """
    终止所有运行中的块记录

    Args:
        session: 数据库会话
        task_record_id: 任务记录ID
        switch_reason: 换机器人原因

    Returns:
        Tuple[int, Dict]: (终止的块数量, 已有块记录映射 - 使用block_name作为key)
    """
    from data.enum.task_block_record_enum import TaskBlockRecordStatus
    from sqlalchemy import insert

    blocks_query = await session.execute(
        select(VWEDBlockRecord)
        .where(VWEDBlockRecord.task_record_id == task_record_id)
    )
    block_records = blocks_query.scalars().all()

    # 创建已有块记录的映射 - 使用block_name作为key(而非block_id,因为block_id可能重复)
    existing_block_map = {block.block_name: block for block in block_records}

    terminated_count = 0
    for block in block_records:
        if block.status not in [TaskBlockRecordStatus.SUCCESS, TaskBlockRecordStatus.FAILED, TaskBlockRecordStatus.CANCELED]:
            block.status = TaskBlockRecordStatus.CANCELED
            block.ended_on = datetime.datetime.now()
            block.ended_reason = f"换机器人终止: {switch_reason}"
            terminated_count += 1

            # 创建日志记录
            log_id = str(uuid.uuid4())
            log_message = {
                "success": False,
                "message": f"换机器人终止: {switch_reason}",
                "output": {
                    "block_name": block.block_name,
                    "block_type": block.block_execute_name,
                    "terminated_at": block.ended_on.isoformat() if block.ended_on else None
                }
            }

            stmt = insert(VWEDTaskLog).values(
                id=log_id,
                level=3,
                message=json.dumps(log_message, ensure_ascii=False),
                task_block_id=block.block_name,
                task_id=block.task_id,
                task_record_id=task_record_id,
                block_record_id=block.id,
                log_type="block_execution"
            )
            await session.execute(stmt)

    logger.info(f"已终止任务 {task_record_id} 的 {terminated_count} 个未完成块记录")
    return terminated_count, existing_block_map


async def create_unexecuted_block_records(
    session,
    task_record_id: str,
    task_def_detail: Dict,
    existing_block_map: Dict,
    task_record,
    switch_reason: str
) -> int:
    """
    为任务定义中所有未执行的块创建终止状态记录

    Args:
        session: 数据库会话
        task_record_id: 任务记录ID
        task_def_detail: 任务定义详情
        existing_block_map: 已有块记录映射
        task_record: 任务记录对象
        switch_reason: 换机器人原因

    Returns:
        int: 创建的块记录数量
    """
    from data.enum.task_block_record_enum import TaskBlockRecordStatus
    from sqlalchemy import insert

    def collect_all_blocks(block_def, blocks_list):
        """递归收集所有块定义"""
        blocks_list.append(block_def)
        children = block_def.get('children', {})
        if children:
            for branch_blocks in children.values():
                if isinstance(branch_blocks, list):
                    for child_block in branch_blocks:
                        collect_all_blocks(child_block, blocks_list)

    all_blocks_in_def = []
    if task_def_detail and 'rootBlock' in task_def_detail:
        collect_all_blocks(task_def_detail['rootBlock'], all_blocks_in_def)

    created_count = 0
    current_time = datetime.datetime.now()

    for block_def in all_blocks_in_def:
        block_id = str(block_def.get('id', ''))
        block_name = block_def.get('name', '')
        block_type = block_def.get('blockType', '')

        # 跳过根块和已有记录的块(使用block_name检查,因为block_id可能重复)
        if block_name == '-1' or block_name in existing_block_map:
            continue

        # 创建终止状态记录
        terminated_block_id = str(uuid.uuid4())
        input_params = block_def.get('inputParams', {})
        input_params_value = {
            key: param.get('value') for key, param in input_params.items()
        } if input_params else {}

        new_block_record = VWEDBlockRecord(
            id=terminated_block_id,
            block_name=block_name,
            block_id=block_id,
            block_config_id='',
            block_input_params=json.dumps(input_params, ensure_ascii=False),
            block_input_params_value=json.dumps(input_params_value, ensure_ascii=False),
            block_out_params_value='{}',
            block_internal_variables='{}',
            block_execute_name=block_type,
            task_id=task_record.def_id,
            task_record_id=task_record_id,
            started_on=current_time,
            ended_on=current_time,
            ended_reason=f"换机器人终止: {switch_reason}",
            status=TaskBlockRecordStatus.CANCELED,
            ctrl_status=None,
            input_params=json.dumps(input_params, ensure_ascii=False),
            internal_variables='{}',
            output_params='{}',
            version=1,
            remark=f"未执行即被终止: {switch_reason}"
        )
        session.add(new_block_record)

        # 创建日志记录
        log_id = str(uuid.uuid4())
        log_message = {
            "success": False,
            "message": f"换机器人终止(未执行): {switch_reason}",
            "output": {
                "block_name": block_name,
                "block_type": block_type,
                "terminated_at": current_time.isoformat()
            }
        }

        stmt = insert(VWEDTaskLog).values(
            id=log_id,
            level=3,
            message=json.dumps(log_message, ensure_ascii=False),
            task_block_id=block_name,
            task_id=task_record.def_id,
            task_record_id=task_record_id,
            block_record_id=terminated_block_id,
            log_type="block_execution"
        )
        await session.execute(stmt)
        created_count += 1

    logger.info(f"为任务 {task_record_id} 创建了 {created_count} 个未执行块的终止记录")
    return created_count


async def cancel_and_cleanup_task(scheduler, task_record_id: str, cancel_reason: str = "任务被取消") -> None:
    """
    取消任务并清理相关资源

    Args:
        scheduler: 调度器实例
        task_record_id: 任务记录ID
        cancel_reason: 取消原因
    """
    import asyncio

    # 取消正在执行的任务(会自动生成所有块的执行记录)
    cancel_result = await scheduler.cancel_task(task_record_id, cancel_reason)
    logger.info(f"取消任务 {task_record_id} 结果: {cancel_result}")

    # 等待执行器完全停止
    await asyncio.sleep(1.5)

    # 强制从running_tasks中移除任务
    if task_record_id in scheduler.running_tasks:
        logger.info(f"强制从running_tasks中移除任务: {task_record_id}")
        del scheduler.running_tasks[task_record_id]
        await scheduler.persistence_manager.remove_task(task_record_id)


def regenerate_block_ids(block: Dict, id_mapping: Dict[str, str] = None) -> Dict:
    """
    递归重新生成块的id和name，避免与原有块冲突

    Args:
        block: 任务块定义
        id_mapping: id映射字典，用于更新Expression类型的参数引用

    Returns:
        Dict: 更新后的任务块
    """
    if id_mapping is None:
        id_mapping = {}

    # 保存旧的id和name
    old_id = str(block.get('id', ''))
    old_name = block.get('name', '')

    # 生成新的id和name
    new_id = str(uuid.uuid4())
    new_name = f"sw_{new_id[:8]}"  # sw = switch，表示换机器人后新增的块

    # 更新id和name
    block['id'] = new_id
    block['name'] = new_name

    # 记录id映射关系
    id_mapping[old_id] = new_id
    id_mapping[old_name] = new_name

    # 更新inputParams中引用其他块的Expression类型参数
    input_params = block.get('inputParams', {})
    for param_key, param_value in input_params.items():
        if isinstance(param_value, dict):
            param_type = param_value.get('type')
            param_val = param_value.get('value', '')

            # 如果是Expression类型且引用了blocks.xxx的格式
            if param_type == 'Expression' and isinstance(param_val, str) and param_val.startswith('blocks.'):
                # 解析引用的块名称: blocks.{block_name}.{field}
                parts = param_val.split('.')
                if len(parts) >= 2:
                    referenced_block_name = parts[1]
                    # 如果这个块名在映射中，更新引用
                    if referenced_block_name in id_mapping:
                        new_referenced_name = id_mapping[referenced_block_name]
                        parts[1] = new_referenced_name
                        new_param_val = '.'.join(parts)
                        param_value['value'] = new_param_val
                        logger.info(f"更新Expression引用: {param_val} -> {new_param_val}")

    # 递归处理子块
    children = block.get('children', {})
    for branch_name, branch_blocks in children.items():
        if isinstance(branch_blocks, list):
            for child_block in branch_blocks:
                regenerate_block_ids(child_block, id_mapping)

    return block


def extract_original_task_blocks(task_def_detail: Dict) -> List[Dict]:
    """
    从任务定义中提取原始任务块列表(rootBlock的children)
    并重新生成所有块的id和name以避免冲突

    Args:
        task_def_detail: 任务定义详情

    Returns:
        List[Dict]: 原始任务块列表（已更新id和name）
    """
    root_block = task_def_detail.get('rootBlock', {})
    children = root_block.get('children', {})
    default_blocks = children.get('default', [])

    # 深拷贝以避免修改原始对象
    copied_blocks = copy.deepcopy(default_blocks)

    # 为所有块重新生成id和name
    id_mapping = {}
    for block in copied_blocks:
        regenerate_block_ids(block, id_mapping)

    logger.info(f"重新生成了 {len(copied_blocks)} 个块的id和name，映射关系: {len(id_mapping)} 个")
    return copied_blocks


def update_select_agv_params(
    block: Dict,
    robot_name: Optional[str],
    robot_group: Optional[str],
    robot_label_group: Optional[str]
) -> Dict:
    """
    更新选择机器人块(CSelectAgvBp)的参数

    Args:
        block: 任务块定义
        robot_name: 机器人名称
        robot_group: 机器人组
        robot_label_group: 机器人标签组

    Returns:
        Dict: 更新后的任务块
    """
    if block.get('blockType') == 'CSelectAgvBp':
        input_params = block.get('inputParams', {})

        # 更新机器人选择参数
        if robot_name:
            input_params['vehicle'] = {"value": robot_name, "type": "Simple", "required": False}
        if robot_group:
            input_params['group'] = {"value": robot_group, "type": "Simple", "required": False}
        if robot_label_group:
            input_params['label_group'] = {"value": robot_label_group, "type": "Simple", "required": False}

        block['inputParams'] = input_params
        logger.info(f"已更新选择机器人块参数: vehicle={robot_name}, group={robot_group}, label_group={robot_label_group}")

    return block


def recursively_update_select_agv_blocks(
    blocks: List[Dict],
    robot_name: Optional[str],
    robot_group: Optional[str],
    robot_label_group: Optional[str]
) -> List[Dict]:
    """
    递归更新所有选择机器人块的参数

    Args:
        blocks: 任务块列表
        robot_name: 机器人名称
        robot_group: 机器人组
        robot_label_group: 机器人标签组

    Returns:
        List[Dict]: 更新后的任务块列表
    """
    for block in blocks:
        # 更新当前块
        update_select_agv_params(block, robot_name, robot_group, robot_label_group)

        # 递归处理子块
        children = block.get('children', {})
        for branch_name, branch_blocks in children.items():
            if isinstance(branch_blocks, list):
                recursively_update_select_agv_blocks(branch_blocks, robot_name, robot_group, robot_label_group)

    return blocks


async def validate_site_id_type(session, site_id: str, map_id: str) -> Tuple[bool, str]:
    """
    校验site_id的类型（库位ID还是站点名称）
    如果是站点名称，将其转换为对应的库位ID

    Args:
        session: 数据库会话
        site_id: 站点/库位ID
        map_id: 地图ID

    Returns:
        Tuple[bool, str]: (is_storage_location, validated_site_id)
        - is_storage_location: True表示是库位，False表示是站点
        - validated_site_id: 如果是站点名称则转换为库位ID，否则保持原值
    """
    from data.models.operate_point import OperatePoint
    from data.models.operate_point_layer import OperatePointLayer

    is_storage_location = False
    validated_site_id = site_id

    try:
        # 首先检查是否是库位名称
        stmt = select(OperatePointLayer).where(
            OperatePointLayer.layer_name == site_id,
            OperatePointLayer.is_disabled == False,
            OperatePointLayer.scene_id == map_id
        )
        result = await session.execute(stmt)
        operate_point_layer = result.scalar_one_or_none()

        if operate_point_layer:
            logger.info(f"site_id '{site_id}' 识别为库位名称，对应动作点: {operate_point_layer.station_name}")
            is_storage_location = True
            validated_site_id = site_id  # 已经是库位ID，保持不变
        else:
            # 检查是否是站点名称（动作点）
            stmt = select(OperatePoint).where(
                OperatePoint.station_name == site_id,
                OperatePoint.is_disabled == False,
                OperatePoint.scene_id == map_id
            )
            result = await session.execute(stmt)
            operate_point = result.scalar_one_or_none()

            if operate_point:
                # 找到了站点，需要查询该站点下的库位
                logger.info(f"site_id '{site_id}' 识别为站点名称，查询其对应的库位")

                # 根据站点名称和场景ID查询库位
                stmt = select(OperatePointLayer).where(
                    OperatePointLayer.station_name == site_id,
                    OperatePointLayer.scene_id == map_id,
                    OperatePointLayer.is_disabled == False
                ).order_by(OperatePointLayer.layer_index)  # 按层索引排序

                result = await session.execute(stmt)
                layers = result.scalars().all()

                if layers:
                    # 取第一个可用的库位
                    first_layer = layers[0]
                    validated_site_id = first_layer.layer_name
                    is_storage_location = True
                    logger.info(f"站点 '{site_id}' 转换为库位: {validated_site_id} (共找到 {len(layers)} 个库位)")
                else:
                    # 站点存在但没有库位，保持为站点模式
                    logger.warning(f"站点 '{site_id}' 没有找到对应的库位，将作为站点名称使用")
                    is_storage_location = False
                    validated_site_id = site_id
            else:
                logger.warning(f"site_id '{site_id}' 未在数据库中找到匹配记录，将作为站点名称使用")
                is_storage_location = False
                validated_site_id = site_id

    except Exception as e:
        logger.error(f"校验site_id类型时发生异常: {str(e)}")
        import traceback
        traceback.print_exc()
        is_storage_location = False
        validated_site_id = site_id

    return is_storage_location, validated_site_id


async def find_and_convert_station_to_storage(session, blocks: List[Dict], map_id: str) -> List[Dict]:
    """
    查找任务块中的站点参数，如果是站点名称则转换为库位ID

    Args:
        session: 数据库会话
        blocks: 任务块列表
        map_id: 地图ID

    Returns:
        List[Dict]: 转换后的任务块列表
    """
    from data.models.operate_point_layer import OperatePointLayer

    async def convert_block_recursively(block: Dict) -> Dict:
        """递归转换单个块中的站点参数"""
        block_type = block.get('blockType', '')
        input_params = block.get('inputParams', {})

        # 检查需要转换站点的块类型
        # 1. CAgvOperationBp - targetSiteLabel参数
        if block_type == 'CAgvOperationBp':
            target_site_param = input_params.get('targetSiteLabel', {})
            if target_site_param and target_site_param.get('type') == 'Simple':
                station_name = target_site_param.get('value', '')
                if station_name:
                    # 查询该站点对应的库位
                    stmt = select(OperatePointLayer).where(
                        OperatePointLayer.station_name == station_name,
                        OperatePointLayer.scene_id == map_id,
                        OperatePointLayer.is_disabled == False
                    ).order_by(OperatePointLayer.layer_index)

                    result = await session.execute(stmt)
                    layers = result.scalars().all()

                    if layers:
                        first_layer = layers[0]
                        logger.info(f"CAgvOperationBp块: 站点 '{station_name}' 转换为库位: {first_layer.layer_name}")
                        input_params['targetSiteLabel']['value'] = first_layer.layer_name

        # 2. GetIdleSiteBp / GetIdleCrowdedSiteBp - siteId参数
        elif block_type in ['GetIdleSiteBp', 'GetIdleCrowdedSiteBp']:
            site_id_param = input_params.get('siteId', {})
            if site_id_param and site_id_param.get('type') == 'Simple':
                station_name = site_id_param.get('value', '')
                if station_name:
                    # 查询该站点对应的库位
                    stmt = select(OperatePointLayer).where(
                        OperatePointLayer.station_name == station_name,
                        OperatePointLayer.scene_id == map_id,
                        OperatePointLayer.is_disabled == False
                    ).order_by(OperatePointLayer.layer_index)

                    result = await session.execute(stmt)
                    layers = result.scalars().all()

                    if layers:
                        first_layer = layers[0]
                        logger.info(f"{block_type}块: 站点 '{station_name}' 转换为库位: {first_layer.layer_name}")
                        input_params['siteId']['value'] = first_layer.layer_name

        # 3. CSelectAgvBp - keyRoute参数（可能包含站点）
        elif block_type == 'CSelectAgvBp':
            key_route_param = input_params.get('keyRoute', {})
            if key_route_param and key_route_param.get('type') == 'Simple':
                station_name = key_route_param.get('value', '')
                if station_name and not station_name.startswith('blocks.'):
                    # 查询该站点对应的库位
                    stmt = select(OperatePointLayer).where(
                        OperatePointLayer.station_name == station_name,
                        OperatePointLayer.scene_id == map_id,
                        OperatePointLayer.is_disabled == False
                    ).order_by(OperatePointLayer.layer_index)

                    result = await session.execute(stmt)
                    layers = result.scalars().all()

                    if layers:
                        first_layer = layers[0]
                        logger.info(f"CSelectAgvBp块: keyRoute站点 '{station_name}' 转换为库位: {first_layer.layer_name}")
                        input_params['keyRoute']['value'] = first_layer.layer_name

        # 递归处理子块
        children = block.get('children', {})
        for branch_name, branch_blocks in children.items():
            if isinstance(branch_blocks, list):
                for child_block in branch_blocks:
                    await convert_block_recursively(child_block)

        return block

    # 转换所有块
    for block in blocks:
        await convert_block_recursively(block)

    return blocks


def find_pick_storage_location_block(blocks: List[Dict]) -> Optional[Tuple[Dict, int, List[Dict]]]:
    """
    在任务块列表中查找取货的获取库位块(filled=false或空)

    Args:
        blocks: 任务块列表

    Returns:
        Optional[Tuple[Dict, int, List[Dict]]]: (找到的块, 块索引, 所在的列表) 或 None
    """
    for idx, block in enumerate(blocks):
        block_type = block.get('blockType', '')

        # 检查是否是获取库位块
        if block_type in ['GetIdleSiteBp', 'GetIdleCrowdedSiteBp']:
            input_params = block.get('inputParams', {})
            filled_param = input_params.get('filled', {})
            filled_value = filled_param.get('value', '')

            # 判断是否是取货(filled为false或空)
            if filled_value == 'false' or filled_value == '' or filled_value == False:
                logger.info(f"找到取货的获取库位块: {block_type}, name={block.get('name')}, filled={filled_value}")
                return (block, idx, blocks)

        # 递归查找子块
        children = block.get('children', {})
        for branch_blocks in children.values():
            if isinstance(branch_blocks, list):
                result = find_pick_storage_location_block(branch_blocks)
                if result:
                    return result

    return None


def replace_get_crowded_site_to_get_site(block: Dict, site_id: str) -> Dict:
    """
    将GetIdleCrowdedSiteBp替换为GetIdleSiteBp，并设置siteId参数

    Args:
        block: 获取库位块
        site_id: 库位ID

    Returns:
        Dict: 替换后的块
    """
    if block.get('blockType') == 'GetIdleCrowdedSiteBp':
        logger.info(f"将GetIdleCrowdedSiteBp替换为GetIdleSiteBp，siteId={site_id}")
        block['blockType'] = 'GetIdleSiteBp'
        block['label'] = '获取库位'

    # 更新siteId参数
    input_params = block.get('inputParams', {})
    input_params['siteId'] = {"value": site_id, "type": "Simple", "required": False}
    block['inputParams'] = input_params

    return block


def build_put_cargo_blocks_for_storage_location(
    site_id: str,
    action: str,
    robot_name: Optional[str],
    robot_group: Optional[str],
    robot_label_group: Optional[str]
) -> List[Dict]:
    """
    构建库位场景的放货任务块
    包括: GetIdleSiteBp → CSelectAgvBp(包含CAgvOperationBp) → SetSiteFilledBp → SetSiteUnlockedBp

    Args:
        site_id: 库位ID
        action: 执行动作
        robot_name: 机器人名称
        robot_group: 机器人组
        robot_label_group: 机器人标签组

    Returns:
        List[Dict]: 放货任务块列表
    """
    new_block_id_1 = str(uuid.uuid4())  # 获取库位
    new_block_id_2 = str(uuid.uuid4())  # 选择机器人
    new_block_id_3 = str(uuid.uuid4())  # 执行通用动作
    new_block_id_4 = str(uuid.uuid4())  # 设置库位为占用
    new_block_id_5 = str(uuid.uuid4())  # 解锁库位

    get_site_block_name = f"put_site_{new_block_id_1[:8]}"

    return [
        # 1. 获取库位块并锁定
        {
            "id": new_block_id_1,
            "name": get_site_block_name,
            "blockType": "GetIdleSiteBp",
            "label": "获取库位(放货)",
            "description": "获取目标库位并锁定",
            "inputParams": {
                "siteId": {"value": site_id, "type": "Simple", "required": False},
                "locked": {"value": "false", "type": "Simple", "required": True},
                "lock": {"value": "true", "type": "Simple", "required": False},
                "content": {"value": "", "type": "Simple", "required": False},
                "filled": {"value": "", "type": "Simple", "required": False},
                "groupName": {"value": "", "type": "Simple", "required": False},
                "ifFair": {"value": True, "type": "Simple", "required": False},
                "orderDesc": {"value": "", "type": "Simple", "required": False},
                "retryPeriod": {"value": "", "type": "Simple", "required": False},
                "type": {"value": "", "type": "Simple", "required": False}
            },
            "children": {},
            "expanded": True,
            "selected": False,
            "refTaskDefId": ""
        },
        # 2. 选择机器人块
        {
            "id": new_block_id_2,
            "name": f"put_robot_{new_block_id_2[:8]}",
            "blockType": "CSelectAgvBp",
            "label": "选择执行机器人(放货)",
            "description": "选择机器人执行放货任务",
            "inputParams": {
                "vehicle": {"value": robot_name or "", "type": "Simple", "required": False},
                "group": {"value": robot_group or "", "type": "Simple", "required": False},
                "label_group": {"value": robot_label_group or "", "type": "Simple", "required": False},
                "keyRoute": {"value": f"blocks.{get_site_block_name}.siteId", "type": "Expression", "required": True},
                "priority": {"value": "", "type": "Simple", "required": False}
            },
            "children": {
                "default": [
                    # 3. 执行通用动作块
                    {
                        "id": new_block_id_3,
                        "name": f"put_action_{new_block_id_3[:8]}",
                        "blockType": "CAgvOperationBp",
                        "label": "机器人通用动作(放货)",
                        "description": f"执行动作: {action}",
                        "inputParams": {
                            "targetSiteLabel": {"value": f"blocks.{get_site_block_name}.siteId", "type": "Expression", "required": True},
                            "task": {"value": action or "", "type": "Simple", "required": False},
                            "spin": {"value": "", "type": "Simple", "required": False}
                        },
                        "children": {},
                        "expanded": True,
                        "selected": False,
                        "refTaskDefId": ""
                    }
                ]
            },
            "expanded": True,
            "selected": False,
            "refTaskDefId": ""
        },
        # 4. 设置库位为占用块
        {
            "id": new_block_id_4,
            "name": f"put_fill_{new_block_id_4[:8]}",
            "blockType": "SetSiteFilledBp",
            "label": "设置库位为占用(放货)",
            "description": "设置库位为占用状态",
            "inputParams": {
                "siteId": {"value": f"blocks.{get_site_block_name}.siteId", "type": "Expression", "required": True}
            },
            "children": {},
            "expanded": True,
            "selected": False,
            "refTaskDefId": ""
        },
        # 5. 解锁库位块
        {
            "id": new_block_id_5,
            "name": f"put_unlock_{new_block_id_5[:8]}",
            "blockType": "SetSiteUnlockedBp",
            "label": "解锁库位(放货)",
            "description": "解锁目标库位",
            "inputParams": {
                "siteId": {"value": f"blocks.{get_site_block_name}.siteId", "type": "Expression", "required": True},
                "unLockedId": {"value": "", "type": "Simple", "required": False}
            },
            "children": {},
            "expanded": True,
            "selected": False,
            "refTaskDefId": ""
        }
    ]


async def check_select_robot_block_success(session, task_record_id: str) -> Tuple[bool, str]:
    """
    检查任务记录中选择机器人块是否执行成功

    Args:
        session: 数据库会话
        task_record_id: 任务记录ID

    Returns:
        Tuple[bool, str]: (是否成功, 错误信息)
    """
    from data.enum.task_block_record_enum import TaskBlockRecordStatus

    try:
        # 查询所有选择机器人块的执行记录
        stmt = select(VWEDBlockRecord).where(
            VWEDBlockRecord.task_record_id == task_record_id,
            VWEDBlockRecord.block_execute_name == 'CSelectAgvBp'
        )
        result = await session.execute(stmt)
        select_robot_blocks = result.scalars().all()

        if not select_robot_blocks:
            return False, "任务中未找到选择机器人块，无法换机器人"

        # 检查是否有执行成功的选择机器人块
        has_success = False
        for block in select_robot_blocks:
            if block.status == TaskBlockRecordStatus.SUCCESS:
                has_success = True
                # 检查是否选择了机器人（输出参数中应该有agvId）
                if block.block_out_params_value:
                    try:
                        out_params = json.loads(block.block_out_params_value)
                        agv_id = out_params.get('agvId')
                        if agv_id:
                            logger.info(f"找到成功执行的选择机器人块，已选择机器人: {agv_id}")
                            return True, ""
                    except json.JSONDecodeError:
                        logger.warning(f"解析选择机器人块输出参数失败: {block.block_out_params_value}")

        if has_success:
            return False, "选择机器人块执行成功但未选择到机器人，无法换机器人"
        else:
            return False, "选择机器人块尚未执行成功，无法换机器人"

    except Exception as e:
        logger.error(f"检查选择机器人块状态时发生异常: {str(e)}")
        import traceback
        traceback.print_exc()
        return False, f"检查选择机器人块状态失败: {str(e)}"


async def validate_and_convert_site_id(
    session,
    site_id: str,
    map_id: str,
    task_def_detail: Dict,
    has_cargo: bool
) -> Tuple[bool, str, str]:
    """
    校验并转换site_id

    对于载货和空载场景都需要进行如下校验:
    1. 判断site_id是库位还是站点
    2. 如果是站点:
       - 检查原任务定义中是否有获取库位块(GetIdleSiteBp/GetIdleCrowdedSiteBp)
       - 如果有获取库位块，则必须将站点转换为对应的库位ID
       - 如果没有获取库位块但site_id是站点，则返回错误

    Args:
        session: 数据库会话
        site_id: 站点/库位ID
        map_id: 地图ID
        task_def_detail: 任务定义详情
        has_cargo: 是否载货

    Returns:
        Tuple[bool, str, str]: (是否成功, 转换后的site_id, 错误信息)
    """
    from data.models.operate_point import OperatePoint
    from data.models.operate_point_layer import OperatePointLayer

    try:
        # 1. 首先判断site_id是库位还是站点
        is_storage_location = False
        validated_site_id = site_id

        # 检查是否是库位名称
        stmt = select(OperatePointLayer).where(
            OperatePointLayer.layer_name == site_id,
            OperatePointLayer.is_disabled == False,
            OperatePointLayer.scene_id == map_id
        )
        result = await session.execute(stmt)
        operate_point_layer = result.scalar_one_or_none()

        if operate_point_layer:
            # 是库位，直接返回
            logger.info(f"site_id '{site_id}' 是库位，无需转换")
            return True, site_id, ""

        # 检查是否是站点名称
        stmt = select(OperatePoint).where(
            OperatePoint.station_name == site_id,
            OperatePoint.is_disabled == False,
            OperatePoint.scene_id == map_id
        )
        result = await session.execute(stmt)
        operate_point = result.scalar_one_or_none()

        if not operate_point:
            # 既不是库位也不是站点
            return False, site_id, f"site_id '{site_id}' 既不是有效的库位也不是有效的站点"

        # 2. site_id是站点，需要检查原任务定义中是否有获取库位块
        logger.info(f"site_id '{site_id}' 是站点，检查原任务定义中是否有获取库位块")

        def has_storage_location_block(block_def: Dict) -> bool:
            """递归检查是否有获取库位块"""
            block_type = block_def.get('blockType', '')
            if block_type in ['GetIdleSiteBp', 'GetIdleCrowdedSiteBp']:
                return True

            # 递归检查子块
            children = block_def.get('children', {})
            for branch_blocks in children.values():
                if isinstance(branch_blocks, list):
                    for child_block in branch_blocks:
                        if has_storage_location_block(child_block):
                            return True
            return False

        # 从rootBlock开始检查
        has_get_site_block = False
        if task_def_detail and 'rootBlock' in task_def_detail:
            has_get_site_block = has_storage_location_block(task_def_detail['rootBlock'])

        if not has_get_site_block:
            # 没有获取库位块，不允许使用站点
            return False, site_id, f"任务定义中没有获取库位块，不能使用站点 '{site_id}'，请使用库位ID"

        # 3. 有获取库位块，需要将站点转换为对应的库位
        logger.info(f"任务定义中有获取库位块，将站点 '{site_id}' 转换为对应的库位")

        # 查询站点对应的库位
        stmt = select(OperatePointLayer).where(
            OperatePointLayer.station_name == site_id,
            OperatePointLayer.scene_id == map_id,
            OperatePointLayer.is_disabled == False
        ).order_by(OperatePointLayer.layer_index)

        result = await session.execute(stmt)
        layers = result.scalars().all()

        if not layers:
            return False, site_id, f"站点 '{site_id}' 没有找到对应的库位，请检查库位配置"

        # 取第一个可用的库位
        first_layer = layers[0]
        validated_site_id = first_layer.layer_name
        logger.info(f"站点 '{site_id}' 转换为库位: {validated_site_id} (共找到 {len(layers)} 个库位)")

        return True, validated_site_id, ""

    except Exception as e:
        logger.error(f"校验并转换site_id时发生异常: {str(e)}")
        import traceback
        traceback.print_exc()
        return False, site_id, f"校验site_id失败: {str(e)}"


def build_put_cargo_blocks_for_station(
    site_id: str,
    action: str,
    robot_name: Optional[str],
    robot_group: Optional[str],
    robot_label_group: Optional[str]
) -> List[Dict]:
    """
    构建站点场景的放货任务块
    包括: CSelectAgvBp(包含CAgvOperationBp)

    Args:
        site_id: 站点ID
        action: 执行动作
        robot_name: 机器人名称
        robot_group: 机器人组
        robot_label_group: 机器人标签组

    Returns:
        List[Dict]: 放货任务块列表
    """
    new_block_id_1 = str(uuid.uuid4())  # 选择机器人
    new_block_id_2 = str(uuid.uuid4())  # 执行通用动作

    return [
        # 1. 选择机器人块
        {
            "id": new_block_id_1,
            "name": f"put_robot_{new_block_id_1[:8]}",
            "blockType": "CSelectAgvBp",
            "label": "选择执行机器人(放货)",
            "description": "选择机器人执行放货任务",
            "inputParams": {
                "vehicle": {"value": robot_name or "", "type": "Simple", "required": False},
                "group": {"value": robot_group or "", "type": "Simple", "required": False},
                "label_group": {"value": robot_label_group or "", "type": "Simple", "required": False},
                "keyRoute": {"value": site_id, "type": "Simple", "required": True},
                "priority": {"value": "", "type": "Simple", "required": False}
            },
            "children": {
                "default": [
                    # 2. 执行通用动作块
                    {
                        "id": new_block_id_2,
                        "name": f"put_action_{new_block_id_2[:8]}",
                        "blockType": "CAgvOperationBp",
                        "label": "机器人通用动作(放货)",
                        "description": f"执行动作: {action}",
                        "inputParams": {
                            "targetSiteLabel": {"value": site_id, "type": "Simple", "required": True},
                            "task": {"value": action or "", "type": "Simple", "required": False},
                            "spin": {"value": False, "type": "Simple", "required": False}
                        },
                        "children": {},
                        "expanded": True,
                        "selected": False,
                        "refTaskDefId": ""
                    }
                ]
            },
            "expanded": True,
            "selected": False,
            "refTaskDefId": ""
        }
    ]
