package net.northking.cctp.se.exec;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.element.core.exception.AssertException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.se.config.AtuServerConfig;
import net.northking.cctp.se.debug.bean.DebugerConst;
import net.northking.cctp.se.device.bean.DevicePerInfo;
import net.northking.cctp.se.device.bean.PCDeviceConnection;
import net.northking.cctp.se.device.service.DeviceConnection;
import net.northking.cctp.se.device.service.DeviceConnectionService;
import net.northking.cctp.se.dto.ScriptEngineInfo;
import net.northking.cctp.se.exec.constant.AtuExecConstant;
import net.northking.cctp.se.file.Attachment;
import net.northking.cctp.se.lifecycle.EngineRuntime;
import net.northking.cctp.se.log.bean.StepLog;
import net.northking.cctp.se.plan.AtuTaskExecHeartbeatSchedule;
import net.northking.cctp.se.plan.bean.AutoTask;
import net.northking.cctp.se.plan.bean.QuoteData;
import net.northking.cctp.se.plan.bean.TaskExecResult;
import net.northking.cctp.se.script.bean.Script;
import net.northking.cctp.se.script.bean.ScriptStep;
import net.northking.cctp.se.script.bean.TestTarget;
import net.northking.cctp.se.script.bean.Variable;
import net.northking.cctp.se.script.core.*;
import net.northking.cctp.se.util.HttpUtils;
import net.northking.cctp.se.util.JsonUtils;
import net.northking.cctp.se.util.RegexUtils;
import net.northking.cctp.se.util.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class DefaultExecThread implements AtuExecThread{

    private final static String TASK_THREAD_NAME_TEMPLATE = "ExecThread-%s";
    public static final String DEVICE_INIT_FAIL_MSG = "初始化失败,原因：%s";

    private DeviceConnection deviceConnection;

    private ScriptExecutor scriptExecutor;

    private boolean running = false;

    private Thread thread;

    private String deviceToken;

    private AutoTask task;

    private RabbitTemplate rabbitTemplate;

    private RestTemplate restTemplate;

    public DefaultExecThread(AutoTask task, String deviceToken,boolean running, RabbitTemplate rabbitTemplate) {
        this.task = task;
        this.deviceToken = deviceToken;
        this.running = running;
        this.rabbitTemplate = rabbitTemplate;
        restTemplate = SpringUtils.getBean(RestTemplate.class);
    }

    private ScriptRuntimeListener listener = new ScriptRuntimeListener() {

        @Override
        public void doLog(IScriptRuntimeContext context, String level, String msg) {

        }

        @Override
        public void doLog(IScriptRuntimeContext context, StepLog steplog) {

        }

        @Override
        public void doReply(IScriptRuntimeContext context, StepExecuteResult result) {
            updateTaskResult(result);
        }

        @Override
        public void doReply(IScriptRuntimeContext context, String cmd, String level, Object obj) {

        }
    };




    @Override
    public void destroy(String taskId) {
        log.debug("Destroy the task:[taskId->{}]",taskId);
        //任务取消更新结果
        boolean isFinish = sendCancelResult(taskId);

        this.scriptExecutor = null;
        this.running = false;
        this.task = null;
        try {
            if (!isFinish) {
                thread.interrupt();
            }
        } catch (Exception e) {
            log.error("线程中断异常：",e);
        }

        if (this.deviceConnection != null) {
            try {
                this.deviceConnection.close();
            } catch(Exception e) {
                log.error("任务："+taskId+"终止断开连接异常");
            }
        }
        if (deviceToken != null) {
            //释放设备
            releaseDevice(deviceToken);
        }
        this.listener = null;
        log.debug("task destroyed.");
    }

    private void destroyWhenTaskFinish(String taskId) {
        log.debug("Destroy the finished task:[taskId->{}]",taskId);

        this.scriptExecutor = null;
        this.running = false;
        this.task = null;

        if (this.deviceConnection != null) {
            this.deviceConnection.close();
        }
        this.listener = null;
        log.debug("finished task destroyed.");
    }

    private boolean sendCancelResult(String taskId) {
        try {
            log.debug("任务取消更新步骤结果。。。");
            ScriptStep step = (ScriptStep)this.scriptExecutor.getCurrentStep(); // // todo: 没有开始执行，导致取消任务时候空指针
            StepExecuteResult stepResult = createStepResult(step, taskId);
            updateTaskResult(stepResult);

            log.debug("任务取消更新结果。。。");
            String fileId = uploadScriptResult();
            try {
                AtuServerConfig config = SpringUtils.getBean(AtuServerConfig.class);
                String url = config.getServerUrl() + config.getTaskCancelResultUrl();
                Map<String,String> paramMap = new HashMap<>();
                paramMap.put("taskId",taskId);
                paramMap.put("filePath",fileId);
                ResponseEntity<ResultWrapper> result = restTemplate.postForEntity(url, paramMap, ResultWrapper.class);
                if(HttpStatus.OK.equals(result.getStatusCode())){
                    log.debug("任务取消更新结果成功。。。");
                }
            }catch (Exception e2){
                log.error("终止任务，结果更新失败：",e2);
            }
        } catch (Exception e) {
            log.error("任务终止信息更新失败：",e);
            return true;
        }
        return false;
    }

    private StepExecuteResult createStepResult(ScriptStep step,String requestId) {
        StepExecuteResult stepResult = new StepExecuteResult();
        stepResult.setRetId(UUIDUtil.create32UUID());
        stepResult.setSuccess(false);
        stepResult.setMsg("任务已取消");
        stepResult.setStepId(step.getId());
        stepResult.setRequestId(requestId);
        stepResult.getResult().put(DebugerConst.ReplyConst.KEY_STEP_ID, step.getId());
        stepResult.setFinish(true);
        return stepResult;
    }

    @Override
    public void run() {
        this.thread =  Thread.currentThread();
        thread.setName(String.format(TASK_THREAD_NAME_TEMPLATE,task.getTaskId().substring(0,4)));
        log.debug("Task exec process thread started.");
        log.debug("任务信息："+JsonUtils.toJson(task));
        while(running){
            log.debug("Take a task from queue to handle ...");
            TaskExecResult execResult = initResultMsg();

            //变更任务状态为执行中
            updateExecResult(execResult,AtuExecConstant.TASK_START,"");

            Script script = null;
            try {
                script = initScript(execResult);
            } catch (Exception e) {
                log.error("脚本初始化失败：",e);
                //释放设备
                releaseDevice(deviceToken);
                this.running = false;
                break;
            }

            //脚本执行逻辑处理
            handleAutoTask(execResult,script);
            //断开连接
            destroyWhenTaskFinish(task.getTaskId());
        }
    }


    private void releaseDevice(String deviceToken) {
        if(StringUtils.isNotBlank(deviceToken)){
            AtuServerConfig atuServerConfig = SpringUtils.getBean(AtuServerConfig.class);
            String releaseUrl = atuServerConfig.getServerUrl() + atuServerConfig.getDeviceReleasePath()+deviceToken;
            try {
                ResponseEntity<ResultWrapper> resultEntity = restTemplate.getForEntity(releaseUrl, ResultWrapper.class);
                if (HttpStatus.OK.equals(resultEntity.getStatusCode())) {
                    ResultWrapper bodyResult = resultEntity.getBody();
                    if (bodyResult.isSuccess()) {
                        if ((boolean) bodyResult.getData()) {
                            log.debug("设备退出成功。。。。。");
                        }
                    }else{
                        log.warn("设备释放异常："+JsonUtils.toJson(bodyResult));
                    }
                }else{
                    log.warn("设备释放异常："+JsonUtils.toJson(resultEntity));
                }
            } catch (Exception e1) {
                log.error("设备退出异常：",e1);
                try {
                    String queueName = AtuExecConstant.ENGINE_RELEASE;
                    String[] tokens = {deviceToken};
                    rabbitTemplate.send(queueName,new Message(JSON.toJSONString(Arrays.asList(tokens)).getBytes()));
                } catch (AmqpException e) {
                    log.error("设备退出重试异常：",e);
                }
            }
        }
    }

    private Script initScript(TaskExecResult execResult){
        Script script = null;
        try {
            String scriptPath = task.getScriptPath();
            script = getScriptInfo(scriptPath);
            if (null == script){
                //执行失败，无效的脚本数据
                throw new ExecuteException("执行失败，无效的脚本数据");
            }

            //设置失败不继续执行
            Map<String, Object> globalSettings = script.getGlobalSettings();
            if(CollectionUtils.isEmpty(globalSettings)){
                globalSettings = new HashMap<>();
            }
            globalSettings.put(DebugerConst.ReqConst.KEY_IGNORE_ERROR, false);
            script.setGlobalSettings(globalSettings);
            // 处理是否使用默认值：useDefaultValue
            boolean  useDefaultValue = false;
            if (globalSettings.containsKey(DebugerConst.ReqConst.KEY_USE_DEFAULT_VALUE)) {
                useDefaultValue = (boolean) globalSettings.get(DebugerConst.ReqConst.KEY_USE_DEFAULT_VALUE);
            } else {
                globalSettings.put(DebugerConst.ReqConst.KEY_USE_DEFAULT_VALUE, useDefaultValue);
            }
            Map<String, Object> caseParams = task.getCaseParams();
            Variable[] variables = script.getVariables();
            Map<String, Object> inputParamMap = new HashMap<>();
            if(variables!=null&&variables.length>0){
                for (Variable vb:variables) {
                    String name = vb.getName();
                    // 传入外部参数到脚本中
                    if(!CollectionUtils.isEmpty(caseParams)&&caseParams.containsKey(name)){
                        String actualData = null;
                        if (caseParams.get(name) instanceof Map) {
                            // 获取引用数据
                            log.debug("单元格数据：{}", caseParams.get(name));
                            actualData = getQuoteData((Map)caseParams.get(name));
                        } else if (caseParams.get(name) instanceof String) {
                            actualData = (String) caseParams.get(name);
                            log.debug("脚本变量名称 -> {}, value -> {}", name, caseParams.get(name));
                            // 模糊数据
                            if (RegexUtils.containFuzzyVar(actualData)) {
                                List<String> fuzzyList = RegexUtils.extractFuzzyVar(actualData);
                                if (!fuzzyList.isEmpty()) {
                                    //调用模糊数据接口
                                    for (String fuzzy : fuzzyList) {
                                        String fuzzyValue = getRuleDataByName(fuzzy, task.getProjectId());
                                        log.debug("fuzz -> {} value -> {}", fuzzy, fuzzyValue);
                                        actualData = RegexUtils.replace(actualData, fuzzy, fuzzyValue, RegexUtils.VarType.FUZZY);
                                    }
                                }
                                log.debug("after replace fuzzy data -> {}", actualData);
                            }
                        } else {
                            actualData = String.valueOf(caseParams.get(name));
                        }
                        // 处理是否使用默认值
                        if (!useDefaultValue || StringUtils.isNotBlank(actualData)) {   // 不用默认值 或者 使用默认值但是实际值不为空
                            vb.setValue(actualData);
                            vb.setDefaultValue(actualData);
                        }
                        inputParamMap.put(name, vb.getDefaultValue());
                    }
                    //设置脚本输出项
                    if(vb.isOutput()){
                        execResult.getOutputs().put(name,"");
                    }
                }
                log.debug("脚本输入项：{}", JsonUtils.toJson(variables));
                log.debug("脚本输出项:{}", JsonUtils.toJson(execResult.getOutputs()));
                script.setVariables(variables);
                // 发送队列更新输入参数
                updateTaskInputParam(inputParamMap);
            }

        } catch (Exception e) {
            log.error("脚本信息初始化失败：",e);
            updateExecResult(execResult,AtuExecConstant.TASK_FAIL,"脚本信息初始化失败："+e.getMessage());
            throw new ExecuteException("脚本信息初始化失败："+e);
        }
        return script;
    }

    private String getQuoteData(Map quote) {
        ResponseEntity<QuoteData> resEntity = null;
        try {
            QuoteData quoteData = BeanUtil.toBean(quote, QuoteData.class);
            quoteData.setEnvId(task.getEnvId());
            AtuServerConfig config = SpringUtils.getBean(AtuServerConfig.class);
            String url = config.getServerUrl() + config.getQuoteDataUrl();
            log.debug("环境信息：{}", task.getEnvId());
            resEntity = restTemplate.postForEntity(url, quoteData, QuoteData.class);
            if (resEntity.getStatusCode() == HttpStatus.OK) {
                QuoteData body = resEntity.getBody();
                if (body != null) {
                    return body.getValue();
                }
            }
        } catch (Exception e) {
            if (resEntity != null) {
                log.error("响应状态信息：{}", JsonUtils.toJson(resEntity.getStatusCode()));
            }
            log.error("引用数据转化失败", e);
        }
        return null;
    }


    //脚本步骤结果更新
    private void updateTaskResult(Object msg) {
        if(!this.thread.isInterrupted()){
            //脚本步骤结果写入文件
            FileWriter writer = null;
            try {
                String message = JsonUtils.toJson(msg);
                String replace = message.replace("executed", "isExecuted");
                log.debug("脚本步骤结果写入文件:"+replace);
                String fileName = task.getTenantId()+"_"+task.getTaskId()+".txt";
                String rootPath = System.getProperty("user.dir");
                String filePath = rootPath+"/tmp";
                File file = new File(filePath);
                if(!file.exists()){
                    file.mkdirs();
                }
                File stepFile = new File(filePath+"/"+fileName);
                writer = new FileWriter(stepFile,true);
                writer.write(replace+"\n");
                writer.flush();
            } catch (Exception e) {
                log.error("步骤结果保存失败；",e);
            }finally {
                if (writer != null){
                    try {
                        writer.close();
                    } catch (IOException e) {
                        log.error("",e);
                    }
                }
            }
        }else{
            log.debug("任务线程已取消不更新步骤结果。。。。");
        }
    }
	
	/**
     * 更新任务的输入参数信息
     * @param paramMap
     */
    private void updateTaskInputParam(Map<String, Object> paramMap) {
        try {
            AutoTask update = new AutoTask();
            update.setCaseType(this.task.getCaseType());
            update.setTaskId(this.task.getTaskId());
            update.setCaseParams(paramMap);
            log.debug("更新任务的输入参数信息：{}", JsonUtils.toJson(update));
            RabbitTemplate rabbitTemplate = SpringUtils.getBean(RabbitTemplate.class);
            rabbitTemplate.convertAndSend(AtuExecConstant.TASK_INPUT_PARAM_UPDATE, JsonUtils.toJson(update));
        } catch (Exception e) {
            log.error("更新任务输入参数失败", e);
        }
    }

    /**
     * 更新任务状态
     */
    private void updateExecResult(TaskExecResult result,String type,String message) {
        result.setStatus(type);
        if (this.thread.isInterrupted()) {
            result.setStatus(AtuExecConstant.TASK_CANCEL);
            result.setMessage("线程中断，执行取消");
        }
        result.resetCurrentTime();
        if (StringUtils.isNotBlank(message)) {
            result.setMessage(message);
        }
        if (!AtuExecConstant.TASK_START.equalsIgnoreCase(type)) {
            String fileId = uploadScriptResult();
            if (null != fileId) {
                result.setFilePath(fileId);
            }
        }
        log.debug("任务信息更新：" + JsonUtils.toJson(result));
        try {
            RabbitTemplate rabbitTemplate = SpringUtil.getBean(RabbitTemplate.class);
            rabbitTemplate.convertAndSend(AtuExecConstant.TASK_EXEC_RESULT, JsonUtils.toJson(result));

            if (!AtuExecConstant.TASK_START.equalsIgnoreCase(type) && !AtuExecConstant.TASK_RETRY.equals(type)) {
                log.debug("清理正在执行中任务数据");
                AtuTaskExecHeartbeatSchedule.execTaskMap.remove(result.getTaskId());
            }
        } catch (Exception e) {
            log.error("任务状态信息更新失败；", e);
        }

    }

    private String uploadScriptResult(){
        File tempFile = null;
        String fileId = null;
        try {
            String fileName = task.getTenantId()+"_"+task.getTaskId()+".txt";
            String rootPath = System.getProperty("user.dir");
            String filePath = rootPath+"/tmp/"+fileName;
            tempFile = new File(filePath);
            if(!tempFile.exists()){
                return fileId;
            }
        } catch (Exception e) {
            log.error("生成临时文件失败!", e);
        }
        try {
            log.debug("正在上传结果文件");
            // 需要改成http上传 jieying.li 2023.3.1
            if (tempFile != null && tempFile.exists()) {
                Attachment attachment = HttpUtils.upload(tempFile.getAbsolutePath(), task.getTenantId(), task.getTaskId(), FileBusinessTypeEnum.TASK_EXECUTE_RESULT.getCode());
                if (attachment != null) {
                    fileId = attachment.getId();
                }
            }
            if (StringUtils.isNotBlank(fileId)) {
                log.debug("文件上传成功，返回id：{}", fileId);
            }
            if (tempFile != null) {
                boolean delete = tempFile.delete();
                log.info("删除 tempFile：{}",delete);
            }
        }catch (Exception e) {
            log.error("文件上传失败", e);
        }
        return fileId;
    }

    private TaskExecResult initResultMsg(){
        TaskExecResult result = new TaskExecResult();
        BeanUtils.copyProperties(task,result);
        //获取引擎信息
        EngineRuntime engineRuntime = EngineRuntime.getRuntime();
        ScriptEngineInfo info = engineRuntime.getEngineInfo();
        result.setDeviceId(task.getDeviceId());
        result.setEngineId(info.getEngineId());
        return result;
    }


    private void handleAutoTask(TaskExecResult execResult,Script script){
        Map<String,Object> screenMap = new HashMap<>();
        screenMap.put("tenantId",task.getTenantId());
        screenMap.put("deviceToken",deviceToken);
        try {
            String taskType = task.getTaskType();
            log.debug("任务类型："+taskType);
            if(TYPE_PC.equalsIgnoreCase(taskType)||TYPE_MOBILE.equalsIgnoreCase(taskType)){
                //初始化设备连接
                handleDeviceEnter(execResult);
            }
            //开始执行脚本
            handleExecScriptStart(screenMap,execResult,script);
        } catch (Exception e) {
            log.error("脚本执行结束异常：",e);
            //停止录屏
            boolean flag = false;
            if(Integer.parseInt(task.getScreenRecordSet())!=0){
                flag = true;
            }
            if(this.deviceConnection!=null){
                try {
                    screenMap.put("save",flag);
                    screenMap.put("tenantId",task.getTenantId());
                    screenMap.put("taskId",task.getTaskId());
                    String record = this.deviceConnection.stopRecord(screenMap);
                    if(record!=null){
                        execResult.setVideoPath(record);
                    }
                } catch (Exception e2) {
                    log.error("停止录屏失败!",e2);
                }

                /**
                 * 停止手机获取app和手机性能数据
                 */
                try {
                    if(deviceConnection!=null&&(!this.thread.isInterrupted())){
                        DevicePerInfo devicePerInfo = deviceConnection.stopRecordDevicePer();
                        if(devicePerInfo != null){
                            execResult.setDevicePerInfo(devicePerInfo);
                        }
                    }
                } catch (Exception e2) {
                    log.error("停止手机获取app和手机性能失败：",e2);
                }
            }
            updateExecResult(execResult,AtuExecConstant.TASK_FAIL,"脚本执行异常,原因："+e.getMessage());
        }finally {
            //释放设备
            releaseDevice(deviceToken);
        }
    }


    /**
     * 连接设备逻辑处理
     *
     */
    private void handleDeviceEnter(TaskExecResult execResult) {
        log.debug("Begin to handle device enter");
        if(StringUtils.isNotBlank(deviceToken)){
            try{

                DeviceConnection connection = SpringUtils.getBean(DeviceConnectionService.class).createDeviceConnection(deviceToken);
                if (this.scriptExecutor!=null ){//如果不为空则切换设备驱动模式
                    scriptExecutor.setContextVariable(IScriptRuntimeContext.KEY_CURRENT_DRIVER,connection.getDeviceDriver());
                    this.scriptExecutor.setCurrentTaskId(task.getTaskId());
                }
                if (this.deviceConnection!=null){
                    this.deviceConnection.close();
                }
                if(null == connection){
                    log.warn("创建连接失败!");
                    Thread.sleep(2000);
                    throw new ExecuteException("创建设备连接失败");
                }
                Thread.sleep(2000);
                this.deviceConnection = connection;
                if(Integer.parseInt(task.getScreenRecordSet())!=0){
                    Map<String, String> otherInfo = deviceConnection.getDeviceInfo().getOtherInfo();
                    if (null == otherInfo) {
                        otherInfo = new HashMap<>();
                    }
                    otherInfo.put("taskId",task.getTaskId());
                    otherInfo.put("tenantId", task.getTenantId());
                    deviceConnection.getDeviceInfo().setOtherInfo(otherInfo);
                    //开启录屏
                    deviceConnection.startRecord();
                }
                log.debug("设备连接成功  ---->  设备token【{}】",deviceToken);
            }catch (Exception e){
                log.error("连接设备失败",e);
                log.debug("设备连接失败  ---->  设备token【{}】",deviceToken);
                try {
                    Thread.sleep(2000);
                } catch (Exception ex) {
                    log.debug("");
                }
                throw new ExecuteException("设备连接失败", e);
            }
        }
    }

    /**
     * mobile设备初始化逻辑处理
     */
    private void handleMobileDeviceInit(String taskType) {
        log.debug("Begin to handle mobile device init");

        if (this.deviceConnection == null){
            log.error("初始化失败,原因：移动设备连接不存在");
            throw new ExecuteException("初始化失败,原因:移动设备连接不存在");
        }
        try {
            Map<String, Object> initData = new HashMap<>();
            initData.put("deviceId",task.getDeviceId());
            initData.put("tenantId", task.getTenantId());
            initData.put("caseId", task.getTaskId());
            initData.put("debug", false);
            if(StringUtils.isNotBlank(task.getAppId())){
                String appId = task.getAppId();
                initData.put("appId",appId);
                String appDownloadUrl = task.getAppDownloadUrl().replace("{appId}", appId);
                initData.put("appDownloadUrl", appDownloadUrl);

            }
            initData.put("forceCleanData",false);
            initData.put("forceReInstall",false);
            if(StringUtils.isNotBlank(task.getAppSet())){
                //清楚数据
                if("1".equalsIgnoreCase(task.getAppSet())){
                    initData.put("forceCleanData",true);
                }
                //卸载安装
                if("2".equalsIgnoreCase(task.getAppSet())){
                    initData.put("forceReInstall",true);
                }
                //清楚数据卸载安装
                if("3".equalsIgnoreCase(task.getAppSet())){
                    initData.put("forceReInstall",true);
                    initData.put("forceCleanData",true);
                }
            }
            log.debug("初始化app参数：-----> "+JsonUtils.toJson(initData));
            deviceConnection.initConfig(initData);
            log.debug("移动应用初始化成功 --> ----> taskType【{}】, 设备token【{}】", taskType, deviceToken);
        }catch (Exception e){
            log.warn("移动应用初始化失败",e);
            if (e instanceof ExecuteException) {
                throw e;
            }
            throw new ExecuteException("移动应用初始化失败", e);
        }
    }

    private Map<String,Object> getAppDetail(String appId){
        Map<String, Object> data = new HashMap<>();
        try {
            AtuServerConfig config = SpringUtils.getBean(AtuServerConfig.class);
            String url = config.getServerUrl() + config.getAppInfoUrl() +"?appId="+appId;
            ResponseEntity<ResultWrapper> result = restTemplate.getForEntity(url,ResultWrapper.class);
            if(HttpStatus.OK.equals(result.getStatusCode())){
                ResultWrapper wrapper = result.getBody();
                if(wrapper.isSuccess()){
                    data = (Map<String,Object>)wrapper.getData();
                }
            }
        }catch (Exception e2){
            log.error("app信息获取失败：",e2);
        }
        log.info("获取到app的参数详情：{}",JSON.toJSONString(data));
        return data;
    }

    /**
     * PC设备初始化逻辑处理
     */
    private void handlePcDeviceInit(TestTarget[] targets,int count) {
        log.debug("Begin to handle pc device init");
        Map<String, Object> initData = new HashMap<>();
        boolean isBs = false;
        if(targets.length>0){
            TestTarget target = targets[0];
            String targetType = target.getTargetType();
            if("web".equalsIgnoreCase(targetType)){
                log.debug("BS端pc应用初始化");
                isBs = true;
                initData.put(PCDeviceConnection.PROJECT_ID, target.getTarget().get("projectId"));
                initData.put(PCDeviceConnection.SYS_NAME, target.getTarget().get("sysName"));
            }
        }
        if (this.deviceConnection == null){
            log.error("初始化失败,原因：{}", "pc设备连接不存在");
            throw new ExecuteException("初始化失败,原因:pc设备连接不存在");
        }
        try {
            initData.put(PCDeviceConnection.ENV_ID, task.getEnvId());
            initData.put(PCDeviceConnection.DEVICE_ID,task.getDeviceId());
            initData.put(PCDeviceConnection.BROWSER_TYPE,task.getBrowserType());
            log.debug("初始化数据：{}", initData);
            if(count>0 && isBs){
                //重试
                deviceConnection.restart(initData);
            }else{
                deviceConnection.initConfig(initData);
            }
            log.debug("PC应用初始化成功 --> ----> 设备token【{}】", deviceToken);
        }catch (Exception e){
            String errorMsg = "PC应用初始化失败";
            log.warn(errorMsg,e);
            if (e.getMessage() != null) {
                errorMsg = errorMsg + "," + e.getMessage();
            }
            throw new ExecuteException(errorMsg, e);
        }
    }

    /**
     * 脚本执行逻辑处理
     */
    private void handleExecScriptStart(Map<String,Object> screenMap,TaskExecResult result,Script script){
        log.debug("开始解析执行脚本。。。。");
        if(StringUtils.isBlank(task.getScriptPath())){
            throw new ExecuteException("脚本地址为空无法获取脚本！");
        }
        //录屏保存标识
        boolean stopRecord = false;
        if(1==Integer.parseInt(task.getScreenRecordSet())){
            stopRecord = true;
        }

        //设置重试次数
        int retry = 1;
        if(task.getFailRetryNum()>0){
            retry = task.getFailRetryNum()+1;
        }
        if (AtuExecConstant.TASK_RETRY_STRATEGY_PACKAGE.equals(task.getRetryStrategy())) { // 重试策略如果是失败任务打包重试，那么重试次数应该为0，由计划执行完一轮后统一下发
            retry = 1;
        }
        // 创建脚本执行器
        this.scriptExecutor = SpringUtils.getBean(ScriptResolutionService.class).getScriptExecutor(task.getTaskId());
        if (null == scriptExecutor){
            Map<String,Object> taskInfo = new HashMap<>();
            taskInfo.put("scheduleId",task.getBatchId());
            taskInfo.put("projectId",task.getProjectId());
            taskInfo.put("snapshot",false);
            taskInfo.put("errorSnapshot", false);
            //全部截图
            if(1 == Integer.parseInt(task.getScreenshotSet())){
                taskInfo.put("snapshot",true);
                taskInfo.put("errorSnapshot", true);
            }
            //失败截图
            if(2 == Integer.parseInt(task.getScreenshotSet())){
                taskInfo.put("errorSnapshot",true);
            }
            if (StringUtils.isNotBlank(task.getEnvId())) {
                taskInfo.put("envId", task.getEnvId());
            }
            scriptExecutor = SpringUtils.getBean(ScriptResolutionService.class).createTaskScriptRuntime(taskInfo,script);
            scriptExecutor.setRuntimeListener(this.listener);
            // 准备脚本信息 20230204 jieying.li
            if (!TYPE_INTERFACE.equalsIgnoreCase(task.getTaskType())) {
                scriptExecutor.setContextVariable(IScriptRuntimeContext.KEY_CURRENT_DRIVER,this.deviceConnection.getDeviceDriver());
                scriptExecutor.setContextVariable(IScriptRuntimeContext.KEY_CURRENT_CONN,this.deviceConnection);
                scriptExecutor.setContextVariable(IScriptRuntimeContext.KEY_TASK_TENANT,task.getTenantId());
            }
        }
        scriptExecutor.setRequestId(task.getTaskId());    // 设置请求标识id
        scriptExecutor.setContextVariable(IScriptRuntimeContext.IS_DEBUG,false);
        //查询应用详情
        if (TYPE_MOBILE.equalsIgnoreCase(task.getTaskType())) {
            Map<String, Object> appDetail = getAppDetail(task.getAppId());
            scriptExecutor.setContextVariable(IScriptRuntimeContext.APP_VERSION, appDetail.get("buildVersion"));  //app版本
        } else {
            log.debug("不是移动端执行，无需设置应用版本。。。。。。。。。。。。");
        }
        ScriptExecuteResult executeResult = null;
        //结果中展示重试次数
        int count = 0;
        for (int j = 0; j < retry; j++) {
            Script copyScript = ObjectUtil.cloneByStream(script);
            count = j;
            if(j>0){
                log.debug("第["+j+"]次重试执行脚本");
            }
            try {
                //初始化结果文件
                try {
                    String fileName = task.getTenantId()+"_"+task.getTaskId()+".txt";
                    String rootPath = System.getProperty("user.dir");
                    String filePath = rootPath+"/tmp/"+fileName;
                    File tempFile = new File(filePath);
                    boolean delete = tempFile.delete();
                    log.info("删除 tempFile:{}",delete);
                } catch (Exception e) {
                    log.error("文件初始化删除失败：",e);
                }
                executeResult = new ScriptExecuteResult();
                if(!this.thread.isInterrupted()){
                    String taskType = task.getTaskType();
                    log.debug("任务类型："+taskType);
                    //初始化设备连接
                    if(TYPE_MOBILE.equalsIgnoreCase(taskType)){
                        //移动应用初始化
                        handleMobileDeviceInit(taskType);
                    }else if(TYPE_PC.equalsIgnoreCase(taskType)){
                        //初始化PC应用
                        handlePcDeviceInit(copyScript.getTestTargets(),count);
                    }
                    
                    // 开始执行全部
                    scriptExecutor.setContextVariable(IScriptRuntimeContext.KEY_CURRENT_REQUEST, task.getCaseParams());
                    // 需要重置参数数据
                    scriptExecutor.reset(copyScript);
                    // 传入外部参数到脚本中


                    executeResult = scriptExecutor.runScript(copyScript, executeResult);

                    //判断是否重试
                    if (executeResult.getResultStatus()==0) {
                        break;
                    }
                }else{
                    break;
                }
            } catch (AssertException ae) {
                //断言失败
                log.error("第["+j+"]次执行任务断言失败：",ae);
                if(StringUtils.isBlank(executeResult.getResultMsg())){
                    executeResult.setResultMsg(ae.getMessage());
                }
                executeResult.setResultStatus(2);
                // 更新结果到计划服务，新增一条执行记录，用于记录任务重试
                retryExecuteCheck(j + 1 < retry, result, AtuExecConstant.TASK_ASSERT_FAIL);
            }catch (Exception e) {
                //执行失败
                log.error("第["+j+"]次执行任务执行失败：",e);
                if(StringUtils.isBlank(executeResult.getResultMsg())){
                    String msg = "任务执行失败";
                    if (StringUtils.isNotBlank(e.getMessage())){
                        msg = msg + ",失败原因："+e.getMessage();
                    }
                    executeResult.setResultMsg(msg);
                }
                executeResult.setResultStatus(1);
                // 更新结果到计划服务，新增一条执行记录，用于记录任务重试
                retryExecuteCheck(j + 1 < retry, result, AtuExecConstant.TASK_ASSERT_FAIL);
            }
        }
        try {
            if(null==executeResult){
                updateExecResult(result,AtuExecConstant.TASK_FAIL,"自动重试次数："+count+";任务执行失败,无结果返回或未执行。");
            }else{
                if ((executeResult.getResultStatus()!=0) && (2 == Integer.parseInt(task.getScreenRecordSet()))) {
                    //出错保存录屏
                    stopRecord = true;
                }
                if(2==executeResult.getResultStatus()){
                    //断言失败增加截图
                    log.debug("断言失败步骤截图！~");
                    try {
                        DeviceConnection deviceConnection = (DeviceConnection)scriptExecutor.getContextVariable(IScriptRuntimeContext.KEY_CURRENT_CONN);
                        String tenantId = scriptExecutor.getContextVariable(IScriptRuntimeContext.KEY_TASK_TENANT).toString();
                        String imageUrl = deviceConnection.snapshotAllScreen(tenantId, task.getTaskId());
                        //放入断言失败结果中
                        result.setImageUrl(imageUrl);
                    } catch (Exception e) {
                        log.error("断言失败步骤截图失败：",e);
                    }
                }
                //执行结束向结果中传递输出参数
                Map<String, Object> outputs = result.getOutputs();
                if(!CollectionUtils.isEmpty(outputs)){
                    List<StepExecuteResult> stepResults = executeResult.getStepExecuteResults();
                    if(!CollectionUtils.isEmpty(stepResults)){
                        StepExecuteResult finalStep = stepResults.get(stepResults.size() - 1);
                        Map<String,String> variables = (Map<String,String>)finalStep.getResult().get("variables");
                        for (String name:outputs.keySet()) {
                            try {
                                String value = variables.get(name);
                                if(null!=value){
                                    outputs.put(name,value);
                                }
                            } catch (Exception e) {
                                log.error("输出参数{}获取值异常",name,e);
                            }
                        }
                    }
                }
                try {
                    screenMap.put("save",stopRecord);
                    screenMap.put("tenantId",task.getTenantId());
                    screenMap.put("taskId",task.getTaskId());
                    if(deviceConnection!=null&&(!this.thread.isInterrupted())){
                        //休眠2秒等待视频结束完成
                        Thread.sleep(2000);
                        String videoPath = deviceConnection.stopRecord(screenMap);
                        if(null!=videoPath){
                            result.setVideoPath(videoPath);
                        }
                    }
                } catch (Exception e) {
                    log.error("停止录屏失败：",e);
                }
                /**
                 * 停止手机获取app和手机性能数据
                 */
                try {
                    log.debug("停止手机获取app和手机性能数据");
                    if(deviceConnection!=null&&(!this.thread.isInterrupted())){
                        DevicePerInfo devicePerInfo = deviceConnection.stopRecordDevicePer();
                        if(devicePerInfo != null){
                            result.setDevicePerInfo(devicePerInfo);
                        }
                    }
                } catch (Exception e) {
                    log.error("停止手机获取app和手机性能失败：",e);
                }
                String resultMsg = "";
                if(0!=executeResult.getResultStatus()){
                    resultMsg = "自动重试次数："+count+";"+executeResult.getResultMsg();
                }
                updateExecResult(result,Integer.toString(executeResult.getResultStatus()+2),resultMsg);
            }

        }catch(Exception e){
            //捕获异常信息不往上抛 前面则抛出方便上层停止录屏
           log.error("执行任务异常：",e);
           updateExecResult(result,AtuExecConstant.TASK_FAIL,"自动重试次数："+count+";任务执行结果返回失败,请检查引擎结果返回");
        } finally {
            //释放设备
            releaseDevice(deviceToken);
        }
    }

    /**
     * 单条任务重试检查
     * @param result
     */
    private void retryExecuteCheck(boolean forFlag, TaskExecResult result, String resultStatus) {
        if (!forFlag) {
            return;
        }
        try {
            result.setNeedRetry(true);
            if ((task.getRetryStrategy() != null && AtuExecConstant.TASK_RETRY_STRATEGY_SINGLE.equals(task.getRetryStrategy())) || (task.getFailRetryNum() > 0 && StringUtils.isBlank(task.getRetryStrategy()))) {  // 单条重试，兼容以前没有但是重试次数不为0
                log.debug("发送信息到平台保存记录，准备重试...");
                updateExecResult(result, resultStatus, "");
            }
        } catch (Exception e) {
            log.error("新增任务[{}]执行记录失败", task.getTaskId(), e);
        } finally {
            result.setNeedRetry(false);
        }
    }

    private Script getScriptInfo(String scriptPath){
        AtuServerConfig config = SpringUtils.getBean(AtuServerConfig.class);
        String url = config.getServerUrl() + config.getFileDownloadPath() + scriptPath;
        log.debug("请求获取脚本地址：{}", url);
        try {
            ResponseEntity<byte[]> resultEntity = restTemplate.getForEntity(url, byte[].class);
            if (HttpStatus.OK.equals(resultEntity.getStatusCode())) {
                byte[] content = resultEntity.getBody();
                String msg = new String(content, StandardCharsets.UTF_8);
                Script script = JSON.parseObject(msg).toJavaObject(Script.class);
                return script;
            }
        } catch (Exception e) {
            log.error("获取脚本信息失败：",e);
        }
        return null;
    }

    private String getRuleDataByName(String ruleName,String proId){
        try {
            AtuServerConfig config = SpringUtils.getBean(AtuServerConfig.class);
            String ruleNameEncode = URLEncoder.encode(ruleName, StandardCharsets.UTF_8.name());
            String url = config.getServerUrl() + config.getRuleDataByName() + "?ruleName="+ruleNameEncode+"&proId="+proId;
            ResponseEntity<ResultWrapper> resultEntity = restTemplate.getForEntity(url, ResultWrapper.class);
            if (HttpStatus.OK.equals(resultEntity.getStatusCode())) {
                ResultWrapper bodyResult = resultEntity.getBody();
                if (bodyResult != null && bodyResult.isSuccess()) {
                    String data = (String) bodyResult.getData();
                    return data;
                } else {
                    log.debug("获取模糊数据失败，返回信息：{}", JsonUtils.toJson(bodyResult));
                    if(bodyResult==null){
                        throw new ExecuteException("获取模糊数据失败，返回信息: "+JsonUtils.toJson(bodyResult));
                    }else{
                        throw new ExecuteException(bodyResult.getMessage());
                    }
                }
            } else {
                log.debug("获取模糊数据失败 请求状态：{}，{}", resultEntity.getStatusCode(), JsonUtils.toJson(resultEntity.getBody()));
                throw new ExecuteException(resultEntity.getStatusCode().name());
            }
        } catch (Exception e) {
            log.error("获取模糊测试数据失败：",e);
            throw new ExecuteException(e.getMessage());
        }
    }
    

}
