package com.css.simulation.resource.scheduler.app.service;

import api.common.pojo.constants.DictConstants;
import api.common.pojo.enums.MultiSimulationResultTypeEnum;
import api.common.pojo.enums.MultiSimulationStatusEnum;
import api.common.pojo.enums.MultiSimulationTaskStatusEnum;
import api.common.pojo.param.project.MultiCreateYamlRet;
import api.common.pojo.param.project.MultiSimulationProjectParam;
import api.common.pojo.po.project.MultiSimulationProjectResultPO;
import api.common.pojo.po.project.MultiSimulationProjectTaskRecordPO;
import api.common.pojo.vo.project.MultiSimulationProjectVO;
import api.common.util.*;
import com.alibaba.fastjson.JSONObject;
import com.css.simulation.resource.scheduler.domain.service.ProjectDomainService;
import com.css.simulation.resource.scheduler.domain.service.TaskDomainService;
import com.css.simulation.resource.scheduler.infra.configuration.custom.CustomConfiguration;
import com.css.simulation.resource.scheduler.infra.configuration.kubernetes.KubernetesConfiguration;
import com.css.simulation.resource.scheduler.infra.configuration.kubernetes.KubernetesUtil;
import com.css.simulation.resource.scheduler.infra.configuration.minio.MinioConfiguration;
import com.css.simulation.resource.scheduler.infra.db.mysql.mapper.*;
import com.css.simulation.resource.scheduler.infra.entity.MultiTaskMessageEntity;
import com.css.simulation.resource.scheduler.infra.fs.minio.MinioUtil;
import com.css.simulation.resource.scheduler.infra.db.redis.CustomRedisClient;
import com.css.simulation.resource.scheduler.infra.entity.PrefixEntity;
import com.css.simulation.resource.scheduler.infra.entity.ProjectEntity;
import com.css.simulation.resource.scheduler.infra.entity.TaskEntity;
import com.css.simulation.resource.scheduler.infra.db.redis.RedisUtil;
import com.css.simulation.resource.scheduler.infra.mq.kafka.KafkaUtil;
import io.kubernetes.client.openapi.ApiClient;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.admin.Admin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TaskApplicationService {
    @Value("${minio.bucket-name}")
    private String bucketName;
    @Value("${scheduler.minio-path.project-result}")
    private String resultPathMinio;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private SimulationManualProjectTaskMapper simulationManualProjectTaskMapper;
    @Resource
    private MultiSimulationProjectTaskRecordMapper multiTaskRecordMapper;

    @Resource
    private MinioClient minioClient;
    @Resource
    private ProjectDomainService projectDomainService;
    @Resource
    private KubernetesConfiguration kubernetesConfiguration;
    @Resource
    private ApiClient apiClient;
    @Resource
    private CustomRedisClient customRedisClient;
    @Resource
    private CustomConfiguration customConfiguration;
    @Resource
    private TaskDomainService taskDomainService;

    @Resource
    private MultiSimulationProjectMapper multiSimulationProjectMapper;
    @Resource
    private MultiSimulationProjectTaskRecordMapper taskRecordMapper;

    @Resource
    private MultiSimulationSceneMapper multiSimulationSceneMapper;
    @Resource
    private MultiSimulationProjectResultMapper multiSimulationResultMapper;

    @Resource
    private MinioConfiguration minioConfiguration;

    @Resource(name = "myKafkaAdmin")
    private Admin kafkaAdminClient;
    @Value("${scheduler.linux-path.multi-pod-yaml-directory}")
    private String multiPodYamlDirectory;
    @Value("${scheduler.linux-path.temp}")
    private String linuxTempPath;
    @Value("${scheduler.minio-path.project-result}")
    private String projectResultPathOfMinio;

    //
    @Value("${scheduler.linux-path.multi-vtd-pod-ana-py-file}")
    private String multiVtdPodTemplateAnaPy;

    // -------------------------------- Comment --------------------------------

    /**
     * state 接口不能阻塞，因为会导致 pod 里调用接口超时
     *
     * @param taskId  任务id
     * @param state   任务状态
     * @param podName podName
     */
    @Async
    public void state(String taskId, String state, String podName) {
        TaskEntity taskEntity = simulationManualProjectTaskMapper.selectById(taskId);
        if (taskEntity == null) {
            log.info("收到不存在的任务的状态消息：" + taskId);
            return;
        }
        String projectId = taskEntity.getPId(); // 项目 id
        String lock1 = "taskId:" + taskId + ":state:" + state + ":pod-name:" + podName;

        customRedisClient.lock(lock1, 1L, 30 * 60L);
        try {
            // 查询相关信息
            ProjectEntity projectEntity = projectDomainService.getProjectByProjectId(projectId);
            String projectType = projectEntity.getProjectType();  // 项目类型
            String maxSimulationTime = projectEntity.getMaxSimulationTime();  // 项目类型
            String projectUserId = taskEntity.getCreateUserId();   // 用户 id
            PrefixEntity redisPrefix = projectDomainService.getRedisPrefixByUserIdAndProjectIdAndTaskId(projectUserId, projectId, taskId);
            // 判断是否完成
            boolean isCompleted;
            String nodeName = projectDomainService.getNodeNameOfPod(projectId, podName);
            final String isChoiceGpu = projectDomainService.getIsChoiceGpuByProjectId(projectId);
            if (DictConstants.TASK_RUNNING.equals(state)) {  // 运行中的 pod 无需删除
                // 将运行中的任务的 pod 名称放入 redis
                stringRedisTemplate.opsForValue().set(redisPrefix.getTaskPodKey(), podName);
                log.info("修改任务 " + taskId + " 的状态为 " + state + "，pod 名称为：" + podName);
                simulationManualProjectTaskMapper.updateStateWithStartTime(taskId, state, TimeUtil.getNowForMysql());
                return;
            } else { // 结束的 pod 都直接删除，并判断项目是否完成
                // -------------------------------- 处理状态 --------------------------------
                log.info("修改任务 {} 的状态为 {} ，pod 名称为 {} ，并删除 pod。", taskId, state, podName);
                if (DictConstants.TASK_ABORTED.equals(state)) {
                    String minioPathOfErrorLog = resultPathMinio + projectId + "/" + taskId + "error.log";
                    boolean objectExist = MinioUtil.isObjectExist(minioClient, bucketName, minioPathOfErrorLog);
                    String targetEvaluate;
                    if (objectExist) {
                        String errorString = MinioUtil.downloadToString(minioClient, bucketName, minioPathOfErrorLog);
                        String[] lines = errorString.split("\n");
                        StringBuilder errorMessage = new StringBuilder();
                        for (String line : lines) {
                            if (line.startsWith("Original Error")) {
                                errorMessage.append(line).append("\n");
                            }
                            if (line.startsWith("Possible Cause")) {
                                errorMessage.append(line);
                                break;
                            }
                        }
                        targetEvaluate = errorMessage.toString();
                    } else {
                        targetEvaluate = DictConstants.TASK_ERROR_REASON_2;
                    }
                    simulationManualProjectTaskMapper.updateFailStateWithStopTime(taskId, state, TimeUtil.getNowForMysql(), targetEvaluate);
                } else if (DictConstants.TASK_TERMINATED.equals(state)) {
                    simulationManualProjectTaskMapper.updateFailStateWithStopTime(taskId, state, TimeUtil.getNowForMysql(), DictConstants.TASK_ERROR_REASON_3);
                } else if (DictConstants.TASK_ANALYSIS.equals(state)) { // 该状态只会获得一次
                    log.info("任务 {} 的状态为 {} ，pod 名称为 {}", taskId, state, podName);
                    simulationManualProjectTaskMapper.updateSuccessStateWithStopTime(taskId, state, TimeUtil.getNowForMysql());
                    // 查询项目是否使用 CPU 生成视频
                    if (DictConstants.VIDEO_CPU.equals(isChoiceGpu)) {
                        String generateVideoKey = "task:" + taskId + ":generateVideo";
                        customRedisClient.set(generateVideoKey, "0");
                        HttpUtil.get(customConfiguration.getGenerateVideoUrl().replace("simulation-resource-video", nodeName) + "?generateVideoKey=" + generateVideoKey + "&nodeName=" + nodeName + "&projectId=" + projectId + "&projectType=" + projectType + "&maxSimulationTime=" + maxSimulationTime + "&taskId=" + taskId);
//                    HttpUtil.get("http://" + nodeName + ":8007//simulation/resource/video/generate" + "?generateVideoKey=" + generateVideoKey + "&nodeName=" + nodeName + "&projectId" + projectId + "&projectType" + projectType + "&maxSimulationTime" + maxSimulationTime + "&taskId" + taskId);
//                    videoFeignClient.generateVideo(generateVideoKey, nodeName, projectId, projectType, maxSimulationTime, taskId);
                        log.info("任务 {} 使用 CPU 生成视频开始>>>>>>>", taskId);
                        while (true) {
                            TimeUnit.SECONDS.sleep(3);
                            final String generateVideoValue = customRedisClient.get(generateVideoKey);
                            if (DictConstants.YES.equals(generateVideoValue)) {
                                customRedisClient.delete(generateVideoKey);
                                break;
                            }
                        }
                        log.info("任务 {} 使用 CPU 生成视频结束<<<<<<<", taskId);
                    }
                }
                // -------------------------------- 判断项目是否结束 --------------------------------
                isCompleted = projectDomainService.complete(redisPrefix, projectId);
                if (isCompleted) {
                    log.info("项目 " + projectId + " 已完成。");
                    //如果项目已完成先把 pod 删除，并归还并行度
                    KubernetesUtil.deletePod2(apiClient, kubernetesConfiguration.getNamespace(), podName);
                    projectDomainService.incrementOneParallelism(isChoiceGpu, nodeName);
                    projectDomainService.releaseLicense(projectDomainService.getClusterUserIdByProjectUserId(projectUserId), projectDomainService.getModelTypeByProjectIdAndProjectType(projectId, projectType), 1);
                } else {
                    log.info("项目 " + projectId + " 还未运行完成。");
                    projectDomainService.createNextPod(projectUserId, projectId, projectType, nodeName, podName);
                }
                RedisUtil.deleteByPrefix(stringRedisTemplate, redisPrefix.getTaskMessageKey());
                RedisUtil.deleteByPrefix(stringRedisTemplate, redisPrefix.getTaskPodKey());
            }
            //* -------------------------------- 打分 --------------------------------
            String lock2 = "project:" + projectId + ":completed-lock";
            try {
                if (isCompleted) {
                    customRedisClient.tryLock(lock2, 10 * 60L);
                    log.info("项目 {} 开始打分。", projectId);
                    taskDomainService.score(redisPrefix.getProjectRunningKey(), projectUserId, projectId, projectType);
                    log.info("项目 {} 计算评价等级。", projectId);
                    taskDomainService.evaluationLevel(projectId);
                    log.info("项目 {} 开始释放资源。", projectId);
                    taskDomainService.done(redisPrefix, projectId, projectType);
                    log.info("项目 {} 运行结束。", projectId);
                }
            } finally {
                customRedisClient.unlock(lock2);
            }
        } catch (io.kubernetes.client.openapi.ApiException apiException) {
            log.error("POD：" + podName + "已删除。", apiException);
        } catch (Exception e) {
            log.error("项目 {} 已结束。", projectId, e);
        } finally {
            customRedisClient.unlock(lock1);
        }

    }

    @Async
    public void multiState(String taskId, String state, String podName) {
        log.info("多模式仿真任务反馈状态taskId:{},state:{},podName:{}", taskId, state, podName);
        MultiSimulationProjectTaskRecordPO taskEntity = multiTaskRecordMapper.selectMultiSimulationProjectTaskRecordById(taskId);
        if (taskEntity == null) {
            log.info("多模式仿真收到不存在的任务的状态消息：" + taskId);
            return;
        }

        String projectId = taskEntity.getProjectId(); // 项目 id
        String minioUploadPath = projectId + "/" + taskId + "/";

        String lock1 = "taskId:" + taskId + ":state:" + state + ":pod-name:" + podName;
        customRedisClient.lock(lock1, 1L, 30 * 60L);
        String isChoiceGpu = DictConstants.USE_GPU;
        try {
            MultiSimulationProjectVO projectVO = multiSimulationProjectMapper.selectMultiSimulationProjectById(projectId);
//            Integer status = projectVO.getProjectStatus();
            MultiSimulationProjectParam multiSimulationProjectParam = new MultiSimulationProjectParam();
            multiSimulationProjectParam.setProjectId(projectId);
            String projectUserId = projectVO.getProjectUserId();

            // 查询相关信息
            String nodeNameKey = "multi-taskId:" + taskEntity.getId();
            String value = stringRedisTemplate.opsForValue().get(nodeNameKey);
            MultiCreateYamlRet multiCreateYamlRet = JSONObject.parseObject(value, MultiCreateYamlRet.class);
            String nodeName = multiCreateYamlRet.getNodeName();
            if (DictConstants.TASK_RUNNING.equals(state)) {  // 运行中的 pod 无需删除
                // 将运行中的任务的 pod 名称放入 redis
                log.info("修改任务 " + taskId + " 的状态为 " + state + "，pod 名称为：" + podName);
                multiTaskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationStatusEnum.RUN_STATUS.getProjectStatus(),
                    taskEntity.getId(), 0);
                return;
            }else {
                // 结束的 pod 都直接删除，并判断项目是否完成
                log.info("修改任务 {} 的状态为 {} ，pod 名称为 {} ，并删除 pod。", taskId, state, podName);
                // 已完成
                // 删除pod
                try {
                    KubernetesUtil.deletePod2(apiClient, kubernetesConfiguration.getMultiNamespace(), podName);
                }catch (Exception e){
                    log.info("删除pod失败,projectId:{},taskId:{}",projectId,taskId, e);
                }
                // 并行度加一
                projectDomainService.incrementOneMultiParallelism(isChoiceGpu, nodeName);
                // 释放证书
                projectDomainService.releaseLicense(projectDomainService.getClusterUserIdByProjectUserId(projectUserId), DictConstants.MODEL_TYPE_VTD, 1);
                // 删除nodeName相关信息
                stringRedisTemplate.delete(nodeNameKey);
                // 执行完成待分析状态
                if (DictConstants.TASK_ANALYSIS.equals(state)){
                    taskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationTaskStatusEnum.NEED_ANALYSIS_STATUS.getProjectStatus(), taskEntity.getId(), 0);
                    // 进行仿真评价
                    String taskBody = taskEntity.getTaskBody();
                    MultiTaskMessageEntity messageEntity = JSONObject.parseObject(taskBody, MultiTaskMessageEntity.class);
                    String taskPath = messageEntity.getInfo().getTask_path();
                    List<String> list = MinioUtil.listAllFileName(minioClient, bucketName, taskPath);
                    String linuxPath = linuxTempPath + "multiProject/" + projectId + "/" + taskId + "/";

                    String csvName = null;
                    String allMp4Url = null;
                    String simulationMp4Url = null;
                    for (String str : list) {
                        String split = str.substring(str.lastIndexOf("/") + 1);
                        if (StringUtils.contains(split, "xml") || StringUtils.contains(split, "xodr") || StringUtils.contains(split, "osgb")){
                            continue;
                        }
                        if (StringUtils.contains(split, ".mp4")) {
                            if (StringUtils.contains(split, "_custom_camera") || StringUtils.contains(split, "_overlook_camera")){
//                                allMp4Url = split;
                                allMp4Url = str;
                            }else {
//                                simulationMp4Url = split;
                                simulationMp4Url = str;
                            }
                        }else{
                            csvName = split;
                            String linuxFile = linuxPath + split;
                            String minioPathCsv = taskPath + split;
                            MinioUtil.downloadToFile(minioClient, bucketName, minioPathCsv, linuxFile);
                        }
                    }
                    // 文件存在
                    if (StringUtils.isNotBlank(csvName)) {
                        String pythonCom = "python3 " + multiVtdPodTemplateAnaPy + " --csvFile=" + linuxPath + "" + " --outputResultFile=" + linuxPath + "result.json";
                        LinuxUtil.execute(pythonCom);
                        Thread.sleep(10000);
                        // 获取到json结果,并插入数据库
                        String podString = FileUtil.read(new File(linuxPath + "result.json"));
                        JSONObject jsonObject = JSONObject.parseObject(podString).getJSONObject("scenario");
                        MultiSimulationProjectResultPO multiSimulationProjectResultPO = new MultiSimulationProjectResultPO();
                        multiSimulationProjectResultPO.setId(StringUtil.getRandomUUID()).setSceneId(taskEntity.getSceneId())
                            .setAbnormalTimeDescription(jsonObject.getString("collision_time"))
                            .setProjectId(projectId)
                            .setAbnormalType(MultiSimulationResultTypeEnum.COLLISION.getResultType());
                        multiSimulationResultMapper.insertProjectResult(multiSimulationProjectResultPO);
                        multiSimulationProjectResultPO = new MultiSimulationProjectResultPO();
                        multiSimulationProjectResultPO.setId(StringUtil.getRandomUUID()).setSceneId(taskEntity.getSceneId()).setProjectId(projectId)
                            .setAbnormalTimeDescription(jsonObject.getString("line_crossing_time")).setAbnormalType(MultiSimulationResultTypeEnum.ABNORMAL_PARKING.getResultType());
                        multiSimulationResultMapper.insertProjectResult(multiSimulationProjectResultPO);
                        multiSimulationProjectResultPO = new MultiSimulationProjectResultPO();
                        multiSimulationProjectResultPO.setId(StringUtil.getRandomUUID()).setSceneId(taskEntity.getSceneId()).setProjectId(projectId)
                            .setAbnormalTimeDescription(jsonObject.getString("lane_departure")).setAbnormalType(MultiSimulationResultTypeEnum.OUT_OF_PAVEMENT.getResultType());
                        multiSimulationResultMapper.insertProjectResult(multiSimulationProjectResultPO);
                        multiSimulationProjectResultPO = new MultiSimulationProjectResultPO();
                        multiSimulationProjectResultPO.setId(StringUtil.getRandomUUID()).setSceneId(taskEntity.getSceneId()).setProjectId(projectId)
                            .setAbnormalTimeDescription(jsonObject.getString("phrases")).setAbnormalType(MultiSimulationResultTypeEnum.LAST_DESCRIPTION.getResultType());
                        multiSimulationResultMapper.insertProjectResult(multiSimulationProjectResultPO);
//                        String urlMp4Pub = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
//                                .method(Method.GET)
//                                .bucket(bucketName)
//                                .object(allMp4Url)
//                                .build())
//                            .replace(minioConfiguration.getEndpointPrivate(), minioConfiguration.getEndpointPublic());
//                        String simUrlMp4Pub = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
//                                .method(Method.GET)
//                                .bucket(bucketName)
//                                .object(simulationMp4Url)
//                                .build())
//                            .replace(minioConfiguration.getEndpointPrivate(), minioConfiguration.getEndpointPublic());

//                        String csvUrlPub = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
//                                .method(Method.GET)
//                                .bucket(bucketName)
//                                .object(minioPathCsv)
//                                .build())
//                            .replace(minioConfiguration.getEndpointPrivate(), minioConfiguration.getEndpointPublic());
                        multiSimulationSceneMapper.updateSceneResultById("", allMp4Url, simulationMp4Url, taskEntity.getSceneId());
                    } else {
                        log.info("taskId:{}未找到csv文件", taskId);
                    }
                    taskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationTaskStatusEnum.COMPLETED_STATUS.getProjectStatus(), taskEntity.getId(), 0);
                } else if (DictConstants.TASK_ABORTED.equals(state)) {
                    taskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationTaskStatusEnum.AUTO_TERMINATED_STATUS.getProjectStatus(), taskEntity.getId(), 0);
                } else if (DictConstants.TASK_TERMINATED.equals(state)) {
                    taskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationTaskStatusEnum.TERMINATED_STATUS.getProjectStatus(), taskEntity.getId(), 0);
                } else {
                    // 终止
                    log.info("未知的反馈状态类型projectId:{},taskId:{},state:{}", projectId, taskId, state);
//                    return;
//                    taskRecordMapper.updateMultiSimulationProjectTaskRecordStatus(MultiSimulationStatusEnum.TERMINATED_STATUS.getProjectStatus(), taskEntity.getId(), 0);
//                    log.info("taskId:{},项目已经停止", taskId);
                }
            }
            List<MultiSimulationProjectTaskRecordPO> recordPOList = multiTaskRecordMapper.selectMultiSimulationProjectTaskRecordList(projectId);
            // 如果所有的都已经完成，则进行后续处理
            boolean allCom = true;
            if (!CollectionUtils.isEmpty(recordPOList)){
                for (MultiSimulationProjectTaskRecordPO record: recordPOList) {
                    Integer status = record.getStatus();
                    if (status == MultiSimulationStatusEnum.INIT_STATUS.getProjectStatus() || status == MultiSimulationStatusEnum.RUN_STATUS.getProjectStatus()){
                        allCom = false;
                    }
                }
            }
            if (allCom){
                multiSimulationProjectParam.setProjectStatus(MultiSimulationStatusEnum.COMPLETED_STATUS.getProjectStatus());
                multiSimulationProjectMapper.updateMultiSimulationProjectStatus(multiSimulationProjectParam);
                // 删除kafka topic
                // todo 暂时不删除
                KafkaUtil.deleteTopic(kafkaAdminClient, projectId);
                //6 删除项目 pod 启动文件
                FileUtil.deleteFileBySubstring(multiPodYamlDirectory, projectId);
                //7 删除项目临时文件
                FileUtil.rm(linuxTempPath + "multiProject/" + projectId + "/");
                // 删除minio临时文件
                // 暂时不删除minio文件，用来排查问题
//                MinioUtil.rmR(minioClient, bucketName, projectResultPathOfMinio + projectId + "/");
                // 删除算法key
                // 删除yaml路径redis
                // 删除记录podNamekey
                RedisUtil.deleteByPrefix(stringRedisTemplate, "multi_project:" + projectId);
            }
        }
//        catch (io.kubernetes.client.openapi.ApiException apiException) {
//            log.info("POD：" + podName + "已删除。");
//        }
        catch (Exception e) {
            log.error("项目 {} 已结束。", projectId, e);
        } finally {
            customRedisClient.unlock(lock1);
        }
    }


    public Boolean confirm(String taskId) {
        return taskDomainService.taskConfirm(taskId);
    }

    public Boolean multiConfirm(String taskId) {
        MultiSimulationProjectTaskRecordPO multiSimulationProjectTaskRecordPO = multiTaskRecordMapper.selectMultiSimulationProjectTaskRecordById(taskId);
        if (Objects.isNull(multiSimulationProjectTaskRecordPO)){
            return false;
        }
        Integer status = multiSimulationProjectTaskRecordPO.getStatus();
        if (status == MultiSimulationTaskStatusEnum.TERMINATED_STATUS.getProjectStatus() ||
            status == MultiSimulationTaskStatusEnum.COMPLETED_STATUS.getProjectStatus() ||
            status == MultiSimulationTaskStatusEnum.AUTO_TERMINATED_STATUS.getProjectStatus() ||
            status == MultiSimulationTaskStatusEnum.NEED_ANALYSIS_STATUS.getProjectStatus() ||
            status == MultiSimulationTaskStatusEnum.TERMINATED_STATUS.getProjectStatus()){
            return false;
        }

        if (status == MultiSimulationTaskStatusEnum.RUN_STATUS.getProjectStatus() || status == MultiSimulationTaskStatusEnum.INIT_STATUS.getProjectStatus()){
            return true;
        }
        return false;
    }

    public void tick(String taskId) {
        taskDomainService.taskTick(taskId);
    }

    public void multiTick(String taskId) {
        log.info("多模式仿真收到任务 " + taskId + " 的心跳。");
        MultiSimulationProjectTaskRecordPO taskRecordPO = multiTaskRecordMapper.selectMultiSimulationProjectTaskRecordById(taskId);
        if (taskRecordPO == null) {
            return;
        }
        String projectId = taskRecordPO.getProjectId();
        // 刷新 redis 心跳时间
        String multiTaskTickPodRedisKey = taskDomainService.getMultiTaskTickPodRedisKey(taskId, projectId);
        stringRedisTemplate.opsForValue().set(multiTaskTickPodRedisKey, TimeUtil.getNowString());
    }
}



