package net.northking.cctp.executePlan.api.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
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.http.ResultWrapper;
import net.northking.cctp.common.s3.FileDownloadException;
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.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RedisConstant;
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.entity.AtuPlanTaskRecord;
import net.northking.cctp.executePlan.db.service.AtuPlanInfoService;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskRecordService;
import net.northking.cctp.executePlan.dto.planBatch.AppPerInfo;
import net.northking.cctp.executePlan.dto.planBatch.DevicePerInfo;
import net.northking.cctp.executePlan.dto.planBatch.MobileTaskPerformanceDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNextNodeDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeExecDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeInfoDto;
import net.northking.cctp.executePlan.dto.planTask.*;
import net.northking.cctp.executePlan.feign.AttachmentFeignClient;
import net.northking.cctp.executePlan.feign.PublicFeignClient;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
public class AtuPlanSceneCaseTaskApiServiceImpl implements AtuPlanSceneCaseTaskApiService{

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

    @Autowired
    private AtuPlanInfoService planInfoService;

    @Autowired
    private AtuPlanBatchApiService planBatchApiService;

    @Autowired
    private AtuPlanSceneCaseTaskService planSceneCaseTaskService;

    @Autowired
    private AtuPlanTaskApiService planTaskApiService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private PublicFeignClient publicFeignClient;

    @Autowired
    private AttachmentFeignClient attachmentFeignClient;

    @Autowired
    private SimpleStorageService simpleStorageService;

    @Autowired
    private AtuPlanTaskRecordService planTaskRecordService;

    /**
     * 场景用例处理
     * @param taskExecResult 任务执行结果
     */
    @Override
    public void sceneCaseHandle(AtuTaskExecResultDto taskExecResult, String clusterKeyPrefix){
        AtuPlanSceneCaseTask planSceneCaseTask = planSceneCaseTaskService.findByPrimaryKey(taskExecResult.getTaskId());
        if (ObjectUtil.isNull(planSceneCaseTask)){
            logger.error("场景节点任务[" + taskExecResult.getTaskId() + "]信息不存在");
            // 根据批次和用例id查询任务信息
            AtuPlanTask query = new AtuPlanTask();
            query.setBatchId(taskExecResult.getBatchId());
            query.setCaseId(taskExecResult.getCaseId());
            List<AtuPlanTask> taskList = planTaskApiService.query(query);
            if (CollUtil.isEmpty(taskList)){
                logger.error("根据批次id[{}]与用例id[{}]无法查询到该任务", query.getBatchId(), query.getCaseId());
                return;
            }
            planTaskApiService.taskExecFailUpdate(taskList.get(0), PlanConstant.TASK_EXECUTE_FAIL_STATUS, "场景节点任务信息不存在");
            return;
        }
        //开始执行同步更新计划任务表信息
        AtuPlanTask planTask = planTaskApiService.findByPrimaryKey(planSceneCaseTask.getTaskId());
        if (ObjectUtil.isNull(planTask)) {
            logger.error("任务[" + planSceneCaseTask.getTaskId() + "]信息不存在");
            return;
        }
        // 判断是否开始执行
        if (ObjectUtil.equal(PlanConstant.TASK_START_EXECUTE_STATUS, taskExecResult.getStatus())){
            planSceneCaseTask.setStartTime(taskExecResult.getCurrentTime());
            planSceneCaseTask.setEngineId(taskExecResult.getEngineId());
            planSceneCaseTask.setDeviceId(taskExecResult.getDeviceId());
            planSceneCaseTask.setAppId(taskExecResult.getAppId());
            planSceneCaseTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
            Date now = new Date();
            planSceneCaseTask.setLastHeartbeatTime(now);
            planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
            // 判断是否首节点
            AtuPlanSceneCaseTask countParams = new AtuPlanSceneCaseTask();
            countParams.setTaskId(planSceneCaseTask.getTaskId());
            long count = planSceneCaseTaskService.count(countParams);
            if (count == 1) {

                planTask.setStartTime(taskExecResult.getCurrentTime());
                planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
                planTask.setLastHeartbeatTime(now);
                planTaskApiService.updateByPrimaryKey(planTask);

                if (!redisTemplate.opsForHash().hasKey(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planTask.getBatchId(),
                        clusterKeyPrefix + PlanConstant.BATCH_START_TIME)) {
                    logger.debug("批次[" + planTask.getBatchId() + "]开始执行，更新计划的状态");
                    planInfoService.updatePlanByLastBatchId(planTask.getBatchId(), PlanConstant.PLAN_EXECUTING_STATUS);
                }

                // 更新批次统计数据
                planBatchApiService.updateCacheBatchSumData(planTask);
            }

        }else {
            // 获取下一节点信息
            queryNextNodeInfo(taskExecResult, planSceneCaseTask, planTask);
        }
    }


    /**
     * 查询下一节点信息
     * @param taskExecResult 任务执行结果
     * @param planSceneCaseTask 场景用例节点任务
     * @param planTask 任务信息
     */
    @Override
    public void queryNextNodeInfo(AtuTaskExecResultDto taskExecResult, AtuPlanSceneCaseTask planSceneCaseTask,
                                   AtuPlanTask planTask){

        planSceneCaseTask.setStatus(taskExecResult.getStatus());
        planSceneCaseTask.setEndTime(taskExecResult.getCurrentTime());
        planSceneCaseTask.setErrorMsg(taskExecResult.getMessage());
        planSceneCaseTask.setVideoUrl(taskExecResult.getVideoPath());
        planSceneCaseTask.setExecResultFile(taskExecResult.getFilePath());
        logger.debug("更新当前节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
        planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);

        AtuSceneNextNodeDto sceneNextNodeDto = new AtuSceneNextNodeDto();
        //传递计划任务表id
        sceneNextNodeDto.setTaskId(planSceneCaseTask.getTaskId());
        sceneNextNodeDto.setCaseId(taskExecResult.getCaseId());
        sceneNextNodeDto.setNodeId(planSceneCaseTask.getNodeId());
        sceneNextNodeDto.setSuccess(ObjectUtil.equal(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS, taskExecResult.getStatus()));
        sceneNextNodeDto.setOutputArgs(taskExecResult.getOutputs());
        sceneNextNodeDto.setScriptId(planTask.getScriptId());
        sceneNextNodeDto.setSceneScriptUrl(planTask.getScriptJson());
        ResultWrapper<AtuSceneNodeExecDto> nextNodeResult;
        try {
            logger.debug("查询场景下一节点信息，参数为 => " + JSONUtil.toJsonStr(sceneNextNodeDto));
            //捕获异常信息
            nextNodeResult = publicFeignClient.getNextNode(sceneNextNodeDto);
            if (!nextNodeResult.isSuccess()){
                logger.error("获取下一节点信息结果异常," + nextNodeResult.getMessage());
                planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
                        nextNodeResult.getMessage());
                return;
            }
        } catch (Exception e) {
            logger.error("获取下一节点信息异常:",e);
            planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
                    "获取下一节点信息异常" + e.getMessage());
            return;
        }

        logger.debug("查询场景下一节点信息，返回结果为 => " + JSONUtil.toJsonStr(nextNodeResult));
        AtuSceneNodeExecDto sceneNodeExecDto = nextNodeResult.getData();

        //更新url到表里
        if (sceneNodeExecDto != null && sceneNodeExecDto.getSceneScriptUrl() != null) {
            planTask.setScriptJson(sceneNodeExecDto.getSceneScriptUrl());
            planTaskApiService.updateByPrimaryKey(planTask);
        }

        AtuSceneNodeInfoDto nodeInfo = sceneNodeExecDto.getNodeInfo();
        // 节点不管成功或者失败，若没有连接到结束节点，默认最后节点为结束，节点信息不包含lineId
        planSceneCaseTask.setNextNodeId(nodeInfo.getNodeId());
        planSceneCaseTask.setLineId(nodeInfo.getLineId());

        // 记录性能数据
        if (PlanConstant.SCRIPT_TYPE_ANDROID.equals(planSceneCaseTask.getNodeType())
                || PlanConstant.SCRIPT_TYPE_IOS.equals(planSceneCaseTask.getNodeType())) {
            logger.debug("处理场景移动端性能文件数据");
            planSceneCaseTask.setPerDataPath(parseMobilePerformanceFile(taskExecResult));
        }

        logger.debug("更新当前节点中下一节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
        planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
        // 关联文件，使用 CompletableFuture 复用线程
        CompletableFuture.runAsync(() -> associatedActualImgAndVideo(taskExecResult));
//        future.get();
        // 判断是否结束节点
        if (sceneNodeExecDto.getEnd()){
            logger.info("场景任务执行结束....");
            //判断任务状态是否未非引擎返回的状态 取消/超时
            if(ObjectUtil.equal(PlanConstant.TASK_CANCEL_STATUS, planTask.getStatus())
                    ||ObjectUtil.equal(PlanConstant.TASK_TIMEOUT_STATUS, planTask.getStatus())){
                return;
            }
            planTask.setEndTime(planSceneCaseTask.getEndTime());
            if (StrUtil.isEmpty(nodeInfo.getLineId())) {
                planTask.setStatus(planSceneCaseTask.getStatus());
                planTask.setErrorMsg(planSceneCaseTask.getErrorMsg());
            }else{
                // 节点执行结束且连接了结束节点，则该场景任务设置成功执行
                planTask.setStatus(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
            }
            logger.debug("更新场景任务为结束");
            planTaskApiService.updateByPrimaryKey(planTask);
            // 添加任务记录到表atu_plan_task_record
            addTaskRecord(taskExecResult, planTask);
            // 更新批次统计数据
            planBatchApiService.updateCacheBatchSumData(planTask);
            // 删除节点更新时的场景任务文件
            String noDeleteFileId = "/" + MinioPathUtils.idToPath(sceneNodeExecDto.getSceneScriptUrl())[1];
            attachmentFeignClient.clearSceneTaskAttachments(planTask.getScriptId(), FileBusinessTypeEnum.SCENE_TASK_REFRESH_FILE.getCode(), noDeleteFileId);
        }else {
            // 新增下一节点任务信息
            try {
                createNextNodeTask(planSceneCaseTask, nodeInfo, taskExecResult, planTask.getEnvId());
            }catch (Exception e){
                logger.error("创建下一节点任务信息失败", e);
                planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
                        "创建下一节点任务信息失败" + e.getMessage());
            }
        }
    }

    private void addTaskRecord(AtuTaskExecResultDto taskExecResult, AtuPlanTask planTask) {
        try {
            planTask.setEndTime(taskExecResult.getCurrentTime());

            AtuPlanTaskRecord record = new AtuPlanTaskRecord();
            record.setTaskId(planTask.getId());
            // 根据taskId查询执行序号
            long count = planTaskRecordService.count(record);
            if (count > 0) {
                record.setExecuteType(PlanConstant.TASK_EXECUTE_TYPE_AUTO_RETRY);
            }
            record.setExecIdx(Math.toIntExact(count) + 1);
            // 插入执行任务记录
            record.setId(UUIDUtil.create32UUID());
            record.setStartTime(planTask.getStartTime());   // 任务开始时间
            record.setEndTime(planTask.getEndTime());       // 任务结束时间
            record.setBatchId(planTask.getBatchId());       // 批次
            record.setErrorMsg(taskExecResult.getMessage());    // 任务错误信息
            record.setExecResultFile(taskExecResult.getFilePath()); // 任务执行记录结果文件地址
            record.setVideoUrl(taskExecResult.getVideoPath());      // 任务执行录屏文件地址
            planTaskRecordService.insert(record);
        } catch (Exception e) {
            logger.error("新增任务记录失败", e);
        }
    }

    /**
     * 创建下一节点任务
     * @param planSceneCaseTask 场景用例节点任务
     * @param nodeInfo 节点信息
     * @param taskExecResult 任务执行结果
     */
    private void createNextNodeTask(AtuPlanSceneCaseTask planSceneCaseTask, AtuSceneNodeInfoDto nodeInfo,
                                    AtuTaskExecResultDto taskExecResult, String envId){
        logger.debug("保存下一节点信息");
        logger.debug("节点信息：" + JSONUtil.toJsonStr(nodeInfo));
        AtuPlanSceneCaseTask nextNodeTask = new AtuPlanSceneCaseTask();
        nextNodeTask.setId(IdUtil.simpleUUID());
        nextNodeTask.setTaskId(planSceneCaseTask.getTaskId());
        nextNodeTask.setScriptId(nodeInfo.getScriptId());
        nextNodeTask.setVersionId(nodeInfo.getVersionId());
        nextNodeTask.setVersionName(nodeInfo.getVersionName());
        nextNodeTask.setScriptName(nodeInfo.getScriptName());
        nextNodeTask.setScriptJson(nodeInfo.getScriptPath());
        nextNodeTask.setNodeId(nodeInfo.getNodeId());
        nextNodeTask.setNodeType(nodeInfo.getNodeType());
        nextNodeTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
        if (CollUtil.isNotEmpty(nodeInfo.getCaseParam())) {
            nextNodeTask.setNodeParams(JSONUtil.toJsonStr(nodeInfo.getCaseParam()));
        }
        nextNodeTask.setPrevNodeId(planSceneCaseTask.getNodeId());
        nextNodeTask.setCreatedTime(new Date());
        planSceneCaseTaskService.insert(nextNodeTask);

        // 查询计划信息
        AtuPlanInfo planInfo = planInfoService.findByPrimaryKey(taskExecResult.getPlanId());
        if (ObjectUtil.isNull(planInfo)){
            logger.error("计划[" + taskExecResult.getPlanId() + "]信息为空");
            return;
        }

        logger.debug("生成任务并推送至队列");
        AtuTaskExecDto atuTaskExecDto = new AtuTaskExecDto();
        atuTaskExecDto.setTenantId(planInfo.getTenantId());
        atuTaskExecDto.setProjectId(planInfo.getProjectId());
        atuTaskExecDto.setEnvId(envId);
        atuTaskExecDto.setPlanId(planInfo.getId());
        atuTaskExecDto.setBatchId(taskExecResult.getBatchId());
        atuTaskExecDto.setTaskId(nextNodeTask.getId());
        atuTaskExecDto.setCaseId(taskExecResult.getCaseId());
        atuTaskExecDto.setCaseType(PlanConstant.SCRIPT_TYPE_SCENE);
        atuTaskExecDto.setScriptPath(nodeInfo.getScriptPath());
        atuTaskExecDto.setCaseParams(nodeInfo.getCaseParam());
        atuTaskExecDto.setAppSet(planInfo.getAppSet());
        atuTaskExecDto.setScreenRecordSet(planInfo.getScreenRecordSet());
        atuTaskExecDto.setScreenshotSet(planInfo.getScreenshotSet());
        atuTaskExecDto.setFailRetryNum(planInfo.getFailRetryCount());
        // 重试策略
        atuTaskExecDto.setRetryStrategy(planInfo.getRetryStrategy());

        atuTaskExecDto.setAppId(planTaskApiService.queryAppId(planInfo.getId(), nodeInfo.getPlatformType(),
                nodeInfo.getAppPackage(), nodeInfo.getNodeType()));

        // 发送任务执行信息至消息队列
        planTaskApiService.sendToQueue(atuTaskExecDto.getBatchId(), atuTaskExecDto, nodeInfo.getNodeType());
    }

    @Override
    public void associatedActualImgAndVideo(AtuTaskExecResultDto taskExecResult) {
        try {
            if (StringUtils.hasText(taskExecResult.getFilePath())) {
                InputStream inputStream = simpleStorageService.downloadAsStream(NKSecurityContext.getTenantId(), taskExecResult.getFilePath());
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                List<String> fileUriList = new ArrayList<>();
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
                    String actualImgUri = stepExecuteResult.getActualImgUri();
                    if (StringUtils.hasText(actualImgUri)) {
                        fileUriList.add(actualImgUri);
                    }
                }
                fileUriList.add(taskExecResult.getFilePath());
                if (StringUtils.hasText(taskExecResult.getVideoPath())) {
                    fileUriList.add(taskExecResult.getVideoPath());
                }
                attachmentFeignClient.associatedFiles(fileUriList, taskExecResult.getTaskId());
            }
        } catch (FileDownloadException e) {
            logger.error("文件下载异常", e);
        } catch (Exception e) {
            logger.error("文件读取异常", e);
        }
    }

    @Override
    public String parseMobilePerformanceFile(AtuTaskExecResultDto taskExecResult){
        String path = "";
        DevicePerInfo devicePerInfo = taskExecResult.getDevicePerInfo();
        logger.debug("设备性能数据结果 => {}", JSONUtil.toJsonStr(devicePerInfo) );
        if (devicePerInfo == null){
            return path;
        }
        AtuPlanInfo planInfo = planInfoService.findByBatchId(taskExecResult.getBatchId());
        MobileTaskPerformanceDto taskPerInfo = new MobileTaskPerformanceDto();
        taskPerInfo.setDeviceId(taskExecResult.getDeviceId());
        taskPerInfo.setAppId(taskExecResult.getAppId());
        String devicePerInfoAddress = devicePerInfo.getDevicePerInfoAddress();
        logger.info("任务ID：{}，设备性能数据地址：{}", taskExecResult.getTaskId(), devicePerInfoAddress);
        if (StrUtil.isNotBlank(devicePerInfoAddress)){
            DevicePerInfoDataDto deviceData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
                    planInfo.getTenantId(), devicePerInfoAddress, DevicePerInfoDataDto.class);
            logger.debug("设备性能数据 => {}", JSONUtil.toJsonStr(deviceData));
            if (deviceData != null){
                taskPerInfo.setDeviceCpuList(filterData(deviceData.getCpuList()));
                taskPerInfo.setDeviceMemoList(filterData(deviceData.getMemoList()));
                taskPerInfo.setDeviceFlowList(filterData(deviceData.getFlowList()));
                taskPerInfo.setDeviceTemperatureList(filterData(deviceData.getTemperatureList()));
            }
        }
        AppPerInfo appPerInfo = devicePerInfo.getAppPerInfo();
        if (appPerInfo != null) {
            // 安装时间
            taskPerInfo.setAppInstallTime(appPerInfo.getAppInstallTime());
            // 启动时间
            taskPerInfo.setAppActiveTime(appPerInfo.getAppActiveTime());

            String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
            logger.info("任务ID：{}，app性能数据地址：{}", taskExecResult.getTaskId(), appPerInfoAddress);
            if (StrUtil.isNotBlank(appPerInfoAddress)) {
                AppPerInfoDataDto appData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
                        planInfo.getTenantId(), appPerInfoAddress, AppPerInfoDataDto.class);
                logger.debug("app性能数据 => {}", JSONUtil.toJsonStr(appData) );
                if (appData != null) {
                    taskPerInfo.setAppCpuList(filterData(appData.getCpuList()));
                    taskPerInfo.setAppMemoList(filterData(appData.getMemoList()));
                    taskPerInfo.setAppFlowList(filterData(appData.getFlowList()));
                }

            }
        }

        logger.debug("上传新的性能数据");
        logger.debug("MobileTaskPerformanceDto => {}", JSONUtil.toJsonStr(taskPerInfo) );
        File file = MinioPathUtils.objectToJsonFile(taskPerInfo);
        try {
            NKFile nkFile = simpleStorageService.upload(planInfo.getTenantId(), file, true, taskExecResult.getTaskId(), FileBusinessTypeEnum.MOBILE_TASK_PERFORMANCE_SUMMARY);
            path = nkFile.getId();
            logger.debug("保存任务性能数据存放地址 => {}", path);
        }catch (Exception e){
            logger.error("文件上传异常", e);
        }
        // 删除临时性能文件
        if (StrUtil.isNotBlank(devicePerInfoAddress)){
            String[] pathUrl = MinioPathUtils.idToPath(devicePerInfoAddress);
            try {
                simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
            } catch (Exception e) {
                logger.error("文件删除删除失败", e);
            }
        }
        String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
        if (StrUtil.isNotBlank(appPerInfoAddress)){
            String[] pathUrl = MinioPathUtils.idToPath(appPerInfoAddress);
            try {
                simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
            } catch (Exception e) {
                logger.error("文件删除删除失败", e);
            }
        }
        return path;
    }

    /**
     * 过滤小于等于0的数据
     * @param data 需过滤的数据
     * @return 结果集
     */
    private List<Double> filterData(List<Double> data){
        if (data == null || data.size() <= 0){
            return new ArrayList<>();
        }
        return data.stream().filter(num -> num > 0).collect(Collectors.toList());
    }

}
