/*
* Copyright (c) 京北方信息技术股份有限公司 Corporation 2022 . All rights reserved.
*
*/
package net.northking.cctp.executePlan.api.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
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.json.JSONUtil;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.constants.BaseDefineDependencyConstants;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.exception.InvalidException;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.vo.ResponseVO;
import net.northking.cctp.common.cache.EntityNameCache;
import net.northking.cctp.common.cache.EntityNameCatalog;
import net.northking.cctp.common.db.BasicService;
import net.northking.cctp.common.db.Pagination;
import net.northking.cctp.common.db.excel.AbstractExcelService;
import net.northking.cctp.common.db.excel.ExcelHeader;
import net.northking.cctp.common.dto.AssociatedFilesDto;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.common.util.UUIDUtil;
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.AtuPlanBatch;
import net.northking.cctp.executePlan.db.entity.AtuPlanInfo;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.*;
import net.northking.cctp.executePlan.dto.planBatch.AtuBatchTaskPageDto;
import net.northking.cctp.executePlan.dto.planBatch.AtuExceptionCase;
import net.northking.cctp.executePlan.dto.planBatch.AtuPlanBatchPageDto;
import net.northking.cctp.executePlan.dto.planBatch.AtuPlanBatchQueryDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskPageDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskQueryDto;
import net.northking.cctp.executePlan.dto.planTask.StepExecuteResult;
import net.northking.cctp.executePlan.feign.AttachmentFeignClient;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;

/**
 * 计划执行批次表服务实现类
 *
 * @author maven-cctp-plugin
 * @since 1.0
 */

@Service
@ConditionalOnMissingBean(name = "AtuPlanBatchApiServiceImpl")
@RefreshScope
public class AtuPlanBatchApiServiceImpl extends AbstractExcelService<AtuPlanBatch> implements AtuPlanBatchApiService
{
  /**
   * 日志
   */
  private final static Logger logger = LoggerFactory.getLogger(AtuPlanBatchApiServiceImpl.class);

  @Autowired
  private AtuPlanBatchService atuPlanBatchService;

  @Autowired
  private AtuPlanInfoService atuPlanInfoService;

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  @Autowired
  private AtuPlanTaskService planTaskService;

  @Autowired
  private AtuPlanBatchDeviceLinkService planBatchDeviceLinkService;

  @Autowired
  private AtuPlanTaskApiService atuPlanTaskApiService;

  @Autowired
  private AmqpTemplate amqpTemplate;


  @Value("${atu.plan.taskTimeout}")
  private long taskTimeout;

  @Value("${atu.plan.planReportSaveDay}")
  private Long planReportSaveDay;

  @Autowired
  private EntityNameCache entityNameCache;

  @Autowired
  private SimpleStorageService simpleStorageService;

  @Autowired
  private AtuPlanSceneCaseTaskService atuPlanSceneCaseTaskService;

  @Autowired
  private AttachmentFeignClient attachmentFeignClient;
  /**
   * 1分钟的毫秒数
   */
  public static final long MINUTE_MILLISECOND = 60000;

  public static final int EXECUTOR_CORE_POOL_SIZE = 2 * 2 + 2;
  public static final int EXECUTOR_MAX_POOL_SIZE = 20;
  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();

  private static final DefaultRedisScript<Boolean> BATCH_SCRIPT_SUM_DATA_SCRIPT;

  static {
    BATCH_SCRIPT_SUM_DATA_SCRIPT = new DefaultRedisScript<>();
    BATCH_SCRIPT_SUM_DATA_SCRIPT.setLocation(new ClassPathResource("/lua/batchScriptSumDataCache.lua"));
    BATCH_SCRIPT_SUM_DATA_SCRIPT.setResultType(Boolean.class);
  }

  @PostConstruct
  public void init(){
    logger.debug("初始化计划批次号至缓存中 --- start ---");
    this.initPlanBatchNumToCache();
    logger.debug("初始化计划批次号至缓存中 --- end ---");
  }

  //= Excel 导入导出相关代码 start ==================//
  @Override
  public BasicService<AtuPlanBatch> getService()
  {
    return this.atuPlanBatchService;
  }

  @Override
  public ExcelHeader[] excelHeaders()
  {
    ExcelHeader id = new ExcelHeader("主键", "id", String.class);
    ExcelHeader planId = new ExcelHeader("计划主键", "planId", String.class);
    ExcelHeader batch = new ExcelHeader("任务批次", "batch", Integer.class);
    ExcelHeader status = new ExcelHeader("状态;0-等待中，1-执行中，2-已完成，3-已取消", "status", String.class);
    ExcelHeader startTime = new ExcelHeader("调度开始时间", "startTime", Date.class);
    ExcelHeader endTime = new ExcelHeader("调度结束时间", "endTime", Date.class);
    ExcelHeader taskTotal = new ExcelHeader("任务总数", "taskTotal", Integer.class);
    ExcelHeader successNum = new ExcelHeader("成功任务数", "successNum", Integer.class);
    ExcelHeader execFailNum = new ExcelHeader("执行失败数", "execFailNum", Integer.class);
    ExcelHeader assertFailNum = new ExcelHeader("断言失败数", "assertFailNum", Integer.class);
    ExcelHeader timeoutNum = new ExcelHeader("超时任务数", "timeoutNum", Integer.class);
    ExcelHeader cancelNum = new ExcelHeader("取消任务数", "cancelNum", Integer.class);
    ExcelHeader successRate = new ExcelHeader("成功率", "successRate", String.class);
    ExcelHeader createdTime = new ExcelHeader("创建时间", "createdTime", Date.class);

    return new ExcelHeader[]{};
  }

  /**
   * Excel导入，入库之前的处理
   * @param records 从Excel读取到的记录
   * @return 待入库的记录
   */
  @Override
  protected List<AtuPlanBatch> beforeInsertBatch(List<AtuPlanBatch> records)
  {
    for (AtuPlanBatch record : records)
    {
      record.setId(UUIDUtil.create32UUID());
    }
    return records;
  }

  /**
   * 写入Excel之前的处理<br>
   * 数据的特殊处理在此处加工处理
   *
   * @param record 来自数据库记录
   * @return 待写入Excel的记录
   */
  @Override
  protected AtuPlanBatch beforeWriteExcel(AtuPlanBatch record)
  {
    return record;
  }

  //= Excel 导入导出相关代码 end =======================//

  //= 增删改查 相关代码 start ==================//
  @Override
  public Pagination<AtuPlanBatchPageDto> pagingQuery(QueryByPage<AtuPlanBatchQueryDto> queryByPage)
  {
    long queryStartTime = System.currentTimeMillis();
    Pagination<AtuPlanBatchPageDto> query = this.atuPlanBatchService.pagingQuery(queryByPage);
    AtuPlanInfo planInfo = this.atuPlanInfoService.findByPrimaryKey(queryByPage.getQuery().getPlanId());
    long queryEndTime = System.currentTimeMillis();
    logger.info("查询分页信息耗时：" + (queryEndTime - queryStartTime));
    long startCreateData = System.currentTimeMillis();
    for (AtuPlanBatchPageDto dto : query.getRecords()) {
      getCacheScriptStatusData(dto);
      dto.setPlanName(planInfo.getPlanName());
      dto.setScreenRecordSet(planInfo.getScreenRecordSet());
      dto.setExecutedBy(entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, dto.getExecutedBy()));
      dto.setElapsedTime("-");
      if (dto.getStartTime() != null && dto.getEndTime() != null){
        dto.setElapsedTime(DateUtil.formatBetween(dto.getEndTime().getTime() - dto.getStartTime().getTime(),
                BetweenFormatter.Level.MILLISECOND));
      }
    }
    long endCreateData = System.currentTimeMillis();
    logger.info("组装数据耗时：" + (endCreateData - startCreateData));
    return query;
  }

  /**
   * 获取缓存中脚本统计数据
   * @param dto 批次信息
   */
  private void getCacheScriptStatusData(AtuPlanBatchPageDto dto){
    String clusterKeyPrefix = RedisConstant.CLUSTER_KEY_PREFIX + dto.getId().substring(0,4) +
                RedisConstant.CLUSTER_KEY_SUFFIX;
    String scriptSumKey = clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + dto.getId();
    Map<Object, Object> sumDataCacheMap = redisTemplate.opsForHash().entries(scriptSumKey);
    Object startTimeObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.BATCH_START_TIME);
    if (startTimeObj != null && dto.getStartTime() == null) {
      dto.setStartTime(DateUtil.parse(DateUtil.format(new Date(Long.parseLong(startTimeObj.toString())), PlanConstant.DATETIME_FORMAT)));
    }
    Object successObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
    if (successObj != null) {
      dto.setSuccessNum(Integer.parseInt(successObj.toString()));
    }
    Object execFailObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_EXECUTE_FAIL_STATUS);
    if (execFailObj != null) {
      dto.setExecFailNum(Integer.parseInt(execFailObj.toString()));
    }
    Object cancelObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_CANCEL_STATUS);
    int cancelNum = 0;
    if (cancelObj != null){
      cancelNum = Integer.parseInt(cancelObj.toString());
      dto.setCancelNum(cancelNum);
    }
    Object timeoutObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_TIMEOUT_STATUS);
    if (timeoutObj != null){
      dto.setTimeoutNum(Integer.parseInt(timeoutObj.toString()));
    }
    Object totalObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.SCRIPT_TOTAL);
    int totalNum = 0;
    if (totalObj != null) {
      totalNum = Integer.parseInt(totalObj.toString());
      dto.setScriptNum(totalNum);
    }
    Object runningObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_START_EXECUTE_STATUS);
    Object waitingObj = sumDataCacheMap.get(clusterKeyPrefix + PlanConstant.TASK_WAIT_EXECUTE_STATUS);
    if (totalObj != null && runningObj != null && waitingObj != null){

      int waitingNum = Integer.parseInt(waitingObj.toString());
      int runningNum = Integer.parseInt(runningObj.toString());
      boolean isBegin = waitingNum != 0 || runningNum != 0;
      if (isBegin && totalNum >= waitingNum){
        if (totalNum == waitingNum){
          dto.setStatus(PlanConstant.BATCH_WAITING_STATUS);
        }else {
          dto.setStatus(PlanConstant.BATCH_EXECUTING_STATUS);
        }
        // 批次正在执行中设置已有的批次结束时间为null，避免计算耗时
        dto.setEndTime(null);
      }else{
        if (waitingNum == 0 && runningNum == 0){
          dto.setStatus(PlanConstant.BATCH_FINISH_STATUS);
        }
        if (cancelNum > 0){
          dto.setStatus(PlanConstant.BATCH_CANCEL_STATUS);
        }
      }
    }
  }

    @Override
  public AtuPlanBatch findByPrimaryKey(String uuid) {
    return this.atuPlanBatchService.findByPrimaryKey(uuid);
  }

  @Override
  public Integer updateByEntity(AtuPlanBatch entity){
    return this.atuPlanBatchService.updateByEntity(entity);
  }

  @Override
  public void updateCacheBatchSumData(AtuPlanTask planTask) {
    logger.debug("批次脚本统计");
    String clusterPrefix = RedisConstant.CLUSTER_KEY_PREFIX + planTask.getBatchId().substring(0,4) +
              RedisConstant.CLUSTER_KEY_SUFFIX;
    List<String> keys = new ArrayList<>();
    keys.add(clusterPrefix + RedisConstant.BATCH_SCRIPT_STATUS_KEY + planTask.getBatchId());
    keys.add(clusterPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planTask.getBatchId());
    keys.add(clusterPrefix + RedisConstant.BATCH_SCRIPT_TASK_KEY + planTask.getBatchId() + ":" + planTask.getScriptId());
    keys.add(clusterPrefix + PlanConstant.BATCH_START_TIME);
    keys.add(clusterPrefix + PlanConstant.TASK_WAIT_EXECUTE_STATUS);
    keys.add(clusterPrefix + PlanConstant.TASK_START_EXECUTE_STATUS);
    keys.add(clusterPrefix + planTask.getScriptId());
    keys.add(clusterPrefix + planTask.getId());
    keys.add(clusterPrefix);
    redisTemplate.execute(BATCH_SCRIPT_SUM_DATA_SCRIPT, keys, planTask.getStartTime(),Integer.parseInt(planTask.getStatus()));
  }

  @Override
  public Boolean casePushNotice(List<AtuExceptionCase> exceptionCaseList) {
    if (CollUtil.isEmpty(exceptionCaseList)){
      return true;
    }
    for (AtuExceptionCase atuExceptionCase : exceptionCaseList) {
      // 生成失败任务
      AtuPlanTask planTask = new AtuPlanTask();
      BeanUtil.copyProperties(atuExceptionCase, planTask);
      planTask.setId(IdUtil.simpleUUID());
      planTask.setStatus(PlanConstant.TASK_EXECUTE_FAIL_STATUS);
      planTask.setStartTime(System.currentTimeMillis());
      planTask.setEndTime(System.currentTimeMillis());
      planTask.setCreatedTime(new Date());
      planTaskService.insert(planTask);

      // 任务开始
      planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
      updateCacheBatchSumData(planTask);

      // 任务结束
      planTask.setStatus(PlanConstant.TASK_EXECUTE_FAIL_STATUS);
      updateCacheBatchSumData(planTask);
    }
    return true;
  }

    @Override
    public void processBatchTaskTimeout(String batchId, Long lastOfflineTime){
      logger.debug("查询批次【{}】未完成的任务集合", batchId);
      List<AtuPlanTask> planTaskList = planTaskService.queryUnfinishedTaskByBatchId(batchId);
      if (CollUtil.isEmpty(planTaskList)) {
        logger.debug("批次【{}】任务已全部完成", batchId);
        return;
      }
      // 是否有在线设备
      logger.debug("判断批次【{}】是否有在线", batchId);
      Integer onlineCount = planBatchDeviceLinkService.countOnlineDeviceByBatchId(batchId);
      if (ObjectUtil.isNotNull(onlineCount)){
        logger.debug("部分设备在线，在线设备数量为：{}", onlineCount);
        return;
      }
      logger.debug("批次【{}】所有设备都离线", batchId);
      // 超过20分钟，将该批次未完成的任务修改为超时
      for (AtuPlanTask planTask : planTaskList) {

        long currentTime = System.currentTimeMillis();
        // 转换成毫秒数
        long timeout = taskTimeout * MINUTE_MILLISECOND;

        logger.debug("判断是否超过{}分钟 => {}",taskTimeout , timeout);
        if (currentTime - lastOfflineTime > timeout){
          logger.debug("任务超过{}分钟，更新任务为超时",taskTimeout);
          // 超过20分钟则更新任务表
          if (ObjectUtil.isNull(planTask.getStartTime())){
            // 任务还未开始，将任务创建时间设为任务开始时间
            planTask.setStartTime(planTask.getCreatedTime().getTime());
            planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
            this.updateCacheBatchSumData(planTask);
          }

            planTask.setStatus(PlanConstant.TASK_TIMEOUT_STATUS);
            planTask.setEndTime(System.currentTimeMillis());
            planTask.setErrorMsg(MsgConstant.DEV_OFFLINE_TIMEOUT);
            planTaskService.updateByPrimaryKey(planTask);

          this.updateCacheBatchSumData(planTask);
        }else{
          logger.debug("不超过{}分钟则计算还差多久到{}分钟", taskTimeout, taskTimeout);
          // 不超过20分钟则计算还差多久到20分钟
          long delayTime = lastOfflineTime + timeout - currentTime;
          logger.debug("任务还差【{}】 秒超时，加入到延迟队列中", NumberUtil.decimalFormat("#", NumberUtil.div(delayTime, 1000)));
          // 发送至延迟队列
          amqpTemplate.convertAndSend(RabbitConstant.TASK_TIMEOUT_DELAY_EXCHANGE_NAME,
                  RabbitConstant.TASK_TIMEOUT_DELAY_ROUTING_KEY,
                  planTask.getId(), delayMessage ->{
                    // 设置发送消息的延迟时间
                    delayMessage.getMessageProperties().setDelay((int) delayTime);
                    return delayMessage;
                  });
        }
      }
    }

  @Override
  public AtuBatchTaskPageDto queryPlanByBatch(QueryByPage<AtuPlanTaskQueryDto> queryByPage) {
    AtuBatchTaskPageDto pageDto = new AtuBatchTaskPageDto();
    Pagination<AtuPlanTaskPageDto> query = atuPlanTaskApiService.pagingQuery(queryByPage);
    pageDto.setTaskPage(query);
    AtuPlanInfo planInfo = this.atuPlanInfoService.findByPrimaryKey(queryByPage.getQuery().getPlanId());
    AtuPlanBatch atuPlanBatch = this.atuPlanBatchService.findByPrimaryKey(queryByPage.getQuery().getBatchId());
    AtuPlanBatchPageDto dto = new AtuPlanBatchPageDto();
    BeanUtils.copyProperties(atuPlanBatch,dto);

    String clusterKeyPrefix = RedisConstant.CLUSTER_KEY_PREFIX + dto.getId().substring(0,4) +
              RedisConstant.CLUSTER_KEY_SUFFIX;
    String scriptSumKey = clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + dto.getId();
    Object startTimeObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.BATCH_START_TIME);
    if (startTimeObj != null){
      dto.setStartTime(DateUtil.parse(DateUtil.format(new Date(Long.parseLong(startTimeObj.toString())), PlanConstant.DATETIME_FORMAT)));
    }

    Object successObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
    if (successObj != null){
      dto.setSuccessNum(Integer.parseInt(successObj.toString()));
    }

    Object execFailObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_EXECUTE_FAIL_STATUS);
    if (execFailObj != null){
      dto.setExecFailNum(Integer.parseInt(execFailObj.toString()));
    }

    Object assertFailObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_ASSERT_FAIL_STATUS);
    if (assertFailObj != null){
      dto.setAssertFailNum(Integer.parseInt(assertFailObj.toString()));
    }

    Object cancelObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_CANCEL_STATUS);
    int cancelNum = 0;
    if (cancelObj != null){
      cancelNum = Integer.parseInt(cancelObj.toString());
      dto.setCancelNum(cancelNum);
    }

    Object timeoutObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_TIMEOUT_STATUS);
    if (timeoutObj != null){
      dto.setTimeoutNum(Integer.parseInt(timeoutObj.toString()));
    }
    Object totalObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_TOTAL);
    Object runningObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_START_EXECUTE_STATUS);
    Object waitingObj = redisTemplate.opsForHash().get(scriptSumKey, clusterKeyPrefix + PlanConstant.TASK_WAIT_EXECUTE_STATUS);
    if (totalObj != null && runningObj != null && waitingObj != null){
      int totalNum = Integer.parseInt(totalObj.toString());
      int waitingNum = Integer.parseInt(waitingObj.toString());
      int runningNum = Integer.parseInt(runningObj.toString());
      boolean isBegin = waitingNum != 0 || runningNum != 0;
      if (isBegin && totalNum > waitingNum){
        dto.setStatus(PlanConstant.BATCH_EXECUTING_STATUS);
      }
      if (waitingNum == 0 && runningNum == 0){
        dto.setStatus(PlanConstant.BATCH_FINISH_STATUS);
      }
      if (cancelNum > 0){
        dto.setStatus(PlanConstant.BATCH_CANCEL_STATUS);
      }
    }

    dto.setPlanName(planInfo.getPlanName());
    dto.setScreenRecordSet(planInfo.getScreenRecordSet());
    //批次信息
    pageDto.setBatchDto(dto);
    return pageDto;
  }

  @Override
  public Boolean initPlanBatchNumToCache() {
    String endTime = DateUtil.now();
    int count = atuPlanBatchService.countPlanMaxBatch(endTime);
    if (count <= 0){
      logger.debug("无计划批次信息需要初始化");
      return true;
    }
    int totalBatches = NumberUtil.ceilDiv(count, PlanConstant.EXECUTOR_MAX_PROCESS_NUM);
    for (int i = 0; i < totalBatches; i++) {
      int fromIndex = i * PlanConstant.EXECUTOR_MAX_PROCESS_NUM;
      int toIndex = Math.min(fromIndex + PlanConstant.EXECUTOR_MAX_PROCESS_NUM, count);
      executor.execute(() -> {
        List<AtuPlanBatch> planBatchSubList = atuPlanBatchService.queryPlanMaxBatchNumList(endTime, fromIndex, toIndex);
        for (AtuPlanBatch planBatch : planBatchSubList) {
          redisTemplate.opsForValue().setIfAbsent(RedisConstant.PLAN_BATCH_NUM_KEY + planBatch.getPlanId(),
                  planBatch.getBatch());
        }
      });
    }
    return true;
  }

    @Override
    public ResponseVO<String> clearPlanReport() {
      if (planReportSaveDay == null) {
        throw new InvalidException("报告保存天数没有配置");
      }
//      if (planReportSaveDay < 30) {
//        throw new InvalidException("报告保存天数必须大于30天");
//      }
      new Thread(() -> {
        try {
          logger.info("开始清理...");
          LocalDateTime localDateTime = LocalDateTime.now().minusDays(planReportSaveDay);
          List<AtuPlanBatch> batchList = atuPlanBatchService.queryNeedClearBatch(localDateTime);
          List<String> batchIds = batchList.stream().map(AtuPlanBatch::getId).collect(Collectors.toList());
          List<AtuPlanTask> taskList = planTaskService.queryTasksByBatchIds(batchIds);
          // 删除任务以及附件信息
          List<String> deletedFilePath = new ArrayList<>();
          List<String> sceneCaseTaskIdList = new ArrayList<>();
          logger.info("开始处理任务附件");
          taskList.forEach(task -> {
            if (task.getCaseType().equals("5")) {
              sceneCaseTaskIdList.add(task.getId());
            }
            String perDataPath = task.getPerDataPath();
            if (StringUtils.isNotBlank(perDataPath)) {
              deletedFilePath.add("/" + MinioPathUtils.idToPath(perDataPath)[1]);
            }
            String videoUrl = task.getVideoUrl();
            if (StringUtils.isNotBlank(videoUrl)) {
              deletedFilePath.add("/" + MinioPathUtils.idToPath(videoUrl)[1]);
            }
            String execResultFile = task.getExecResultFile();
            if (StringUtils.isNotBlank(execResultFile)) {
              deletedFilePath.add(execResultFile);
              getActualImagePath(deletedFilePath, execResultFile, task.getBatchId(), batchIds);
            }
          });
          List<AtuPlanSceneCaseTask> atuPlanSceneCaseTaskList = new ArrayList<>();
          if (!CollectionUtils.isEmpty(sceneCaseTaskIdList)) {
            atuPlanSceneCaseTaskList = atuPlanSceneCaseTaskService.querySceneCaseTasksByTaskIds(sceneCaseTaskIdList);
            logger.info("开始处理场景任务附件");
            atuPlanSceneCaseTaskList.forEach(atuPlanSceneCaseTask -> {
              String videoUrl = atuPlanSceneCaseTask.getVideoUrl();
              if (StringUtils.isNotBlank(videoUrl)) {
                deletedFilePath.add("/" + MinioPathUtils.idToPath(videoUrl)[1]);
              }
              String perDataPath = atuPlanSceneCaseTask.getPerDataPath();
              if (StringUtils.isNotBlank(perDataPath)) {
                deletedFilePath.add("/" + MinioPathUtils.idToPath(perDataPath)[1]);
              }
              String execResultFile = atuPlanSceneCaseTask.getExecResultFile();
              if (StringUtils.isNotBlank(execResultFile)) {
                deletedFilePath.add(execResultFile);
                String taskId = atuPlanSceneCaseTask.getTaskId();
                String currentBatchId = taskList.stream().filter(t -> t.getId().equals(taskId)).map(AtuPlanTask::getBatchId).findFirst().get();
                getActualImagePath(deletedFilePath, execResultFile, currentBatchId, batchIds);
              }
            });
          }
          logger.info("开始处理批次附件");
          batchList.forEach(atuPlanBatch -> {
            String perDataPath = atuPlanBatch.getPerDataPath();
            if (StringUtils.isNotBlank(perDataPath)) {
              deletedFilePath.add("/" + MinioPathUtils.idToPath(perDataPath)[1]);
            }
          });
          // 删除文件
          logger.info("开始请求文件服务，附件关联为-1");
          if (!CollectionUtils.isEmpty(deletedFilePath)) {
            AssociatedFilesDto associatedFilesDto = new AssociatedFilesDto();
            associatedFilesDto.setObjId("-1");
            associatedFilesDto.setFileIds(deletedFilePath);
            attachmentFeignClient.associatedFiles(associatedFilesDto);
          }
          atuPlanBatchService.updateAttachmentClearStatusByIds(batchIds);
        } catch (Exception e) {
          logger.error("文件处理异常", e);
        }
      }).start();
      ResponseVO<String> responseVO = new ResponseVO<>();
      responseVO.setCode(BaseDefineDependencyConstants.SYSTEM_RESPONSE_CODE_SUCCESS);
      responseVO.setMessage("正在进行清理");
      return responseVO;
    }

  /**
   * 获取实际截图地址
   * @param deletedFilePath 待删除文件地址
   * @param execResultFile 执行结果文件地址
   * @param currentBatchId 当前任务批次id
   * @param batchIds 本次清理的批次集合，发生异常时需要从集合中移除当前批次Id
   */
  private void getActualImagePath(List<String> deletedFilePath, String execResultFile, String currentBatchId, List<String> batchIds) {
    InputStream inputStream = null;
    try {
      inputStream = simpleStorageService.downloadAsStream(NKSecurityContext.getTenantId(), execResultFile);
      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
      List<String> actualImgUriList = new ArrayList<>();
      String line = null;
      while ((line = bufferedReader.readLine()) != null) {
        StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
        String actualImgUri = stepExecuteResult.getActualImgUri();
        if (StringUtils.isNotBlank(actualImgUri)) {
          try {
            actualImgUriList.add("/" + MinioPathUtils.idToPath(actualImgUri)[1]);
          } catch (Exception e) {
            logger.error("实际截图文件路径异常：{}", actualImgUri, e);
          }
        }
      }
      deletedFilePath.addAll(actualImgUriList);
    } catch (FileDownloadException e) {
      logger.error("文件下载异常", e);
      batchIds.remove(currentBatchId);
    } catch (IOException e) {
      logger.error("文件流读取异常", e);
      batchIds.remove(currentBatchId);
    }
  }
}
