package net.northking.cctp.executePlan.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.s3.NKFile;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.executePlan.api.service.AtuPlanInfoApiService;
import net.northking.cctp.executePlan.api.service.AtuPlanTaskApiService;
import net.northking.cctp.executePlan.api.service.MessageCenterService;
import net.northking.cctp.executePlan.api.third.feilang.service.FeiLangService;
import net.northking.cctp.executePlan.config.AtuPlanConfig;
import net.northking.cctp.executePlan.constants.MsgConstant;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RabbitConstant;
import net.northking.cctp.executePlan.constants.RedisConstant;
import net.northking.cctp.executePlan.db.entity.*;
import net.northking.cctp.executePlan.db.service.*;
import net.northking.cctp.executePlan.dto.planBatch.*;
import net.northking.cctp.executePlan.dto.planInfo.AtuPlanRunDto;
import net.northking.cctp.executePlan.dto.planTask.ScriptStatusStatistic;
import net.northking.cctp.executePlan.enums.MobilePlatformEnum;
import net.northking.cctp.executePlan.feign.PublicFeignClient;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * <p>Title: PlanBatchTaskDataUpdateJob</p>
 * <p>Description: 计划批次任务数据更新</p>
 *
 * @Author chenjianfeng
 * @Version V1.0
 * @Date 2022/12/13
 * <p>Copyright (c) 京北方信息技术股份有限公司 Corporation 2022 . All rights reserved.</p>
 */
@EnableScheduling
@Configuration
public class PlanBatchTaskDataUpdateJob {

    private final static Logger logger = LoggerFactory.getLogger(PlanBatchTaskDataUpdateJob.class);

    @Autowired
    private AtuPlanInfoService planInfoService;

    @Autowired
    private AtuPlanBatchService planBatchService;

    @Autowired
    private AtuPlanTaskService planTaskService;

    @Autowired
    private AtuPlanTaskApiService planTaskApiService;

    @Autowired
    private AtuPlanInfoApiService planInfoApiService;

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private AtuPlanScriptLinkService planScriptLinkService;

    @Autowired
    private AtuPlanAppLinkService planAppLinkService;

    @Autowired
    private AtuPlanDeviceLinkService planDeviceLinkService;

    @Autowired
    private PublicFeignClient publicFeignClient;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private FeiLangService feiLangService;

    @Autowired
    private SimpleStorageService simpleStorageService;

    @Autowired
    private AtuPlanEnvLinkService planEnvLinkService;

    @Autowired
    MessageCenterService messageCenterService;

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    private final static String LOCK_KEY_BATCH_SUM = "LOCK:PLAN:BATCH-SUM-DATA-UPDATE-METHOD";

    private final static String BATCH_IS_NULL_COUNT_HASH = "PLAN:BATCH:NULL-COUNT-HASH";

    @Autowired
    private AtuPlanConfig atuPlanConfig;

    public static final int EXECUTOR_CORE_POOL_SIZE = 2 * 2 + 2;
    public static final int EXECUTOR_MAX_POOL_SIZE = 10;
    public static final int EXECUTOR_LINKED_BLOCKING_SIZE = 50;

    private static final ExecutorService executor = ExecutorBuilder.create()
            .setCorePoolSize(EXECUTOR_CORE_POOL_SIZE)
            .setMaxPoolSize(EXECUTOR_MAX_POOL_SIZE)
            .setWorkQueue(new LinkedBlockingDeque<>(EXECUTOR_LINKED_BLOCKING_SIZE))
            .build();

    @Scheduled(fixedRateString = "${atu.plan.batchCountUpdateJob:20000}")
    private void batchSumDataUpdate() {
        // 统计加锁，多节点只需要一个节点启动
        Lock lock = redisLockRegistry.obtain(LOCK_KEY_BATCH_SUM);
        if (!lock.tryLock()) {  // 没有拿到锁退出方法，有别的节点获取到锁进行统计
            return;
        }
        try {
            logger.debug("同步缓存中计划批次统计数据----start----");
            // 1. 获取缓存中所有的批次统计数据
            String patternKey = RedisConstant.CLUSTER_KEY_PREFIX + "*" + RedisConstant.CLUSTER_KEY_SUFFIX +
                    RedisConstant.BATCH_SCRIPT_SUM_KEY + "*";
            Set<String> batchKeySet = redisTemplate.keys(patternKey);
            if (CollUtil.isEmpty(batchKeySet)) {
                logger.debug("不存在批次缓存统计数据----end----");
                return;
            }
            logger.debug("缓存中计划批次统计数据量 => " + batchKeySet.size());
            batchKeySet.forEach(key -> {
                Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
                if (CollUtil.isEmpty(entries)) {
                    logger.info("缓存[" + key + "]中无统计数据");
                    // 没有统计数据，删掉key
                    redisTemplate.delete(key);
                    return;
                }
                String batchId = key.substring(key.lastIndexOf(":") + 1);
                String clusterKeyPrefix = RedisConstant.CLUSTER_KEY_PREFIX + batchId.substring(0, 4) +
                        RedisConstant.CLUSTER_KEY_SUFFIX;

                // 通过数据库进行统计，减少统计数据出错的原因
                AtuPlanBatch batch = this.planBatchService.findByPrimaryKey(batchId);
                if (batch == null) {
                    logger.info("批次【{}】信息目前不存在，再等待下一次查询...", batchId);
                    Boolean existFlag = redisTemplate.opsForHash().hasKey(BATCH_IS_NULL_COUNT_HASH, batchId);
                    if (existFlag) {
                        Object nullCount = redisTemplate.opsForHash().get(BATCH_IS_NULL_COUNT_HASH, batchId);
                        if (nullCount != null && NumberUtil.isNumber(nullCount.toString())) {
                            int i = Integer.parseInt(nullCount.toString());
                            if (i < 100) {  // 统计100次后依然没有批次信息，批次不存在
                                redisTemplate.opsForHash().put(BATCH_IS_NULL_COUNT_HASH, batchId, i + 1);
                            } else {
                                // 没有对应批次数据，删除缓存数据
                                logger.debug("没有对应的批次信息[{}]，删除缓存数据", batchId);
                                redisTemplate.delete(key);
                            }
                        }
                    } else {
                        redisTemplate.opsForHash().put(BATCH_IS_NULL_COUNT_HASH, batchId, 1);
                    }
                    return; // 退出这个批次统计
                }
                // 批次信息存在
                AtuPlanTask totalQ = new AtuPlanTask();
                totalQ.setBatchId(batchId);
                long currentTaskTotal = planTaskService.count(totalQ);
                // 判断批次任务是否已经全部生成
                if (batch.getTaskTotal() > currentTaskTotal) {
                    logger.info("批次[{}]正在生成任务中，结束统计...", batchId);
                    return;
                }
                // 以脚本维度统计批次下各脚本的执行情况
                AtuPlanTask taskQ = new AtuPlanTask();
                taskQ.setBatchId(batchId);
                List<ScriptStatusStatistic> resultList = planTaskService.countTaskStatus(taskQ);
                List<String> batchStatusList = new ArrayList<>();
                for (ScriptStatusStatistic result : resultList) {
                    Set<String> statusSet = result.getStatusSet();
                    result.setStatus(scriptStatus(statusSet));  // 根据脚本下的所有任务确定脚本的执行结果
                    batchStatusList.add(result.getStatus());
                }
                int allScriptCount = batchStatusList.size(); // 总脚本执行数
                // 等待中
                int waitCount = Collections.frequency(batchStatusList, PlanConstant.TASK_WAIT_EXECUTE_STATUS);
                // 执行中
                int ingCount = Collections.frequency(batchStatusList, PlanConstant.TASK_START_EXECUTE_STATUS);
                // 成功
                int successCount = Collections.frequency(batchStatusList, PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
                // 执行失败
                int executeFailCount = Collections.frequency(batchStatusList, PlanConstant.TASK_EXECUTE_FAIL_STATUS);
                // 断言失败
                int assertFailCount = Collections.frequency(batchStatusList, PlanConstant.TASK_ASSERT_FAIL_STATUS);
                // 取消的
                int cancelCount = Collections.frequency(batchStatusList, PlanConstant.TASK_CANCEL_STATUS);
                // 超时的
                int timeoutCount = Collections.frequency(batchStatusList, PlanConstant.TASK_TIMEOUT_STATUS);

                if (waitCount > 0 || ingCount > 0) {    // 批次还没有完成
                    // 检查批次的开始时间
                    Date createdTime = batch.getCreatedTime();
                    if (createdTime != null) {  // 有创建时间，那么比较开始时间与现在的时间差
                        LocalDateTime createDateTime = createdTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                        LocalDateTime now = LocalDateTime.now();
                        Duration duration = Duration.between(createDateTime, now);
                        long hours = duration.toHours();
                        if (atuPlanConfig.isBatchExecuteTimeoutEnabled() && hours > atuPlanConfig.getBatchExecuteTimeout().longValue()) {  // 参数化
                            logger.info("批次[{}-{}]已创建{}小时，任务没有执行完成，强制结束批次", batch.getId(), batch.getBatch(), 48);
                            // 如果批次的创建时间离现在已经过去48小时还没完成，那么判定批次已完成，等待中|执行中 的任务全部算超时处理
                            timeoutCount += waitCount;
                            timeoutCount += ingCount;
                            waitCount = 0;
                            ingCount = 0;
                            // 更新批次下 等待中（0）|执行中（1） 的任务全部算超时处理（6）
                            this.planTaskService.batchUpdateTaskStatusToTimeout(batch.getId(), PlanConstant.TASK_TIMEOUT_STATUS, PlanConstant.TASK_WAIT_EXECUTE_STATUS, PlanConstant.TASK_START_EXECUTE_STATUS);
                        } else {
                            // 批次还没有完成，退出统计
                            return;
                        }
                    } else {
                        // 有批次信息，但是没有创建时间，应该是数据有问题
                        logger.debug("批次[{}]没有创建时间", batch.getId());
                        return;
                    }
                }
                // 批次已完成

                // 更新批次表
                AtuPlanBatch planBatch = new AtuPlanBatch();
                planBatch.setId(batchId);
                planBatch.setSuccessNum(successCount);
                planBatch.setExecFailNum(executeFailCount);
                planBatch.setAssertFailNum(assertFailCount);
                planBatch.setTimeoutNum(timeoutCount);
                planBatch.setCancelNum(cancelCount);

                planBatch.setSuccessRate(MsgConstant.PLAN_BATCH_SUCCESS_RATE);
                if (planBatch.getSuccessNum() > 0 && allScriptCount > 0) {
                    planBatch.setSuccessRate(NumberUtil.decimalFormat(MsgConstant.PATTERN_HASH_DOT_HASH_HASH, NumberUtil.div(planBatch.getSuccessNum().intValue(), allScriptCount)));
                }
                // 查询批次最先开始执行的任务的开始时间
                if (planBatch.getStartTime() == null) {
                    Long firstTaskStartTime = planTaskService.queryBatchFirstTaskStartTime(batchId);
                    if (ObjectUtil.isNotNull(firstTaskStartTime)) {
                        planBatch.setStartTime(new Date(firstTaskStartTime));
                    }
                }
                // 任务状态判断
                String planStatus = "";
                if (planBatch.getCancelNum() > 0) {
                    planStatus = PlanConstant.PLAN_CANCEL_STATUS;
                    // 修改为取消状态
                    planBatch.setStatus(PlanConstant.BATCH_CANCEL_STATUS);
                } else {
                    planStatus = PlanConstant.PLAN_FINISH_STATUS;
                    // 修改为已完成状态
                    planBatch.setStatus(PlanConstant.BATCH_FINISH_STATUS);
                }

                // 查询批次最后执行完成的任务的结束时间
                Long lastTaskEndTime = planTaskService.queryBatchLastTaskEndTime(batchId);
                if (ObjectUtil.isNotNull(lastTaskEndTime)) {
                    planBatch.setEndTime(new Date(lastTaskEndTime));
                }
                logger.debug("更新批次信息");
                planBatchService.updateByPrimaryKey(planBatch);

                // 删除任务队列
                amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_PC_KEY + batchId);
                // 移动端删除各个平台对于的队列
                MobilePlatformEnum[] platformEnums = MobilePlatformEnum.values();
                for (MobilePlatformEnum platformEnum : platformEnums) {
                    amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_MOB_KEY + platformEnum.getName() + "." + batchId);
                }
                amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_API_KEY + batchId);

                //获取更新后的批次信息
                AtuPlanBatchDetailDto atuPlanBatchDetailDto = planBatchService.queryBatchDetailById(planBatch.getId());

                // 判断是否计划最后一批次
                AtuPlanInfo planInfo = planInfoService.queryByLastBatchId(batchId);
                if (ObjectUtil.isNotNull(planInfo)) {
                    // 是则更新计划表
                    planInfo.setStatus(planStatus);
                    planInfo.setWaitingNum(waitCount);
                    planInfo.setRunningNum(ingCount);
                    planInfo.setSuccessNum(planBatch.getSuccessNum());
                    planInfo.setExecFailNum(planBatch.getExecFailNum());
                    planInfo.setAssertFailNum(planBatch.getAssertFailNum());
                    planInfo.setTimeoutNum(planBatch.getTimeoutNum());
                    planInfo.setCancelNum(planBatch.getCancelNum());
                    //不用代码生成的方法
                    planInfoService.updatePlanResultById(planInfo);
                }

                executor.execute(() -> {
                    // 保存设备性能数据
                    saveDevicePerData(atuPlanBatchDetailDto);
                });

                // 2.2. 删除缓存中已完成批次的记录
                logger.debug("删除批次统计缓存 => " + key);
                redisTemplate.delete(key);

                // 收尾处理
                AtuPlanInfo batchPlanInfo = planInfoService.findByBatchId(batchId);
                if (batchPlanInfo != null) {
                    handleEnd(batchPlanInfo, atuPlanBatchDetailDto);
                }
            });
            logger.debug("同步缓存中计划批次统计数据----end----");
        } catch (Exception e) {
            logger.error("批次统计数据执行发生异常", e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 失败重试检查
     * @param planInfo 计划信息
     * @param planBatch 批次信息
     * @return 是否需要重试
     */
    private boolean failRetryCheck(AtuPlanInfo planInfo, AtuPlanBatch planBatch) {
        if (planInfo.getFailRetry() && PlanConstant.TASK_RETRY_STRATEGY_PACKAGE.equals(planInfo.getRetryStrategy())) {
            Object o = redisTemplate.opsForHash().get(RedisConstant.PLAN_BATCH_RETRY_COUNT, planBatch.getId());
            if (o != null) {
                int count = Integer.parseInt(o.toString());
                logger.info("计划-批次【{}-{}】重试次数还剩下：{}次", planInfo.getPlanName(), planBatch.getBatch(), count);
                if (count == 0) {   // 重试已完成
                    redisTemplate.opsForHash().delete(RedisConstant.PLAN_BATCH_RETRY_COUNT, planBatch.getId());
                    return false;
                }
                // 查询批次下的失败任务
                List<String> taskIds = planTaskService.queryAllFailIdList(planBatch.getId());
                List<Object> resultIds = new ArrayList<>(taskIds);
                BatchRetryDto dto = new BatchRetryDto();
                dto.setTaskIdList(resultIds);
                logger.info("计划-批次【{}-{}】需要重试的任务数量：{}", planInfo.getPlanName(), planBatch.getBatch(), taskIds.size());
                if (!taskIds.isEmpty()) {
                    redisTemplate.opsForHash().put(RedisConstant.PLAN_BATCH_RETRY_COUNT, planBatch.getId(), String.valueOf(--count));
                    planTaskApiService.taskRetry(dto);
                } else {
                    redisTemplate.opsForHash().delete(RedisConstant.PLAN_BATCH_RETRY_COUNT, planBatch.getId()); // 没有失败的任务，清除redis信息，不需要重试
                    return false;
                }
                return true;
            }
        }
        return false;
    }

    private void saveDevicePerData(AtuPlanBatchDetailDto atuPlanBatchDetailDto) {
        String batchId = atuPlanBatchDetailDto.getId();
        String tenantId = atuPlanBatchDetailDto.getTenantId();
        if (StrUtil.isBlank(tenantId)) {
            logger.error("获取批次计划中的租户id异常");
            return;
        }
        logger.debug("统计并保存批次设备性能数据");
        // 查询该批次下所有的移动端任务性能文件地址
        List<String> perDataPathList = planTaskService.queryMobilePerDataPathByBatchId(batchId);
        if (CollUtil.isEmpty(perDataPathList)) {
            logger.debug("该批次无性能数据");
            return;
        }
        logger.debug("perDataPathList => {}", JSONUtil.toJsonStr(perDataPathList));
        Map<String, BatchMobilePerformanceDto> deviceAppPerMap = new HashMap<>();
        for (String perDataPath : perDataPathList) {
            MobileTaskPerformanceDto taskPerInfo = null;
            try {
                taskPerInfo = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
                        tenantId, perDataPath, MobileTaskPerformanceDto.class);
            } catch (Exception e) {
                logger.error("获取任务性能数据异常：{}", perDataPath);
            }
            logger.debug("taskPerInfo => {}", JSONUtil.toJsonStr(taskPerInfo));
            if (taskPerInfo == null) {
                logger.debug("任务性能数据为空：{}", perDataPath);
                continue;
            }
            String deviceAppKey = taskPerInfo.getDeviceId() + "-" + taskPerInfo.getAppId();
            BatchMobilePerformanceDto deviceAppPer = new BatchMobilePerformanceDto();
            if (deviceAppPerMap.containsKey(deviceAppKey)) {
                deviceAppPer = deviceAppPerMap.get(deviceAppKey);
            }
            // 添加安装时间
            deviceAppPer.setAppInstallTimeList(defaultListAddData(deviceAppPer.getAppInstallTimeList(), taskPerInfo.getAppInstallTime()));
            // 添加启动时间
            deviceAppPer.setAppActiveTimeList(defaultListAddData(deviceAppPer.getAppActiveTimeList(), taskPerInfo.getAppActiveTime()));
            deviceAppPer.setAppCpuList(defaultListAddList(deviceAppPer.getAppCpuList(), taskPerInfo.getAppCpuList()));
            deviceAppPer.setAppMemoList(defaultListAddList(deviceAppPer.getAppMemoList(), taskPerInfo.getAppMemoList()));
            deviceAppPer.setAppFlowList(defaultListAddList(deviceAppPer.getAppFlowList(), taskPerInfo.getAppFlowList()));
            deviceAppPer.setDeviceCpuList(defaultListAddList(deviceAppPer.getDeviceCpuList(), taskPerInfo.getDeviceCpuList()));
            deviceAppPer.setDeviceMemoList(defaultListAddList(deviceAppPer.getDeviceMemoList(), taskPerInfo.getDeviceMemoList()));
            deviceAppPer.setDeviceFlowList(defaultListAddList(deviceAppPer.getDeviceFlowList(), taskPerInfo.getDeviceFlowList()));
            deviceAppPer.setDeviceTemperatureList(defaultListAddList(deviceAppPer.getDeviceTemperatureList(), taskPerInfo.getDeviceTemperatureList()));
            deviceAppPerMap.put(deviceAppKey, deviceAppPer);
        }
        logger.debug("deviceAppPerMap => {}", JSONUtil.toJsonStr(deviceAppPerMap));
        if (CollUtil.isEmpty(deviceAppPerMap)) {
            logger.debug("任务性能数据整合后为空");
            return;
        }

        List<BatchAppInfoDto> appInfoDtoList = new ArrayList<>();
        deviceAppPerMap.forEach((deviceAppKey, deviceAppPer) -> {
            BatchAppInfoDto appInfoDto = new BatchAppInfoDto();
            appInfoDto.setDeviceId(deviceAppKey.split("-")[0]);
            appInfoDto.setAppId(deviceAppKey.split("-")[1]);

            // APP安装耗时
            appInfoDto.setInstallTime(getListAvgValueDiv1000(deviceAppPer.getAppInstallTimeList(), MsgConstant.LETTER_S));
            appInfoDto.setMaxInstallTime(getListMaxValueDiv1000(deviceAppPer.getAppInstallTimeList(), MsgConstant.LETTER_S));

            // app启动耗时
            appInfoDto.setStartElapsedTime(getListAvgValueDiv1000(deviceAppPer.getAppActiveTimeList(), MsgConstant.LETTER_S));
            appInfoDto.setMaxStartElapsedTime(getListMaxValueDiv1000(deviceAppPer.getAppActiveTimeList(), MsgConstant.LETTER_S));

            // app cpu使用率
            appInfoDto.setAppCpu(getListAvgValue(deviceAppPer.getAppCpuList(), MsgConstant.PERCENTAGE));
            appInfoDto.setMaxAppCpu(getListMaxValue(deviceAppPer.getAppCpuList(), MsgConstant.PERCENTAGE));

            // app内存使用
            appInfoDto.setAppMemory(getListAvgValue(deviceAppPer.getAppMemoList(), MsgConstant.LETTER_M));
            appInfoDto.setMaxAppMemory(getListMaxValue(deviceAppPer.getAppMemoList(), MsgConstant.LETTER_M));

            // app流量使用
            appInfoDto.setAppFlow(getListAvgValue(deviceAppPer.getAppFlowList(), MsgConstant.LETTER_M));
            appInfoDto.setMaxAppFlow(getListMaxValue(deviceAppPer.getAppFlowList(), MsgConstant.LETTER_M));

            // 设备cpu使用率
            appInfoDto.setDeviceCpu(getListAvgValue(deviceAppPer.getDeviceCpuList(), MsgConstant.PERCENTAGE));
            appInfoDto.setMaxDeviceCpu(getListMaxValue(deviceAppPer.getDeviceCpuList(), MsgConstant.PERCENTAGE));

            // 设备内存使用
            appInfoDto.setDeviceMemory(getListAvgValue(deviceAppPer.getDeviceMemoList(), MsgConstant.LETTER_M));
            appInfoDto.setMaxDeviceMemory(getListMaxValue(deviceAppPer.getDeviceMemoList(), MsgConstant.LETTER_M));

            // 设备流量使用
            appInfoDto.setDeviceFlow(getListAvgValue(deviceAppPer.getDeviceFlowList(), MsgConstant.LETTER_M));
            appInfoDto.setMaxDeviceFlow(getListMaxValue(deviceAppPer.getDeviceFlowList(), MsgConstant.LETTER_M));

            // 设备电池温度
            appInfoDto.setCellTemperature(getListAvgValue(deviceAppPer.getDeviceTemperatureList(), MsgConstant.TEMPERATURE_DEGREE));
            appInfoDto.setMaxCellTemperature(getListMaxValue(deviceAppPer.getDeviceTemperatureList(), MsgConstant.TEMPERATURE_DEGREE));
            appInfoDtoList.add(appInfoDto);
        });

        logger.debug("设备性能数据为：{}", appInfoDtoList.size());
        // 保存整合数据
        File file = MinioPathUtils.objectToJsonFile(appInfoDtoList);
        try {
            NKFile nkFile = simpleStorageService.upload(tenantId, file, true, batchId, FileBusinessTypeEnum.BATCH_TASK_PERFORMANCE_SUMMARY);
            String path = nkFile.getId();
            logger.debug("整合数据保存地址 => {}", path);
            AtuPlanBatch byPrimaryKey = this.planBatchService.findByPrimaryKey(batchId);
            String oldPerDataPath = byPrimaryKey.getPerDataPath();
            // 保存批次性能文件地址
            AtuPlanBatch planBatch = new AtuPlanBatch();
            planBatch.setId(batchId);
            planBatch.setPerDataPath(path);
            this.planBatchService.updateByPrimaryKey(planBatch);
            // 删除旧的性能文件
            if (StrUtil.isNotBlank(oldPerDataPath)){
                String[] pathUrl = MinioPathUtils.idToPath(oldPerDataPath);
                try {
                    simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
                } catch (Exception e) {
                    logger.error("文件删除删除失败", e);
                }
            }
        } catch (Exception e) {
            logger.error("文件上传异常", e);
        }
    }

    private String getListMaxValue(List<Double> list, String union) {
        String maxValue = "0.00" + union;
        if (CollUtil.isNotEmpty(list)) {
            double max = list.stream().mapToDouble(Double::doubleValue).max().orElse(0);
            maxValue = max + union;
        }
        return maxValue;
    }

    private String getListAvgValue(List<Double> list, String union) {
        String avgValue = MsgConstant.ZERO_DOT + union;
        if (CollUtil.isNotEmpty(list)) {
            double avg = list.stream().mapToDouble(Double::doubleValue).average().orElse(0);
            avgValue = NumberUtil.roundStr(avg, 2) + union;
        }
        return avgValue;
    }

    private String getListMaxValueDiv1000(List<Long> list, String union) {
        String maxValue = MsgConstant.ZERO_DOT + union;
        if (CollUtil.isNotEmpty(list)) {
            long max = list.stream().mapToLong(Long::longValue).max().orElse(0);
            double maxDiv = NumberUtil.div(max, 1000);
            maxValue = NumberUtil.round(maxDiv, 2) + union;
        }
        return maxValue;
    }

    private String getListAvgValueDiv1000(List<Long> list, String union) {
        String avgValue = MsgConstant.ZERO_DOT + union;
        if (CollUtil.isNotEmpty(list)) {
            double avg = list.stream().mapToLong(Long::longValue).average().orElse(0);
            double avgDiv = NumberUtil.div(avg, 1000);
            avgValue = NumberUtil.round(avgDiv, 2) + union;
        }
        return avgValue;
    }

    private static List<Long> defaultListAddData(List<Long> list, Long data) {
        if (CollUtil.isEmpty(list)) {
            list = CollUtil.newArrayList();
        }
        if (data == null) {
            return list;
        }
        list.add(data);
        return list;
    }

    private static List<Double> defaultListAddList(List<Double> list, List<Double> data) {
        if (CollUtil.isEmpty(list)) {
            list = CollUtil.newArrayList();
        }
        if (CollUtil.isEmpty(data)) {
            return list;
        }
        list.addAll(data);
        return list;
    }

    /**
     * 收尾处理
     *
     * @param planInfo  批次计划信息
     * @param planBatch 批次信息
     */
    private void handleEnd(AtuPlanInfo planInfo, AtuPlanBatchDetailDto planBatch) {
        if (PlanConstant.BATCH_CANCEL_STATUS.equals(planBatch.getStatus())) {   // 如果是取消，不需要执行收尾处理
            return;
        }
        // 检查计划是否需要重试，如果需要并且是执行完成批次后再执行失败的任务，那么获取失败任务，批量重试。
        if (PlanConstant.BATCH_FINISH_STATUS.equals(planBatch.getStatus()) && planInfo.getFailRetry()) {
            boolean isRetry = false;
            try {
                isRetry = failRetryCheck(planInfo, planBatch);
            } catch (Exception e) {
                logger.error("批次{}下失败任务重试失败", planBatch.getId(), e);
            }
            if (isRetry) {
                logger.info("计划需要对失败的任务进行重试...");
                return;
            }
        }
        // 计划是否被其他计划设为前置执行计划
        List<String> planIdList = planInfoService.queryFollowUpPlanIdList(planInfo.getId());
        if (CollUtil.isNotEmpty(planIdList)) {
            for (String id : planIdList) {
                AtuPlanRunDto atuPlanRunDto = new AtuPlanRunDto();
                atuPlanRunDto.setPlanId(id);
                atuPlanRunDto.setExecutedBy(planInfo.getPrincipalId());
                atuPlanRunDto.setTriggerType(PlanConstant.TRIGGER_TYPE_AUTO);
                planInfoApiService.run(atuPlanRunDto);
            }
        }
        // 计划是否需要发送邮件，失败用例是否生成失败计划
        NKSecurityContext.setTenantId(planInfo.getTenantId());
        NKSecurityContext.setUserId(planInfo.getCreatedBy());
        // 发送到飞狼

        executor.execute(() -> {
            RLock rLock = redisson.getLock(RedisConstant.BATCH_MESSAGE_LOCK_PRE + planBatch.getId());
            try {
                if (rLock.tryLock(0, 90, TimeUnit.SECONDS)) {
                    //推送消息中心弹窗
                    try {
                        messageCenterService.sendMessage(planInfo, planBatch);
                    } catch (Exception e) {
                        logger.error("推送消息中心失败" + e);
                    } finally {
                        rLock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                logger.info("推送消息中心失败" + planBatch.getId());
            }
            //判断是否发送到飞狼 此处
            if (planInfo.getSendMail()) {
                // 判断飞狼user是否存在
                if (StrUtil.isNotBlank(planInfo.getMailAddress())) {
                    try {
                        feiLangService.sendMessage(planInfo, planBatch);
                    } catch (Exception e) {
                        logger.error("飞狼推送失败", e);
                    }
                }
            }
        });

        // 判断执行失败是否生成新的计划
        if (planInfo.getFailCreatePlan()) {
            // 判断是否有执行失败的任务
            if (planBatch.getExecFailNum() > 0) {
                // 生成新的计划信息
                createFailCasePlan(planInfo, planBatch);
            }
        }
    }

    /**
     * 生成失败用例计划
     *
     * @param planInfo  原计划信息
     * @param planBatch 批次信息
     */
    private void createFailCasePlan(AtuPlanInfo planInfo, AtuPlanBatchDetailDto planBatch) {
        String batchId = planBatch.getId();
        // 根据失败任务查询失败用例，并生成新的用例集
        AtuPlanTask queryParams = new AtuPlanTask();
        queryParams.setBatchId(batchId);
        List<AtuPlanTask> planTaskList = planTaskService.query(queryParams);
        if (CollUtil.isEmpty(planTaskList)) {
            logger.debug("批次[{}]无任务生成", batchId);
            return;
        }

        // 将任务根据脚本进行分组
        Map<String, List<AtuPlanTask>> scriptTaskMap = planTaskList.stream()
                .collect(Collectors.groupingBy(AtuPlanTask::getScriptId));
        Set<Map.Entry<String, List<AtuPlanTask>>> entries = scriptTaskMap.entrySet();
        String newPlanId = IdUtil.simpleUUID();
        List<AtuPlanScriptLink> scriptLinkList = new ArrayList<>();
        for (Map.Entry<String, List<AtuPlanTask>> entry : entries) {
            String scriptId = entry.getKey();
            List<AtuPlanTask> scriptTaskList = entry.getValue();
            // 将任务分成成功和失败
            List<AtuPlanTask> successTaskList = scriptTaskList.stream()
                    .filter(task -> task.getStatus().equals(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS))
                    .collect(Collectors.toList());
            // 失败任务
            scriptTaskList.removeAll(successTaskList);
            if (CollUtil.isEmpty(scriptTaskList)) {
                continue;
            }
            // 将脚本绑定至新计划
            AtuPlanTask planTask = scriptTaskList.get(0);
            AtuPlanScriptLink scriptLink = new AtuPlanScriptLink();
            scriptLink.setPlanId(newPlanId);
            scriptLink.setScriptId(scriptId);
            scriptLink.setScriptType(planTask.getCaseType());
            scriptLinkList.add(scriptLink);
        }

        if (CollUtil.isEmpty(scriptLinkList)) {
            logger.debug("任务全部成功");
            return;
        }

        // 复制计划信息（修改计划名称，去除触发方式信息）
        AtuPlanInfo newPlan = new AtuPlanInfo();
        BeanUtil.copyProperties(planInfo, newPlan);
        newPlan.setId(newPlanId);
        String planName = planInfo.getPlanName() + "-" + planBatch.getBatch() + "-失败用例";
        // 校验名称是否存在
        int num = 0;
        while (true) {
            AtuPlanInfo countPlan = new AtuPlanInfo();
            String name = num == 0 ? planName : planName + "(" + num + ")";
            countPlan.setPlanName(name);
            long count = planInfoService.count(countPlan);
            if (count > 0) {
                num++;
            } else {
                planName = name;
                break;
            }
        }
        newPlan.setPlanName(planName);
        newPlan.setCronExpr("");
        newPlan.setUpFrontPlanId("");
        newPlan.setWebhookUrl("");
        newPlan.setWebhookToken("");
        //补充
        newPlan.setHasCorn(false);
        newPlan.setHasUpFront(false);
        newPlan.setHasWebhook(false);
        newPlan.setLastBatchId("");
        newPlan.setWaitingNum(0);
        newPlan.setRunningNum(0);
        newPlan.setSuccessNum(0);
        newPlan.setExecFailNum(0);
        newPlan.setAssertFailNum(0);
        newPlan.setTimeoutNum(0);
        newPlan.setCancelNum(0);
        //重置状态为新建
        newPlan.setStatus(PlanConstant.PLAN_NEW_STATUS);
        newPlan.setLastExecTime(null);
        newPlan.setNextExecTime(null);
        newPlan.setPrincipalId(planBatch.getExecutedBy());
        planInfoService.insert(newPlan);

        // 绑定应用
        AtuPlanAppLink appLinkQuery = new AtuPlanAppLink();
        appLinkQuery.setPlanId(planInfo.getId());
        List<AtuPlanAppLink> appLinkList = planAppLinkService.query(appLinkQuery);
        if (CollUtil.isNotEmpty(appLinkList)) {
            for (AtuPlanAppLink planAppLink : appLinkList) {
                planAppLink.setId(IdUtil.simpleUUID());
                planAppLink.setPlanId(newPlan.getId());
            }
            planAppLinkService.insertByBatch(appLinkList);
        }
        // 绑定设备
        AtuPlanDeviceLink deviceLinkQuery = new AtuPlanDeviceLink();
        deviceLinkQuery.setPlanId(planInfo.getId());
        List<AtuPlanDeviceLink> deviceLinkList = planDeviceLinkService.query(deviceLinkQuery);
        if (CollUtil.isNotEmpty(deviceLinkList)) {
            for (AtuPlanDeviceLink planDeviceLink : deviceLinkList) {
                planDeviceLink.setId(IdUtil.simpleUUID());
                planDeviceLink.setPlanId(newPlan.getId());
            }
            planDeviceLinkService.insertByBatch(deviceLinkList);
        }

        // 绑定环境
        AtuPlanEnvLink envLinkQuery = new AtuPlanEnvLink();
        envLinkQuery.setPlanId(planInfo.getId());
        List<AtuPlanEnvLink> envLinkList = planEnvLinkService.query(envLinkQuery);
        List<AtuPlanEnvLink> newEnvLinkList = new ArrayList<>();
        if (CollUtil.isNotEmpty(envLinkList)) {
            for (AtuPlanEnvLink envLink : envLinkList) {
                AtuPlanEnvLink newEnvLink = new AtuPlanEnvLink();
                newEnvLink.setPlanId(newPlanId);
                newEnvLink.setEnvId(envLink.getEnvId());
                newEnvLink.setSort(envLink.getSort());
                newEnvLinkList.add(newEnvLink);
            }
            planEnvLinkService.insertByBatch(newEnvLinkList);
        }

        // 绑定脚本
        planScriptLinkService.insertByBatch(scriptLinkList);
    }

    /**
     * 用于确定脚本的执行结果状态
     * @param statusSet 脚本下的所有任务状态
     * @return
     */
    private String scriptStatus(Set<String> statusSet) {
        String status = PlanConstant.TASK_EXECUTE_SUCCESS_STATUS;   // 默认成功
        if (statusSet == null || statusSet.isEmpty()) { // 如果状态为空，那么脚本成功
            return status;
        }
        boolean flag = statusSet.remove(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
        if (flag) { // 如果存在等待（0）
            if (statusSet.isEmpty()) {  // 全都是等待（0）
                status = PlanConstant.TASK_WAIT_EXECUTE_STATUS;
            } else {    // 还有其他状态
                status = PlanConstant.TASK_START_EXECUTE_STATUS;
            }
        } else {    // 不存在等待（0）
            if (statusSet.remove(PlanConstant.TASK_START_EXECUTE_STATUS)) {  // 存在执行中(1)
                status = PlanConstant.TASK_START_EXECUTE_STATUS;
            } else if (statusSet.remove(PlanConstant.TASK_CANCEL_STATUS)) {  // 存在取消（5）
                status = PlanConstant.TASK_CANCEL_STATUS;
            } else if (statusSet.remove(PlanConstant.TASK_TIMEOUT_STATUS)) { // 存在超时（6）
                status = PlanConstant.TASK_TIMEOUT_STATUS;
            } else if (statusSet.remove(PlanConstant.TASK_ASSERT_FAIL_STATUS)) {   // 存在断言失败（4）
                status = PlanConstant.TASK_ASSERT_FAIL_STATUS;
            } else if (statusSet.remove(PlanConstant.TASK_EXECUTE_FAIL_STATUS)) {    // 存在执行失败（3）
                status = PlanConstant.TASK_EXECUTE_FAIL_STATUS;
            }
        }
        return status;
    }
}
