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

"""
同步服务模块
用于与天风系统进行数据同步
"""

import json
import aiohttp
import asyncio
import requests
from typing import Dict, Any, Optional
from datetime import datetime
from pydantic import BaseModel

# 获取日志记录器
from utils.logger import get_logger
logger = get_logger("services.sync_service")

# 导入天风API配置
from config.tf_api_config import get_tf_api_config

# 获取配置
tf_config = get_tf_api_config()

# 接口配置
class TFApiConfig:
    """系统任务API配置"""
    # 基础URL
    BASE_URL = tf_config["base_url"]
    
    # 接口路径
    LOGIN_PATH = tf_config["endpoints"]["login"]
    CREATE_TASK_PATH = tf_config["endpoints"]["create_task"]
    CHOOSE_AMR_PATH = tf_config["endpoints"]["choose_amr"]
    ADD_ACTION_PATH = tf_config["endpoints"]["add_action"]
    CLOSURE_TASK_PATH = tf_config["endpoints"]["closure_task"]
    GET_TASK_BLOCK_PATH = tf_config["endpoints"]["get_task_block"]
    GET_TASK_BLOCK_ACTION_PATH = tf_config["endpoints"]["get_task_block_action"]
    SET_TASK_IN_PROGRESS_PATH = tf_config["endpoints"]["set_task_in_progress"]
    SET_TASK_COMPLETED_PATH = tf_config["endpoints"]["set_task_completed"]
    SET_TASK_TERMINATED_PATH = tf_config["endpoints"]["set_task_terminated"]
    SET_TASK_FAILED_PATH = tf_config["endpoints"]["set_task_failed"]
    SET_TASK_DESCRIPTION_PATH = tf_config["endpoints"]["set_task_description"]
    GET_AMR_INFO_PATH = tf_config["endpoints"]["get_amr_info"]
    # 超时设置（秒）
    TIMEOUT = tf_config["timeout"]
    
    # 重试次数
    RETRY_TIMES = tf_config["retry_times"]
    
    # 模拟模式
    MOCK_MODE = tf_config["mock_mode"]
    
    # token请求头
    TOKEN_HEADER = tf_config["token_header"]

class CreateTaskRequest(BaseModel):
    """创建任务请求参数"""
    vwedTaskId: str
    vwedTaskParentId: str
    name: str
    isPeriodic: int  # 使用字符串类型，因为接口文档中显示为字符串
    priority: int    # 使用字符串类型，因为接口文档中显示为字符串
    createTime: str  # 格式: "2025-04-08 22:03:57"
    sceneId: str
    needAmr: int
class ChooseAmrRequest(BaseModel):
    """选择AMR请求参数"""
    vwedTaskId: str  # 任务id
    stationName: str  # 关键站点名称
    priority: int = 1  # 优先级
    appointAmrName: str = ""  # 指定AMR名称
    appointAmrGroupName: list = []  # 指定AMR分组名称列表
    appointAmrLabelGroup: list = []  # 指定AMR标签组列表

class AddActionRequest(BaseModel):
    """添加动作请求参数"""
    taskBlockId: str
    stationName: str
    action: str
    param: str
    store: str

class LoginRequest(BaseModel):
    """登录请求参数"""
    username: str
    password: str

class LoginResponse(BaseModel):
    """登录响应模型"""
    success: bool
    message: str
    code: int
    result: Optional[Dict[str, Any]] = None
    timestamp: Optional[int] = None

class ApiResponse(BaseModel):
    """API响应基础模型"""
    success: bool
    message: str
    code: int
    result: Optional[Dict[str, Any]] = None
    timestamp: Optional[int] = None

async def create_task(task_record_id: str, task_name: str, is_periodic: bool, priority: int, parent_id: str, 
                      token: str, map_id: str, is_agv: int) -> Optional[ApiResponse]:
    """
    调用系统任务创建任务接口
    
    Args:
        task_record_id: VWED系统任务实例ID
        task_name: 任务名称
        is_periodic: 是否周期任务
        priority: 优先级(1-5)
        parent_id: 父任务ID，如果没有则为空
        token: 认证令牌
        map_id: 相关地图ID
        is_agv: 是否选择AGV
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """    
    # 构造请求参数
    request_data = CreateTaskRequest(
        vwedTaskId=task_record_id,
        vwedTaskParentId=parent_id,
        name=task_name,
        isPeriodic=int(is_periodic),
        priority=str(priority),
        createTime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        sceneId=map_id,
        needAmr=is_agv
    )
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CREATE_TASK_PATH}"
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"
    logger.debug(f"使用认证令牌调用接口，令牌头: {TFApiConfig.TOKEN_HEADER}")
    
    try:
        logger.info(f"正在同步创建任务到天风系统: {task_record_id}")
        logger.debug(f"创建任务请求参数: {request_data.model_dump_json()}")
        # 优化超时配置，分别设置连接超时和总超时
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.post(
                url, 
                json=request_data.model_dump(), 
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    # print(response_data, "-------------============================")
                    if response_data.get("success"):
                        logger.info(f"成功同步任务到系统任务: {task_record_id}")
                    else:
                        logger.warning(f"同步任务到系统任务失败: {response_data.get('message')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except aiohttp.ClientError as e:
        logger.error(f"调用系统任务创建任务接口失败: {str(e)}")
        return None
    except Exception as e:
        logger.error(f"同步任务到系统任务时发生错误: {str(e)}")
        return None

async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name: str = "", amr_group_name: list = None, amr_label_group: list = None, token: str = None, priority: int = 1) -> Optional[ApiResponse]:
    """
    创建选择AMR任务

    Args:
        task_id: 天风任务ID
        key_station_name: 关键站点名称
        amr_name: 指定的AMR名称，可选
        amr_group_name: 指定的AMR分组名称列表，可选
        amr_label_group: 指定的AMR标签组列表，可选
        token: 认证令牌
        priority: 优先级
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 确保列表参数不为None
    if amr_group_name is None:
        amr_group_name = []
    if amr_label_group is None:
        amr_label_group = []

    # 构造请求参数
    request_data = ChooseAmrRequest(
        vwedTaskId=task_id,
        stationName=key_station_name,
        appointAmrName=amr_name,
        appointAmrGroupName=amr_group_name,
        appointAmrLabelGroup=amr_label_group,
        priority=priority
    )
    
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CHOOSE_AMR_PATH}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"

    try:
        logger.info(f"正在创建选择AMR任务: {task_id}, 站点: {key_station_name}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.post(
                url, 
                json=request_data.model_dump(),
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功为任务选择AMR: {task_id}, AMR: {response_text.get('result', {}).get('amrName')}")
                    else:
                        logger.warning(f"为任务选择AMR失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用选择AMR接口失败: {str(e)}")
        return None

async def add_action(task_id: str, station_name: str, action: str, token: str = None, param: dict = None, store: str = None) -> Optional[ApiResponse]:
    """
    调用系统任务添加动作接口
    
    Args:
        task_id: 系统任务ID
        station_name: 站点名称
        action: 动作
        token: 认证令牌
        param: 动作参数
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """    
    # 构造请求参数
    import json
    request_data = AddActionRequest(
        taskBlockId=task_id,
        stationName=station_name,
        action=action,
        param=json.dumps(param),
        store=store
    )
    
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.ADD_ACTION_PATH}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"

    try:
        logger.info(f"正在为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.post(
                url, 
                json=request_data.model_dump(),
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
                    else:
                        logger.warning(f"为任务添加动作失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用添加动作接口失败: {str(e)}")
        return None

async def closure_task(task_id: str, token: str = None) -> Optional[ApiResponse]:
    """
    调用系统任务封口任务接口
    
    Args:
        task_id: 系统任务ID
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CLOSURE_TASK_PATH.format(task_id)}"
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"

    try:
        logger.info(f"正在封口任务: {task_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功封口任务: {task_id}")
                    else:
                        logger.warning(f"封口任务失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用封口任务接口失败: {str(e)}")
        return None

async def get_task_block_detail(task_block_id: str, token: str = None) -> Optional[Dict[str, Any]]:
    """
    获取任务块详情
    
    Args:
        task_block_id: 任务块ID
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_TASK_BLOCK_PATH.format(id=task_block_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"

    try:
        logger.info(f"正在获取任务块详情: {task_block_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功获取任务块详情: {task_block_id} 具体详情: {response_data}")
                    else:
                        logger.info(f"获取任务块详情失败: {response_data.get('message', '未知错误')}")
                    return response_data
                except json.JSONDecodeError:
                    logger.info(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用获取任务块详情接口失败: {str(e)}")
        return None


async def get_task_block_action_detail(task_block_id: str, token: str = None) -> Optional[Dict[str, Any]]:
    """
    获取任务块动作详情
    
    Args:
        task_block_id: 任务块ID
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_TASK_BLOCK_ACTION_PATH.format(id=task_block_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"
    

    try:
        logger.info(f"正在获取任务块动作详情: {task_block_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功获取任务块动作详情: {task_block_id} 具体详情: {response_data}")
                    else:
                        logger.info(f"获取任务块动作详情失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用获取任务块动作详情接口失败: {str(e)}")
        return None


async def wait_for_amr_selection(task_block_id: str, token: str = None, context = None) -> Optional[Dict[str, Any]]:
    """
    等待AMR选择完成，轮询任务块详情直到获取到AMR ID
    
    Args:
        task_block_id: 任务块ID
        token: 认证令牌
        context: 任务上下文
    Returns:
        Optional[Dict[str, Any]]: 包含AMR ID的响应，如果请求失败则返回None
    """
    logger.info(f"开始等待任务块 {task_block_id} 的AMR选择结果")
    
    retry_count = 0
    # 使用固定的重试间隔0.5秒
    actual_retry_interval = 1
    
    # 无限循环，直到获取到结果
    while True:
        retry_count += 1
        if context:

            is_canceled = await context.is_task_canceled_async()
            # is_canceled = await context.is_task_canceled_async()
            if context.is_failed:
                return {
                    "success": False,
                    "message": context.failure_reason,
                    "is_failed": True,
                    "is_canceled": is_canceled
                }
            if is_canceled:
                logger.info(f"检测到任务已被取消，停止等待任务块 {task_block_id} 的AMR选择")
                return {
                    "success": False,
                    "message": "任务已被取消",
                    "is_canceled": True
                }
        response = await get_task_block_detail(task_block_id, token)
        # print("response", response, "====")
        if not response or not response.get("success", False):
            logger.info(f"获取任务块详情失败，继续重试，当前尝试次数: {retry_count}")
            await asyncio.sleep(actual_retry_interval)
            continue
            
        # 从响应中获取关键字段
        result = response.get("result", {})
        amr_id = result.get("amrId", "")
        
        # 符合以下条件之一就可以返回:
        # 1. amrId有值 - 表示已经分配了AMR
        # 2. appointAmrId有值 - 表示指定了特定AMR
        # 3. appointAmrGroupId有值 - 表示指定了AMR组
        if amr_id:
            if amr_id:
                logger.info(f"任务块 {task_block_id} 已选择AMR: {amr_id}，共尝试 {retry_count} 次")
            return response
        
        # 否则继续等待
        if retry_count % 10 == 0:
            # 每10次请求记录一次INFO级别日志
            logger.info(f"任务块 {task_block_id} AMR选择未完成(amrId/appointAmrId/appointAmrGroupId均为空)，已尝试 {retry_count} 次，继续等待...")
        else:
            # 其他时候记录DEBUG级别日志，减少日志数量
            logger.debug(f"任务块 {task_block_id} AMR选择未完成，已尝试 {retry_count} 次")
            
        # 等待0.5秒后继续尝试
        await asyncio.sleep(actual_retry_interval) 


async def wait_for_task_block_action_completion(task_block_id: str, token: str = None, context = None) -> Optional[Dict[str, Any]]:
    """
    等待任务块动作完成，轮询任务块动作详情直到获取到动作完成
    
    Args:
        task_block_id: 任务块ID
        token: 认证令牌
    Returns:
        Optional[Dict[str, Any]]: 包含AMR ID的响应，如果请求失败则返回None
    """
    logger.info(f"开始等待任务块 {task_block_id} 的动作完成")
    
    retry_count = 0
    # 使用固定的重试间隔0.5秒
    actual_retry_interval = 1
    
    # 无限循环，直到获取到结果
    while True:
        if context:
            is_canceled = await context.is_task_canceled_async()
            if context.is_failed:
                return {
                    "success": False,
                    "message": context.failure_reason,
                    "is_failed": True,
                    "is_canceled": is_canceled
                }
            if is_canceled:
                logger.info(f"检测到任务已被取消，停止等待任务块 {task_block_id} 的AMR选择")
                return {
                    "success": False,
                    "message": "任务已被取消",
                    "is_canceled": True
                }
                # break
        retry_count += 1
        response = await get_task_block_action_detail(task_block_id, token)
        # print("response", response, "=============")
        if not response or not response.get("success", False):
            logger.info(f"获取任务块动作 详情失败，继续重试，当前尝试次数: {retry_count}")
            await asyncio.sleep(actual_retry_interval)
            continue
            
        # 从响应中获取关键字段
        result = response.get("result", {})
        action_status = result.get("status", "")

        if action_status in [3, 4, 5]:
            logger.info(f"任务块 {task_block_id} 动作已完成，共尝试 {retry_count} 次")
            return response
        
        # 否则继续等待
        if retry_count % 10 == 0:
            # 每10次请求记录一次INFO级别日志
            logger.info(f"任务块 {task_block_id} 动作未完成(actionStatus为空)，已尝试 {retry_count} 次，继续等待...")
        else:
            # 其他时候记录DEBUG级别日志，减少日志数量
            logger.debug(f"任务块 {task_block_id} 动作未完成，已尝试 {retry_count} 次")
            
        # 等待0.5秒后继续尝试
        await asyncio.sleep(actual_retry_interval) 

async def set_task_in_progress(task_id: str, token: str = None) -> Optional[ApiResponse]:
    """
    设置系统任务状态为执行中
    
    Args:
        task_id: 系统任务ID
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_IN_PROGRESS_PATH.format(id=task_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"
    
    try:
        logger.info(f"正在设置系统任务状态为执行中: {task_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功设置系统任务状态为执行中: {task_id}")
                    else:
                        logger.error(f"设置系统任务状态为执行中失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用设置任务状态接口失败: {str(e)}")
        return None

async def set_task_completed(task_id: str, token: str = None) -> Optional[ApiResponse]:
    """
    设置系统任务状态为已完成
    
    Args:
        task_id: 系统任务ID
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_COMPLETED_PATH.format(id=task_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"


    try:
        logger.info(f"正在设置系统任务状态为已完成: {task_id}")
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功设置系统任务状态为已完成: {task_id}")
                    else:
                        logger.error(f"设置系统任务状态为已完成失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用设置系统任务状态为已完成接口失败: {str(e)}")
        return None

async def set_task_terminated(task_id: str, token: str = None) -> Optional[ApiResponse]:
    """
    设置系统任务状态为已终止
    
    Args:
        task_id: 系统任务ID
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_TERMINATED_PATH.format(id=task_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"

    try:
        logger.info(f"正在设置系统任务状态为已终止: {task_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功设置系统任务状态为已终止: {task_id}")
                    else:
                        logger.error(f"设置系统任务状态为已终止失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用设置任务状态接口失败: {str(e)}")
        return None

async def set_task_failed(task_id: str, token: str = None) -> Optional[ApiResponse]:
    """
    设置系统任务状态为已失败
    
    Args:
        task_id: 系统任务ID
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_FAILED_PATH.format(id=task_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"
    try:
        logger.info(f"正在设置任务状态为已失败: {task_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.json()
                # 尝试解析JSON
                try:                    
                    if response_text.get("success", False):
                        logger.info(f"成功设置系统任务状态为已失败: {task_id}")
                    else:
                        logger.error(f"设置系统任务状态为已失败失败: {response_text.get('message', '未知错误')}")
                    
                    return response_text
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用设置任务状态接口失败: {str(e)}")
        return None

async def set_task_description(task_id: str, description: str, token: str = None) -> Optional[ApiResponse]:
    """
    设置VWED任务描述
    
    Args:
        task_id: VWED任务ID
        description: 任务描述
        token: 认证令牌
        
    Returns:
        Optional[ApiResponse]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_DESCRIPTION_PATH.format(id=task_id)}"
    
    # 构建请求头
    headers = {}
    headers[TFApiConfig.TOKEN_HEADER] = token
    headers["x-tenant-id"] = "1000"
    headers["Content-Type"] = "application/json"
    
    # 构建请求体
    request_data = {"description": description}
    
    try:
        logger.info(f"正在设置VWED任务描述: {task_id}, 描述: {description}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                json=request_data,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功设置VWED任务描述: {task_id}")
                    else:
                        logger.error(f"设置VWED任务描述失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用设置任务描述接口失败: {str(e)}")
        return None


async def get_amr_info(token: str = None) -> Optional[Dict[str, Any]]:
    """
    获取AMR信息（旧接口）
    
    Args:
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: AMR信息响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_AMR_INFO_PATH}"
    
    # 构建请求头
    headers = {}
    if token:
        headers[TFApiConfig.TOKEN_HEADER] = token
        headers["x-tenant-id"] = "1000"
    
    try:
        logger.info("正在获取AMR信息")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功获取AMR信息，获取到 {len(response_data.get('result', []))} 个AMR")
                    else:
                        logger.warning(f"获取AMR信息失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用获取AMR信息接口失败: {str(e)}")
        return None


async def get_amr_list(token: str = None, **params) -> Optional[Dict[str, Any]]:
    """
    获取AMR列表（新接口）
    
    Args:
        token: 认证令牌
        **params: 查询参数
            - brandId: string(20), 非必填, 品牌id
            - typeId: string(20), 非必填, 类型id
            - name: string(32), 非必填, amr名称
            - isAvailable: int(1), 非必填, 0不可用 1可用
            - isAcceptTask: int(1), 非必填, 是否接单: 0不接单 1可接单
            - pageNo: int, 非必填, 当前页码 默认1
            - pageSize: int, 非必填, 每页显示条数 默认20
        
    Returns:
        Optional[Dict[str, Any]]: AMR列表响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}/amr"
    
    # 构建请求头
    headers = {}
    if token:
        headers[TFApiConfig.TOKEN_HEADER] = token
        headers["x-tenant-id"] = "1000"
    
    try:
        logger.info("正在获取AMR列表")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(
                url,
                params=params,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        records = response_data.get("result", {}).get("records", [])
                        logger.info(f"成功获取AMR列表，获取到 {len(records)} 个AMR")
                    else:
                        logger.warning(f"获取AMR列表失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用获取AMR列表接口失败: {str(e)}")
        return None


# Token管理相关变量
_cached_token = None
_token_expire_time = None

# 登录频率限制相关变量
_login_attempts = []  # 登录尝试时间记录
_max_login_attempts = 3  # 最多尝试次数
_login_window_seconds = 20  # 时间窗口（秒）

def _check_login_rate_limit() -> bool:
    """
    检查登录频率限制
    
    Returns:
        bool: True表示可以登录，False表示被限制
    """
    global _login_attempts, _max_login_attempts, _login_window_seconds
    
    current_time = datetime.now().timestamp()
    
    # 清理超过时间窗口的记录
    _login_attempts = [t for t in _login_attempts if current_time - t < _login_window_seconds]
    
    # 检查是否超过限制
    if len(_login_attempts) >= _max_login_attempts:
        return False
    
    return True

def _record_login_attempt():
    """记录登录尝试"""
    global _login_attempts
    _login_attempts.append(datetime.now().timestamp())

async def get_login_token(username: str = "vwed", password: str = "vwed_123456") -> Optional[str]:
    """
    获取登录token
    
    Args:
        username: 用户名，默认为"vwed"
        password: 密码，默认为"vwed_123456"
        
    Returns:
        Optional[str]: 认证令牌，如果获取失败则返回None
    """
    global _cached_token, _token_expire_time
    
    # 检查登录频率限制
    if not _check_login_rate_limit():
        logger.info(f"登录频率过高，{_login_window_seconds}秒内已尝试{_max_login_attempts}次，请稍后再试")
        return None
    
    # 检查是否有缓存的token且未过期
    if _cached_token and _token_expire_time:
        current_time = datetime.now().timestamp() * 1000  # 转换为毫秒
        if current_time < _token_expire_time - 60000:  # 提前1分钟刷新
            logger.debug("使用缓存的token")
            return _cached_token
    
    # 构造请求参数
    request_data = LoginRequest(
        username=username,
        password=password
    )
    
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.LOGIN_PATH}"
    
    # 构建请求头
    headers = {
        "Content-Type": "application/json"
    }
    
    try:
        # 记录登录尝试
        _record_login_attempt()
        
        logger.info(f"正在获取登录token，用户: {username}")
        logger.debug(f"登录请求参数: {request_data.model_dump_json()}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.post(
                url,
                json=request_data.model_dump(),
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        token = response_data.get("result", {}).get("token")
                        if token:
                            # 缓存token，设置过期时间为1小时后
                            _cached_token = token
                            _token_expire_time = datetime.now().timestamp() * 1000 + 3600000  # 1小时后过期
                            
                            logger.info("成功获取登录token")
                            logger.debug(f"Token: {token[:20]}...")  # 只记录token的前20个字符
                            return token
                        else:
                            logger.error("登录响应中未找到token")
                            return None
                    else:
                        logger.error(f"登录失败: {response_data.get('message', '未知错误')}")
                        return None
                        
                except json.JSONDecodeError:
                    logger.error(f"解析登录响应JSON失败: {response_text}")
                    return None
                    
    except aiohttp.ClientError as e:
        logger.error(f"调用登录接口失败: {str(e)}")
        return None
    except Exception as e:
        logger.error(f"获取登录token时发生错误: {str(e)}")
        return None


async def refresh_token_if_needed() -> Optional[str]:
    """
    根据需要刷新token
    
    Returns:
        Optional[str]: 有效的认证令牌，如果获取失败则返回None
    """
    global _cached_token, _token_expire_time
    
    # 检查token是否即将过期（提前5分钟刷新）
    if _token_expire_time:
        current_time = datetime.now().timestamp() * 1000
        if current_time >= _token_expire_time - 300000:  # 5分钟内过期
            logger.info("Token即将过期，正在刷新...")
            return await get_login_token()
    
    # 如果没有token，获取新的
    if not _cached_token:
        logger.info("没有缓存的token，正在获取...")
        return await get_login_token()
    
    return _cached_token


def clear_cached_token():
    """
    清除缓存的token
    """
    global _cached_token, _token_expire_time
    _cached_token = None
    _token_expire_time = None
    logger.info("已清除缓存的token")


def get_login_token_sync(username: str = "vwed", password: str = "vwed_123456") -> Optional[str]:
    """
    获取登录token (同步版本)
    
    Args:
        username: 用户名，默认为"vwed"
        password: 密码，默认为"vwed_123456"
        
    Returns:
        Optional[str]: 认证令牌，如果获取失败则返回None
    """
    global _cached_token, _token_expire_time
    
    # 检查登录频率限制
    if not _check_login_rate_limit():
        logger.info(f"登录频率过高，{_login_window_seconds}秒内已尝试{_max_login_attempts}次，请稍后再试")
        return None
    
    # 检查是否有缓存的token且未过期
    if _cached_token and _token_expire_time:
        current_time = datetime.now().timestamp() * 1000  # 转换为毫秒
        if current_time < _token_expire_time - 60000:  # 提前1分钟刷新
            logger.debug("使用缓存的token")
            return _cached_token
    
    # 构造请求参数
    request_data = LoginRequest(
        username=username,
        password=password
    )
    
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}{TFApiConfig.LOGIN_PATH}"
    
    # 构建请求头
    headers = {
        "Content-Type": "application/json",
        "X-Tenant-Id": "1000"
    }
    
    try:
        # 记录登录尝试
        _record_login_attempt()
        
        logger.info(f"正在获取登录token，用户: {username}")
        logger.debug(f"登录请求参数: {request_data.model_dump_json()}")
        
        response = requests.post(
            url,
            json=request_data.model_dump(),
            headers=headers,
            timeout=TFApiConfig.TIMEOUT
        )
        
        # 尝试解析JSON
        try:
            response_data = response.json()
            
            if response_data.get("success", False):
                token = response_data.get("result", {}).get("token")
                if token:
                    # 缓存token，设置过期时间为1小时后
                    _cached_token = token
                    _token_expire_time = datetime.now().timestamp() * 1000 + 3600000  # 1小时后过期
                    
                    logger.info("成功获取登录token")
                    logger.debug(f"Token: {token[:20]}...")  # 只记录token的前20个字符
                    return token
                else:
                    logger.error("登录响应中未找到token")
                    return None
            else:
                logger.error(f"登录失败: {response_data.get('message', '未知错误')}")
                return None
                
        except ValueError as e:
            logger.error(f"解析登录响应JSON失败: {response.text}")
            return None
            
    except requests.RequestException as e:
        logger.error(f"调用登录接口失败: {str(e)}")
        return None
    except Exception as e:
        logger.error(f"获取登录token时发生错误: {str(e)}")
        return None


def refresh_token_if_needed_sync() -> Optional[str]:
    """
    根据需要刷新token (同步版本)
    
    Returns:
        Optional[str]: 有效的认证令牌，如果获取失败则返回None
    """
    global _cached_token, _token_expire_time
    
    # 检查token是否即将过期（提前5分钟刷新）
    if _token_expire_time:
        current_time = datetime.now().timestamp() * 1000
        if current_time >= _token_expire_time - 300000:  # 5分钟内过期
            logger.info("Token即将过期，正在刷新...")
            return get_login_token_sync()
    
    # 如果没有token，获取新的
    if not _cached_token:
        logger.info("没有缓存的token，正在获取...")
        return get_login_token_sync()
    
    return _cached_token


async def get_amr_charge_params(token: str = None) -> Optional[Dict[str, Any]]:
    """
    查询所有机器人充电阈值
    
    Args:
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: 充电阈值信息响应，如果请求失败则返回None
    """
    try:
        logger.info("正在查询所有机器人充电阈值")
        
        # 使用新的AMR列表接口
        response = await get_amr_list(token)
        if not response or not response.get("success"):
            logger.warning("获取AMR列表失败，无法查询充电阈值")
            return None
        
        # 提取充电相关参数并转换为蛇形命名
        amr_list = response.get("result", {}).get("records", [])
        charge_params = []
        
        for amr in amr_list:
            charge_info = {
                "id": amr.get("id", ""),
                "name": amr.get("name", ""),
                "exchange_power": amr.get("exchangePower", 0),
                "task_power": amr.get("taskPower", 0), 
                "charge_power": amr.get("chargePower", 0),
                "min_power": amr.get("minPower", 0),
                "max_power": amr.get("maxPower", 0)
            }
            charge_params.append(charge_info)
        
        result = {
            "success": True,
            "message": "success",
            "result": charge_params
        }
        
        logger.info(f"成功查询到 {len(charge_params)} 个机器人的充电阈值")
        return result
        
    except Exception as e:
        logger.error(f"查询所有机器人充电阈值异常: {str(e)}")
        return None


async def get_amr_charge_params_by_vehicles(vehicles: list, token: str = None) -> Optional[Dict[str, Any]]:
    """
    查询指定机器人充电阈值
    
    Args:
        vehicles: 机器人名称列表
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: 充电阈值信息响应，如果请求失败则返回None
    """
    try:
        logger.info(f"正在查询指定机器人充电阈值: {vehicles}")
        
        # 使用新的AMR列表接口
        response = await get_amr_list(token)
        if not response or not response.get("success"):
            logger.warning("获取AMR列表失败，无法查询充电阈值")
            return None
            
        # 提取充电相关参数并转换为蛇形命名，同时筛选指定的机器人
        amr_list = response.get("result", {}).get("records", [])
        filtered_params = []
        
        for vehicle_name in vehicles:
            for amr in amr_list:
                if amr.get("name") == vehicle_name:
                    charge_info = {
                        "id": amr.get("id", ""),
                        "name": amr.get("name", ""),
                        "exchange_power": amr.get("exchangePower", 0),
                        "task_power": amr.get("taskPower", 0), 
                        "charge_power": amr.get("chargePower", 0),
                        "min_power": amr.get("minPower", 0),
                        "max_power": amr.get("maxPower", 0)
                    }
                    filtered_params.append(charge_info)
                    break
        
        result = {
            "success": True,
            "message": "success", 
            "result": filtered_params
        }
        
        logger.info(f"成功查询到 {len(filtered_params)} 个指定机器人的充电阈值")
        return result
        
    except Exception as e:
        logger.error(f"查询指定机器人充电阈值异常: {str(e)}")
        return None


async def get_amr_detail(amr_id: str, token: str = None) -> Optional[Dict[str, Any]]:
    """
    获取AMR详情
    
    Args:
        amr_id: 机器人ID
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: AMR详情响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}/amr/{amr_id}"
    
    # 构建请求头
    headers = {}
    if token:
        headers[TFApiConfig.TOKEN_HEADER] = token
        headers["x-tenant-id"] = "1000"
    
    try:
        logger.info(f"正在获取AMR详情: {amr_id}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(
                url,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功获取AMR详情: {amr_id}")
                    else:
                        logger.warning(f"获取AMR详情失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
    except Exception as e:
        logger.error(f"调用获取AMR详情接口失败: {str(e)}")
        return None


async def modify_amr_charge_param(amr_id: str, charge_params: dict, token: str = None) -> Optional[Dict[str, Any]]:
    """
    修改机器人充电阈值
    
    Args:
        amr_id: 机器人ID
        charge_params: 充电参数字典，包含要修改的参数
        token: 认证令牌
        
    Returns:
        Optional[Dict[str, Any]]: 修改结果响应，如果请求失败则返回None
    """
    try:
        # 首先获取AMR详情，获取所有必填参数的当前值
        amr_detail_response = await get_amr_detail(amr_id, token)
        if not amr_detail_response or not amr_detail_response.get("success"):
            logger.error(f"获取AMR详情失败，无法修改充电参数: {amr_id}")
            return None
        
        amr_detail = amr_detail_response.get("result", {})
        
        # 构建完整的修改参数，包含所有必填项
        api_params = {
            # 必填项 - 从AMR详情获取当前值
            "brandId": amr_detail.get("brandId", ""),
            "typeId": amr_detail.get("typeId", ""),
            "mapId": amr_detail.get("sceneId", ""),  # sceneId对应mapId
            "ipAddress": amr_detail.get("ipAddress", ""),
            "name": amr_detail.get("name", ""),  # 添加名称字段
            "isSimulation": amr_detail.get("isSimulation", 0),
            "length": amr_detail.get("length", 0.0),
            "width": amr_detail.get("width", 0.0),
            # 充电参数 - 当前值作为默认值
            "minPower": amr_detail.get("minPower", 0),
            "maxPower": amr_detail.get("maxPower", 0),
            "chargePower": amr_detail.get("chargePower", 0),
            "taskPower": amr_detail.get("taskPower", 0),
            "exchangePower": amr_detail.get("exchangePower", 0)
        }
        
        # 将蛇形命名转换为驼峰命名，并用用户传入的参数覆盖对应字段
        field_mapping = {
            "exchange_power": "exchangePower",
            "task_power": "taskPower", 
            "charge_power": "chargePower",
            "min_power": "minPower",
            "max_power": "maxPower"
        }
        
        # 用用户传入的充电参数覆盖默认值
        for key, value in charge_params.items():
            if key in field_mapping:
                api_params[field_mapping[key]] = value
            else:
                # 保持原有参数
                api_params[key] = value
        
        # 构建URL
        url = f"{TFApiConfig.BASE_URL}/amr/{amr_id}"
        
        # 构建请求头
        headers = {}
        if token:
            headers[TFApiConfig.TOKEN_HEADER] = token
            headers["x-tenant-id"] = "1000"
        headers["Content-Type"] = "application/json"
        
        logger.info(f"正在修改机器人参数: {amr_id}, 参数: {api_params}")
        
        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.put(
                url,
                json=api_params,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()
                
                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)
                    
                    if response_data.get("success", False):
                        logger.info(f"成功修改机器人参数: {amr_id}")
                    else:
                        logger.warning(f"修改机器人参数失败: {response_data.get('message', '未知错误')}")
                    
                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None
                    
    except Exception as e:
        logger.error(f"调用修改机器人参数接口失败: {str(e)}")
        return None


async def update_exchange_amr_status(original_task_id: str, exchange_amr_status: int, token: str = None) -> Optional[Dict[str, Any]]:
    """
    更新换机器人状态

    根据原始任务ID，更新vwed_exchange_amr表中创建时间最新的一条记录的选车状态

    Args:
        original_task_id: 原始任务ID
        exchange_amr_status: 选车状态
            1: 选车中
            2: 等待运行
            3: 执行中
            4: 完成换车
            5: 取消更换机器人
            6: 其他
        token: 认证令牌

    Returns:
        Optional[Dict[str, Any]]: 接口响应，如果请求失败则返回None
    """
    # 调用接口
    url = f"{TFApiConfig.BASE_URL}/amr/updateLatestExchangeStatus"

    # 构建请求头
    headers = {}
    if token:
        headers[TFApiConfig.TOKEN_HEADER] = token
        headers["x-tenant-id"] = "1000"
    headers["Content-Type"] = "application/json"

    # 构建请求体
    request_data = {
        "originalTaskId": original_task_id,
        "exchangeAmrStatus": exchange_amr_status
    }

    try:
        logger.info(f"正在更新换机器人状态: 任务ID={original_task_id}, 状态={exchange_amr_status}")

        timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.post(
                url,
                json=request_data,
                headers=headers
            ) as response:
                # 读取响应内容
                response_text = await response.text()

                # 尝试解析JSON
                try:
                    response_data = json.loads(response_text)

                    if response_data.get("success", False):
                        logger.info(f"成功更新换机器人状态: 任务ID={original_task_id}")
                    else:
                        logger.warning(f"更新换机器人状态失败: {response_data.get('message', '未知错误')}")

                    return response_data
                except json.JSONDecodeError:
                    logger.error(f"解析响应JSON失败: {response_text}")
                    return None

    except Exception as e:
        logger.error(f"调用更新换机器人状态接口失败: {str(e)}")
        return None


# import asyncio
# task_block_id = "1947858858043117570"
# station_name = "AP1"
# action = "drop"
# token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTE5MTc3MTQsInVzZXJuYW1lIjoiYWRtaW4ifQ.9NcGuKCeYxOBMU-3ppuF7sPVQvZ7YyqQGPipnOdlE94"
# param = {"param": "JackLoad"}
#
# # asyncio.run(create_task("task_record_id123", "名称", True, 1, "", token, "1936411520272753371", 1))
# asyncio.run(add_action(task_block_id, station_name, action, token, param))