package net.northking.cctp.scriptcase.api.scenescript.service.coordinate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import net.northking.cctp.common.db.SqlOrderBy;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.scriptcase.api.atuScript.service.AtuScriptInfoApiService;
import net.northking.cctp.scriptcase.api.scenescript.bean.*;
import net.northking.cctp.scriptcase.constants.ScriptConstant;
import net.northking.cctp.scriptcase.db.entity.AtuGlobalVariable;
import net.northking.cctp.scriptcase.db.entity.AtuSceneInfo;
import net.northking.cctp.scriptcase.db.entity.AtuScriptInfo;
import net.northking.cctp.scriptcase.db.entity.AtuScriptVersion;
import net.northking.cctp.scriptcase.db.service.AtuGlobalVariableService;
import net.northking.cctp.scriptcase.db.service.AtuSceneInfoService;
import net.northking.cctp.scriptcase.db.service.AtuScriptInfoService;
import net.northking.cctp.scriptcase.db.service.AtuScriptVersionService;
import net.northking.cctp.scriptcase.dto.atuScript.atuScriptInfo.AtuScriptInfoDetailDto;
import net.northking.cctp.scriptcase.dto.sceneScript.*;
import net.northking.cctp.scriptcase.enums.ErrorMessage;
import net.northking.cctp.scriptcase.enums.ScriptCaseAppError;
import net.northking.cctp.scriptcase.tools.MinioPathUtils;
import org.apache.logging.log4j.util.Strings;
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.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SceneCoordinatorService {
    private final static Logger logger = LoggerFactory.getLogger(SceneCoordinatorService.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SimpleStorageService simpleStorageService;

    @Autowired
    private MinioPathUtils pathUtil;

    @Autowired
    private AtuScriptInfoService atuScriptInfoService;

    @Autowired
    private AtuGlobalVariableService atuGlobalVariableService;

    @Autowired
    private AtuSceneInfoService sceneInfoService;

    @Autowired
    private AtuScriptVersionService scriptVersionService;

    @Autowired
    private AtuScriptInfoApiService atuScriptInfoApiService;

    public ResNodeInfoDto getFirstNode(ReqSceneFirstNodeDto reqSceneFirstNodeDto) {
        AtuSceneInfo sceneInfo = sceneInfoService.findByPrimaryKey(reqSceneFirstNodeDto.getScriptId());
        //下载场景脚本
        SceneScript sceneScript = downloadScriptFile(sceneInfo.getScriptUrl(), SceneScript.class);
        if (sceneScript == null)
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_DATA_CHANGE_ERROR);

        //场景脚本开始节点
        List<Node> listNodes = sceneScript.getNodes();
        Optional<Node> startNode = listNodes.stream().filter(node -> node.nodeType.equals(ScriptConstant.START)).findFirst();
        if (!startNode.isPresent())
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_IS_PRESENT);

        //开始节点与下一节点连线
        List<Line> listLine = sceneScript.getLines();
        Optional<Line> nextLine = listLine.stream().filter(line -> line.source.equals(startNode.get().id)).findFirst();
        if (!nextLine.isPresent())
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_NEED_NEXT_LINE);

        //首接点
        Optional<Node> nextNode = listNodes.stream().filter(node -> node.id.equals(nextLine.get().getTarget())).findFirst();
        if (!nextNode.isPresent())
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_NEED_NEXT_NODE);

        //初始化变量
        List<Variable> listVariables = sceneScript.getVariables();
        Map<String, Variable> variableMap = new HashMap<>();
        for (Variable var : listVariables) {
            String varName = "${" + var.name + "}";
            if (variableMap.containsKey(varName)) {
                logger.debug("重复变量: 变量原名{}, 变量使用名{}", var.name, varName);
                continue;
            }
            logger.debug("变量原名{}, 变量使用名{}", var.name, varName);
            if (var.getNodeIds() == null || var.getNodeIds().size() == 0) {
                logger.debug("变量{}没有绑定任何节点.", var.name);
                continue;
            }

            var.setName(varName);
            variableMap.put(varName, var);
        }

        //使用用例的参数初始化变量
        Map<String, Object> paramMap = reqSceneFirstNodeDto.getParamMap();
        logger.debug("用例实例化参数：{}", paramMap);
        for (Map.Entry<String, Variable> entry : variableMap.entrySet()) {
            String keyName = entry.getKey().replace("${", "").replace("}", "");
            if (paramMap.containsKey(keyName)) {
                entry.getValue().setValue(paramMap.get(keyName).toString());
            } else {
                entry.getValue().setValue(entry.getValue().getDefaultValue());
            }
        }
        ResNodeInfoDto reqNodeInfoDto = new ResNodeInfoDto();

        String uploadUrl = refreshNodeAndScriptFile(nextNode.get(), sceneInfo.getScriptUrl());
        if (!Strings.isBlank(uploadUrl)) {
            reqNodeInfoDto.setSceneScriptUrl(uploadUrl);
        }

        List<ResVariable> nodeVariableList = getNodeInputs(sceneInfo.getProjectId(), nextNode.get(), variableMap);
        logger.debug("任务号:" + reqSceneFirstNodeDto.getTaskId());
        logger.debug("场景名:" + sceneScript.getName() + "(" + sceneScript.getId() + ")");
        logger.debug("用例Id:" + reqSceneFirstNodeDto.getCaseId());
        logger.debug("首节点:" + nextNode.get().getNodeName() + "(nodeId:" + nextNode.get().getId() + ")");
        logger.debug("首节点({})输入变量:", nextNode.get().getNodeName());
        Map<String, Object> CaseParamMap = new HashMap<>();
        nodeVariableList.forEach(v -> {
            logger.debug("参数名:{}, 变量名:{}, 变量值:{}", v.getName(), v.getDesc(), v.getValue());
            CaseParamMap.put(v.getName(), v.getValue());
        });

        //设置redis, hash缓存(key=batchNo+caseId, field=变量名, value=变量值, nodeIds=["场景节点Id"])
        String key = reqSceneFirstNodeDto.getTaskId() + reqSceneFirstNodeDto.getCaseId();
        redisTemplate.opsForHash().putAll(key, variableMap);
        redisTemplate.expire(key, 1, TimeUnit.DAYS);

        reqNodeInfoDto.taskId = reqSceneFirstNodeDto.getTaskId();
        reqNodeInfoDto.caseId = reqSceneFirstNodeDto.getCaseId();
        reqNodeInfoDto.setEnd(false);
        reqNodeInfoDto.message = ScriptConstant.GET_FIRST_NODE;
        reqNodeInfoDto.nodeInfo = new ResNodeInfo();
        reqNodeInfoDto.nodeInfo.setLineId(nextLine.get().getId());
        reqNodeInfoDto.nodeInfo.setNodeId(nextNode.get().getId());
        String nodeScriptId = nextNode.get().getScriptId();
        reqNodeInfoDto.nodeInfo.setScriptId(nodeScriptId);
        reqNodeInfoDto.nodeInfo.setScriptName(nextNode.get().getScriptName());
        // 查询脚本的版本信息
        String scriptVersion = nextNode.get().getScriptVersion();
        logger.debug("首节点脚本版本为：{}", scriptVersion);
        AtuScriptVersion atuScriptVersion = scriptVersionService.queryByScriptIdAndVersion(scriptVersion, nodeScriptId);;
        if (atuScriptVersion == null) {
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_VERSION_NULL, reqSceneFirstNodeDto.getScriptId(), nodeScriptId);
        }
        reqNodeInfoDto.nodeInfo.setVersionId(atuScriptVersion.getVersionId());
        reqNodeInfoDto.nodeInfo.setVersionName(atuScriptVersion.getVersionName());
        reqNodeInfoDto.nodeInfo.setScriptPath(atuScriptVersion.getUrl());

        //节点入参
        reqNodeInfoDto.nodeInfo.setCaseParam(CaseParamMap);
        reqNodeInfoDto.nodeInfo.setNodeType(convertNodeType(nextNode.get().getNodeType()));
        if (ScriptConstant.NODE_TYPE_ANDROID.equals(nextNode.get().getNodeType()) ||
                ScriptConstant.NODE_TYPE_IOS.equals(nextNode.get().getNodeType())) {
            SceneNodeInfoDto dto = GetOneNodeInfo(nextNode.get());
            if (dto != null) {
                reqNodeInfoDto.nodeInfo.setAppPackage(dto.getAppPackage());
                reqNodeInfoDto.nodeInfo.setPlatformType(dto.getPlatform());
            }
        }

        logger.debug("获取首节点({})成功, 节点入参:{}", nextNode.get().getNodeName(), CaseParamMap);
        return reqNodeInfoDto;
    }

    public void refreshNodeScriptInfo(Node nextNode) {
        if (nextNode.nodeType.equals(ScriptConstant.END)) {
            return;
        }
        AtuScriptInfoDetailDto scriptInfoDetailDto = atuScriptInfoApiService.getScriptInfo(nextNode.getScriptId(), true);
        logger.info("node信息：{}，获取的节点脚本信息{}", nextNode, JSON.toJSONString(scriptInfoDetailDto));
        if (scriptInfoDetailDto != null) {
            nextNode.setScriptUrl(scriptInfoDetailDto.getScriptUrl());
            List<Object> inputList = scriptInfoDetailDto.getInputList();
            List<Object> outputList = scriptInfoDetailDto.getOutputList();

            List<Inputs> inputs = JSON.parseObject(JSON.toJSONString(inputList), new TypeReference<List<Inputs>>() {
            });
            List<Outputs> outputs = JSON.parseObject(JSON.toJSONString(outputList), new TypeReference<List<Outputs>>() {
            });
            List<Inputs> sceneInputs = nextNode.getInputs();
            if (sceneInputs != null && inputs != null) {
                logger.info("sceneInputs 参数：{}，inputs 参数：{}", sceneInputs, inputs);
                Map<String, String> parameterCollect = sceneInputs.stream().collect(Collectors.toMap(Inputs::getName, Inputs::getParameter));
                for (Inputs input : inputs) {
                    input.setParameter(parameterCollect.get(input.getName()));
                }
            }

            nextNode.setInputs(inputs);
            nextNode.setOutputs(outputs);
            nextNode.setScriptVersion(scriptInfoDetailDto.getVersionName());
            logger.info("更新后的node信息：{}", nextNode);
        } else {
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_NODE_NOT_EXIST, nextNode.getId());
        }
    }

    public String refreshNodeAndScriptFile(Node nextNode, String sceneScriptUrl) {
        if (nextNode.nodeType.equals(ScriptConstant.END)) {
            return "";
        }

        AtuScriptInfoDetailDto scriptInfoDetailDto = atuScriptInfoApiService.getScriptInfo(nextNode.getScriptId(), true);
        logger.info("node信息：{}，sceneScriptUrl 信息：{}，获取的节点脚本信息{}", nextNode, sceneScriptUrl, JSON.toJSONString(scriptInfoDetailDto));
        if (scriptInfoDetailDto != null) {
            nextNode.setScriptUrl(scriptInfoDetailDto.getScriptUrl());
            List<Object> inputList = scriptInfoDetailDto.getInputList();
            List<Object> outputList = scriptInfoDetailDto.getOutputList();

            List<Inputs> inputs = JSON.parseObject(JSON.toJSONString(inputList), new TypeReference<List<Inputs>>() {
            });
            List<Outputs> outputs = JSON.parseObject(JSON.toJSONString(outputList), new TypeReference<List<Outputs>>() {
            });
            List<Inputs> sceneInputs = nextNode.getInputs();
            if (sceneInputs != null && inputs != null) {
                logger.info("sceneInputs 参数：{}，inputs 参数：{}", sceneInputs, inputs);
                Map<String, String> parameterCollect = sceneInputs.stream().collect(Collectors.toMap(Inputs::getName, Inputs::getParameter));
                for (Inputs input : inputs) {
                    input.setParameter(parameterCollect.get(input.getName()));
                }
            }
            nextNode.setInputs(inputs);
            nextNode.setOutputs(outputs);
            nextNode.setScriptVersion(scriptInfoDetailDto.getVersionName());

            //下载场景脚本 执行的信息上传到文件服务器
            SceneScript sceneScript = downloadScriptFile(sceneScriptUrl, SceneScript.class);
            logger.info("获取到的 sceneScript 脚本信息为：" + sceneScript);
            List<Node> nodes = sceneScript.getNodes();
            if (!CollectionUtils.isEmpty(nodes)) {
                for (Node node : nodes) {
                    if (node.getId().equals(nextNode.getId())) {
                        node.setScriptUrl(nextNode.getScriptUrl());
                        break;
                    }
                }
                logger.info("更新后的node信息：{}", nextNode);
                //上传 sceneScript 文件到服务器
                return atuScriptInfoApiService.generateScriptFileAndUpload(sceneScript, sceneScript.getId(), FileBusinessTypeEnum.SCENE_TASK_REFRESH_FILE);
            }
            return sceneScriptUrl;
        } else {

            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_NODE_NOT_EXIST, nextNode.getId());
        }
    }

    private String convertNodeType(String nodeType) {
        if (ScriptConstant.NODE_TYPE_INTERFACE.equals(nodeType))
            return ScriptConstant.SCRIPT_TYPE_API;
        else if (ScriptConstant.NODE_TYPE_PCBS.equals(nodeType))
            return ScriptConstant.SCRIPT_TYPE_BS;
        else if (ScriptConstant.NODE_TYPE_PCCS.equals(nodeType))
            return ScriptConstant.SCRIPT_TYPE_CS;
        else if (ScriptConstant.NODE_TYPE_ANDROID.equals(nodeType))
            return ScriptConstant.SCRIPT_TYPE_ANDROID;
        else if (ScriptConstant.NODE_TYPE_IOS.equals(nodeType))
            return ScriptConstant.SCRIPT_TYPE_IOS;

        logger.debug("无效的节点类型nodeType=" + nodeType);
        return "";
    }

    private List<ResVariable> getNodeVariables(String nodeId, Map<String, Variable> variableMap) {
        List<ResVariable> inputVariableList = new ArrayList<>();
        if (variableMap == null)
            return inputVariableList;

        for (Map.Entry<String, Variable> entry : variableMap.entrySet()) {
            Variable var = entry.getValue();
            if (var.getNodeIds() != null && var.getNodeIds().stream().anyMatch(x -> x.equals(nodeId))) {
                ResVariable v = new ResVariable();
                v.setName(var.getName());
                v.setDesc(var.getDesc());
                v.setInput(var.isInput());
                v.setOutput(var.isOutput());
                v.setType(var.getType());
                v.setValue(var.getValue());
                inputVariableList.add(v);
            }
        }
        return inputVariableList;
    }

    private List<ResVariable> getNodeInputs(String projectId, Node node, Map<String, Variable> variableMap) {
        logger.info("variableMap数据为：" + variableMap + "node参数数据为：" + node);
        List<ResVariable> inputVariableList = new ArrayList<>();
        if (node == null || node.getInputs() == null)
            return inputVariableList;

        node.getInputs().forEach(inArgument -> {
            String parameter = inArgument.getParameter();
            //没有绑定变量使用缺省值
            if (!StringUtils.hasText(parameter)) {
                logger.info("入参【" + inArgument.getName() + "】没有绑定变量, 使用参缺省值:" + inArgument.getDefaultValue());
                ResVariable rv001 = new ResVariable();
                rv001.setName(inArgument.getName());
                rv001.setValue(inArgument.getDefaultValue());
                rv001.setType(inArgument.getType());
                rv001.setOutput(false);
                rv001.setInput(true);
                rv001.setDesc("");
                inputVariableList.add(rv001);
                return;
            }

            //绑定场景变量
            if (parameter.contains("${")) {
                List<ResVariable> nodeVariables = getNodeVariables(node.getId(), variableMap);
                if (!CollectionUtils.isEmpty(nodeVariables)) {
                    Optional<ResVariable> _varNode =
                            nodeVariables.stream().filter(v -> parameter.equals(v.getName())).findFirst();
                    if (_varNode.isPresent()) {
                        ResVariable realVariable = _varNode.get();
                        if (realVariable.getValue() == null && inArgument.getDefaultValue() != null) {
                            logger.info("变量" + parameter + "在场景变量表值为空,使用入参缺省值:" + inArgument.getDefaultValue());
                            realVariable.setValue(inArgument.getDefaultValue());
                        }
                        if (realVariable.getValue() == null)
                            logger.info("变量" + parameter + "在场景变量表值为空,参缺省值也为空");

                        ResVariable rv002 = new ResVariable();
                        rv002.setName(inArgument.getName());
                        rv002.setValue(realVariable.getValue());
                        rv002.setType(inArgument.getType());
                        rv002.setOutput(false);
                        rv002.setInput(true);
                        rv002.setDesc(parameter);
                        inputVariableList.add(rv002);
                    }
                } else {
                    throw new PlatformRuntimeException(new ErrorMessage("变量" + parameter + "不存在场景变量表."));
                }
            }
            //绑定全局变量
            else if (parameter.contains("#{")) {
                AtuGlobalVariable where = new AtuGlobalVariable();
                String _varName = parameter.replace("#{", "").replace("}", "");
                where.setName(_varName);
                where.setDelete(false);
                where.setProjectId(projectId);
                where.setVariableType(false);
                AtuGlobalVariable variable = atuGlobalVariableService.queryVariable(where);
                if (variable != null) {
                    ResVariable rv003 = new ResVariable();
                    rv003.setName(inArgument.getName());
                    rv003.setValue(variable.getDefaultValue());
                    rv003.setType(inArgument.getType());
                    rv003.setOutput(false);
                    rv003.setInput(true);
                    rv003.setDesc(parameter);
                    inputVariableList.add(rv003);
                } else {//全局变量不存在
                    logger.error("全局变量" + parameter + "不存在或已被删除.");
                    throw new PlatformRuntimeException(new ErrorMessage("全局变量" + parameter + "不存在或已被删除."));
                }
            }
            //绑定上个节点的输出变量
            else if (variableMap.containsKey(parameter)) {//key = 节点名称.变量名
                Variable refVariable = variableMap.get(parameter);
                logger.info("节点:{}的入参:{}, 引用了变量:{}, 变量值:{}", node.getNodeName(),
                        inArgument.getName(), parameter, refVariable.getValue());
                ResVariable rv004 = new ResVariable();
                rv004.setName(inArgument.getName());
                rv004.setValue(refVariable.getValue());
                rv004.setType(inArgument.getType());
                rv004.setOutput(false);
                rv004.setInput(true);
                rv004.setDesc(parameter);
                inputVariableList.add(rv004);
            } else {
                String msg = String.format("节点:%s的入参:%s(%s)从场景变量、全局变量和脚本输出变量中匹配不到参数值. " +
                                "使用字符串\"\"值传入节点【%s】", node.getNodeName(),
                        inArgument.getName(), inArgument.getParameter(), node.getNodeName());
                logger.debug(msg);

                ResVariable rv005 = new ResVariable();
                rv005.setName(inArgument.getName());
                rv005.setValue("");
                rv005.setType(inArgument.getType());
                rv005.setOutput(false);
                rv005.setInput(true);
                rv005.setDesc(parameter);
                inputVariableList.add(rv005);
            }
        });
        logger.info("返回 inputVariableList 数据为："+inputVariableList);
        return inputVariableList;
    }

    public ResNodeInfoDto getNextNode(ReqSceneNextNodeDto reqSceneNextNodeDto) {

        AtuSceneInfo sceneInfo = sceneInfoService.findByPrimaryKey(reqSceneNextNodeDto.getScriptId());
        if (sceneInfo == null) {
            throw new PlatformRuntimeException(new ErrorMessage("场景脚本[" + reqSceneNextNodeDto.getScriptId() + "]不存在"));
        }
        //下载场景脚本
        SceneScript sceneScript = downloadScriptFile(sceneInfo.getScriptUrl(), SceneScript.class);
        if (sceneScript == null)
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_DATA_CHANGE_ERROR);

        //场景脚本开始节点
        List<Node> listNodes = sceneScript.getNodes();
        Optional<Node> currentNode = listNodes.stream().filter(n -> n.getId().equals(reqSceneNextNodeDto.getNodeId())).findFirst();
        if (!currentNode.isPresent())
            throw new PlatformRuntimeException(ScriptCaseAppError.SCENE_NODE_NOT_EXIST, reqSceneNextNodeDto.getNodeId());

        List<Line> listLine = sceneScript.getLines();
        List<Line> listNextLine =
                listLine.stream().filter(l -> l.source.equals(reqSceneNextNodeDto.getNodeId()) && l.lineType.equals("succLine")).collect(Collectors.toList());
        if (!reqSceneNextNodeDto.isSuccess())
            listNextLine =
                    listLine.stream().filter(l -> l.source.equals(reqSceneNextNodeDto.getNodeId()) && l.lineType.equals("failLine")).collect(Collectors.toList());

        Node nextNode = getNextNode(reqSceneNextNodeDto.isSuccess(), listNodes, currentNode.get(), listNextLine);

        ResNodeInfoDto reqNodeInfoDto = new ResNodeInfoDto();
        refreshNodeAndScriptFile(currentNode.get(), reqSceneNextNodeDto.getSceneScriptUrl());
        String scriptFileUrl = refreshNodeAndScriptFile(nextNode, reqSceneNextNodeDto.getSceneScriptUrl());
        if (Strings.isNotBlank(scriptFileUrl)) {
            reqNodeInfoDto.setSceneScriptUrl(scriptFileUrl);
        }

        //节点最大允许执行次数计数器
        String maxLoopKey = "maxLoop" + reqSceneNextNodeDto.getTaskId() + currentNode.get().getId() + nextNode.getId();
        boolean exceedMaxLoop = false;
        int maxLoop = 20;
        if (!ScriptConstant.END.equals(currentNode.get().getNodeType())) {
            Map<String, String> globalSettingMap = sceneScript.getGlobalSetting();
            if (globalSettingMap != null && globalSettingMap.containsKey("maxLoop")) {
                maxLoop = Integer.valueOf(globalSettingMap.get("maxLoop"));
            }

            String maxLoopField = currentNode.get().getId();
            if (redisTemplate.opsForHash().hasKey(maxLoopKey, currentNode.get().getId())) {
                Integer _maxLoop = (Integer) redisTemplate.opsForHash().get(maxLoopKey, maxLoopField);
                redisTemplate.opsForHash().put(maxLoopKey, maxLoopField, ++_maxLoop);
                if (_maxLoop > maxLoop) {//超出最大循环次数走失败流程
                    exceedMaxLoop = true;
                }
            } else {
                redisTemplate.opsForHash().put(maxLoopKey, maxLoopField, 1);
                redisTemplate.expire(maxLoopKey, 1, TimeUnit.DAYS);
            }
        }

        //节点超出最大循环次数, 切换执行路线....
        if (exceedMaxLoop) {
            logger.debug("节点{},由于超出最大循环次数:{}, 如果成功线或失败线循环超限,强制走失败流程. " +
                    "如果失败线循环且最后一次执行成功,走成功流程线.", currentNode.get().getNodeName(), maxLoop);
            String nodeStateKey = "nodeState" + reqSceneNextNodeDto.getTaskId();
            boolean preState = Boolean.valueOf(String.valueOf(redisTemplate.opsForHash().get(nodeStateKey, currentNode.get().getId())));
            logger.debug("节点{}超出最大循环次数,nodeStateKey:{},前次执行结果:{}", currentNode.get().getNodeName(), nodeStateKey, preState);
            boolean successLine = false;
            if (reqSceneNextNodeDto.isSuccess()) {
                successLine = !preState ? true : false;
            }
            listNextLine =
                    listLine.stream().filter(l -> l.source.equals(reqSceneNextNodeDto.getNodeId()) && l.lineType.equals("failLine")).collect(Collectors.toList());
            if (successLine)
                listNextLine =
                        listLine.stream().filter(l -> l.source.equals(reqSceneNextNodeDto.getNodeId()) && l.lineType.equals("succLine")).collect(Collectors.toList());
            nextNode = getNextNode(successLine, listNodes, currentNode.get(), listNextLine);
            String loopLineKey = "maxLoop" + reqSceneNextNodeDto.getTaskId() + currentNode.get().getId() + nextNode.getId();
            if (!successLine && loopLineKey.equals(maxLoopKey)) {
                nextNode = getEndNode(listNodes);
                if (nextNode != null) {
                    logger.debug("节点{}超出最大循环次数,且最后个次执行结果是失败, 强制走结束接点:{}",
                            currentNode.get().getNodeName(), nextNode.getNodeName());
                }
            }

            logger.debug("节点{}超出最大循环次数, 切换执行路线后重置循环计数器.", currentNode.get().getNodeName());
            String maxLoopField = currentNode.get().getId();
            redisTemplate.opsForHash().put(maxLoopKey, maxLoopField, 1);
            redisTemplate.expire(maxLoopKey, 1, TimeUnit.DAYS);
        }

        //更新 redis 变量上下文
        List<Outputs> outputsList = currentNode.get().getOutputs();
        logger.debug("节点{}输出变量:{}", currentNode.get().getNodeName(), reqSceneNextNodeDto.getOutputArgs());
        String key = reqSceneNextNodeDto.getTaskId() + reqSceneNextNodeDto.getCaseId();
        String currentNodeId = reqSceneNextNodeDto.getNodeId();
        if (!CollectionUtils.isEmpty(outputsList) && reqSceneNextNodeDto.getOutputArgs() != null) {
            reqSceneNextNodeDto.getOutputArgs().forEach((outputArgumentName, value) -> {
                Optional<Outputs> optOutArgument = outputsList.stream().
                        filter(x -> outputArgumentName.equals(x.name)).findFirst();
                if (!optOutArgument.isPresent()) {
                    logger.debug("引擎返回的输出参数不属于节点:{}设置的输出参数. 参数名:{}, 参数值:{}", currentNode.get().getNodeName(), outputArgumentName, value);
                    return;
                }
                Outputs outputArgument = optOutArgument.get();
                String variableName = currentNode.get().getNodeName() + "." + outputArgument.getName();
                logger.debug("引擎返回的输出参数属于节点:{}输出参数. 参数名:{}, 参数值:{}, 参数缓存Key:{}", currentNode.get().getNodeName(),
                        outputArgumentName, value, variableName);
                if (!redisTemplate.opsForHash().hasKey(key, variableName)) {
                    Variable newVariable = new Variable();
                    newVariable.setName(variableName);
                    newVariable.setValue(String.valueOf(value));
                    newVariable.setDesc("");
                    newVariable.setInput(false);
                    newVariable.setOutput(true);
                    newVariable.setNodeIds(Arrays.asList(currentNodeId));
                    newVariable.setType("string");
                    redisTemplate.opsForHash().put(key, variableName, newVariable);
                    logger.debug("引擎输出变量名:{}, 缓存原值:{}, 引擎输出值:{}", variableName, "不存在缓存", value);
                } else {
                    Variable tmpVariable = (Variable) redisTemplate.opsForHash().get(key, variableName);
                    String tmpValue = tmpVariable.getValue();
                    tmpVariable.setValue(String.valueOf(value));
                    if (tmpVariable.getNodeIds() == null)
                        tmpVariable.setNodeIds(Arrays.asList(currentNodeId));
                    else {
                        if (!tmpVariable.getNodeIds().stream().anyMatch(x -> x.equals(currentNodeId))) {
                            tmpVariable.getNodeIds().add(currentNodeId);
                        }
                    }

                    redisTemplate.opsForHash().put(key, variableName, tmpVariable);
                    logger.debug("引擎输出变量名:{}, 缓存原值:{}, 引擎输出值:{}", variableName, tmpValue, value);
                }
            });
        }

        //获取下一节点输入变量
        Map<String, Variable> variableMap = redisTemplate.opsForHash().entries(key);
        List<ResVariable> nodeVariableList = getNodeInputs(sceneInfo.getProjectId(), nextNode, variableMap);
        logger.debug("任务号:" + reqSceneNextNodeDto.getTaskId());
        logger.debug("场景名:" + sceneScript.getName() + "(" + sceneScript.getId() + ")");
        logger.debug("用例Id:" + reqSceneNextNodeDto.getCaseId());
        logger.debug("下一节点名:" + nextNode.getNodeName() + "(nodeId:" + nextNode.getId() + ")");
        logger.debug("下一节点({})输入变量:", nextNode.getNodeName());
        Map<String, Object> CaseParamMap = new HashMap<>();
        nodeVariableList.forEach(v -> {
            logger.debug("参数名:{}, 变量名:{}, 变量值:{}", v.getName(), v.getDesc(), v.getValue());
            CaseParamMap.put(v.getName(), v.getValue());
        });


        reqNodeInfoDto.taskId = reqSceneNextNodeDto.getTaskId();
        reqNodeInfoDto.caseId = reqSceneNextNodeDto.getCaseId();
        logger.debug("下一节点类型({})", nextNode.nodeType);
        reqNodeInfoDto.setEnd(nextNode.nodeType.equals(ScriptConstant.END) ? true : false);
        reqNodeInfoDto.message = "获取下一节点成功";
        reqNodeInfoDto.nodeInfo = new ResNodeInfo();

        String nextLineId = CollectionUtils.isEmpty(listNextLine) ? "" : listNextLine.get(0).getId();
        if (reqNodeInfoDto.getEnd() && !StringUtils.isEmpty(nextLineId)) {
            //循环超出最大次数时
            if (!listNextLine.get(0).target.equals(nextNode.getId())) {
                nextLineId = "";
                logger.debug("节点【{}】循环超出最大次【{}】数,且出口连接线没有连接结束节点.设置nextLineId为空!",
                        currentNode.get().getNodeName(), maxLoop);
            }
        }
        reqNodeInfoDto.nodeInfo.setLineId(nextLineId);
        reqNodeInfoDto.nodeInfo.setNodeId(nextNode.getId());
        String nodeScriptId = nextNode.getScriptId();
        reqNodeInfoDto.nodeInfo.setScriptId(nodeScriptId);
        reqNodeInfoDto.nodeInfo.setScriptName(nextNode.getScriptName());
        // 查询脚本的版本信息
        if (!reqNodeInfoDto.getEnd()) {
            logger.debug("下一节点不是结束节点，获取该节点的脚本信息");
            String scriptVersion = nextNode.getScriptVersion();
            AtuScriptVersion atuScriptVersion = scriptVersionService.queryByScriptIdAndVersion(scriptVersion, nodeScriptId);;
            if (atuScriptVersion == null) {
                throw new PlatformRuntimeException(new ErrorMessage("场景脚本[" + reqSceneNextNodeDto.getScriptId() + "]" +
                        "节点的脚本[" + nodeScriptId + "]版本信息不存在"));
            } else {
                reqNodeInfoDto.nodeInfo.setVersionId(atuScriptVersion.getVersionId());
                reqNodeInfoDto.nodeInfo.setVersionName(atuScriptVersion.getVersionName());
                reqNodeInfoDto.nodeInfo.setScriptPath(atuScriptVersion.getUrl());
            }
        }

        //节点人参
        reqNodeInfoDto.nodeInfo.setCaseParam(CaseParamMap);
        reqNodeInfoDto.nodeInfo.setNodeType(convertNodeType(nextNode.getNodeType()));

        if (ScriptConstant.NODE_TYPE_ANDROID.equals(nextNode.getNodeType()) || ScriptConstant.NODE_TYPE_IOS.equals(nextNode.getNodeType())) {
            SceneNodeInfoDto dto = GetOneNodeInfo(nextNode);
            if (dto != null) {
                reqNodeInfoDto.nodeInfo.setPlatformType(dto.getPlatform());
                reqNodeInfoDto.nodeInfo.setAppPackage(dto.getAppPackage());
            }
        }

        //设置节点上一次执行结果
        String nodeStateKey = "nodeState" + reqSceneNextNodeDto.getTaskId();
        redisTemplate.opsForHash().put(nodeStateKey, currentNode.get().getId(), reqSceneNextNodeDto.isSuccess());

        if (reqNodeInfoDto.getEnd()) {
            //销毁redis缓存
            logger.debug("删除场景{}节点最大允许执行次数计数器缓存,key={}", sceneScript.getName(), maxLoopKey);
            redisTemplate.delete(maxLoopKey);
            logger.debug("删除场景{}全部变量缓存,key={},数量:{}", sceneScript.getName(), key, variableMap.size());
            redisTemplate.delete(key);
            logger.debug("删除场景{}节点执行结果器缓存,key={}", sceneScript.getName(), nodeStateKey);
            redisTemplate.delete(nodeStateKey);
        }

        logger.debug("获取下一节点({})成功, 节点入参:{}", nextNode.getNodeName(), CaseParamMap);
        return reqNodeInfoDto;
    }

    public void stopCase(StopCaseDto stopCaseDto) {
        logger.debug("终止计划, 删除全部变量缓存,taskId={},caseId:{}", stopCaseDto.getTaskId(), stopCaseDto.getCaseId());
        String key = stopCaseDto.getTaskId() + stopCaseDto.getCaseId();
        redisTemplate.delete(key);
        String maxLoopKey = stopCaseDto.getTaskId() + stopCaseDto.getCaseId() + "maxLoop";
        redisTemplate.delete(maxLoopKey);
    }

    private Node getEndNode(List<Node> listNodes) {
        Optional<Node> oEndNode = listNodes.stream().filter(e -> e.nodeType.equals(ScriptConstant.END)).findFirst();
        return oEndNode.isPresent() ? oEndNode.get() : null;
    }

    private Node getNextNode(boolean execResult, List<Node> listNodes, Node currentNode,
                             List<Line> listNextLine) {
        Node nextNode = null;
        if (execResult && CollectionUtils.isEmpty(listNextLine))
            throw new PlatformRuntimeException(new ErrorMessage(currentNode.getNodeName() + "缺少成功连接线"));
        if (!execResult && CollectionUtils.isEmpty(listNextLine))
            nextNode = getEndNode(listNodes);
        else if (listNextLine.size() != 1) {
            throw new PlatformRuntimeException(new ErrorMessage(currentNode.getNodeName() + "出口存在多条成功或失败连接线"));
        } else {
            String target = listNextLine.get(0).getTarget();
            Optional<Node> targetNode = listNodes.stream().filter(n -> n.getId().equals(target)).findFirst();
            nextNode = targetNode.isPresent() ? targetNode.get() : null;
            if (nextNode == null)
                throw new PlatformRuntimeException(new ErrorMessage(listNextLine.get(0).getLable() + "没有连接任何节点"));
        }
        if (nextNode != null) {
            logger.debug("当前节点{},执行接果:{},下一执行节点:{}(节点NodeId:{})", currentNode.getNodeName(), execResult,
                    nextNode.getNodeName(), nextNode.getId());
        }
        return nextNode;
    }

    public <T> T downloadScriptFile(String url, Class<T> clazz) {
        String[] resultPath = pathUtil.idToPath(url);
        InputStream inputStream = null;
        T dataObject = null;
        try {
            inputStream = simpleStorageService.downloadAsStream(resultPath[0], "/" + resultPath[1]);
            dataObject = pathUtil.getFileContent(inputStream, clazz);
        } catch (FileDownloadException e) {
            logger.error("读取原脚本文件失败，失败原因:" + e);
            throw new PlatformRuntimeException(new ErrorMessage("读取原脚本文件失败"));
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error("读取原脚本文件后关闭输入流失败，失败原因:" + e);
                }
            }
        }
        return dataObject;
    }

    public SceneNodeInfoDto GetOneNodeInfo(Node n) {
        SceneNodeInfoDto dto = new SceneNodeInfoDto();
        dto.setNodeType(n.getNodeType());
        dto.setScriptId(n.getScriptId());
        dto.setScriptName(n.getScriptName());
        dto.setScriptUrl(n.getScriptUrl());
        dto.setAppId("");
        dto.setAppPackage("");
        dto.setPlatform("");
        logger.debug("场景脚本节点信息 SceneNodeInfoDto:{}, scriptId:{}", dto, n.getScriptId());
        if (ScriptConstant.NODE_TYPE_ANDROID.equals(n.getNodeType()) || ScriptConstant.NODE_TYPE_IOS.equals(n.getNodeType())) {
            AtuScriptInfo appScriptInfo = atuScriptInfoService.findByPrimaryKey(n.getScriptId());
            if (appScriptInfo == null) {
                logger.error("查询移动端脚本不存在.脚本Id:{}, 脚本名:{}", n.getScriptId(), n.getScriptName());
                return dto;
            }
            dto.setAppId(appScriptInfo.getAppId());
            dto.setPlatform(appScriptInfo.getPlatform());
            dto.setAppPackage(appScriptInfo.getAppPackage());
        }
        logger.debug("场景脚本节点信息(SceneNodeInfoDto:{})", dto);
        return dto;
    }

}
