package org.matrix.actuators.usecase;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.matrix.actuators.Actuator;
import org.matrix.actuators.checkpoint.CheckPointActuator;
import org.matrix.actuators.httpclient.HttpClientActuator;
import org.matrix.actuators.move.MoveActuator;
import org.matrix.actuators.move.MoveStrategy;
import org.matrix.actuators.util.ThreadUtil;
import org.matrix.database.entity.*;
import org.matrix.actuators.checkpoint.CheckPoint;
import org.matrix.actuators.checkpoint.CheckPointResult;
import org.matrix.actuators.httpclient.HttpRequestDetail;
import org.matrix.actuators.httpclient.HttpResponseDetail;
import org.matrix.database.service.IExecutionHistoryService;
import org.matrix.enums.ExecutionHistoryStatus;
import org.matrix.exception.GlobalException;
import org.matrix.socket.*;
import org.matrix.util.SpringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 测试用例执行器
 * @author huangxiahao
 */
@Component
public class CaseActuator implements Actuator {

    private final CheckPointActuator checkPointActuator;

    private final HttpClientActuator httpClientActuator;

    private final IExecutionHistoryService executionHistoryService;

    private final String baseJsPath = "syntaxCheck.js";

    public CaseActuator(CheckPointActuator checkPointActuator, HttpClientActuator httpClientActuator, IExecutionHistoryService executionHistoryService) {
        this.checkPointActuator = checkPointActuator;
        this.httpClientActuator = httpClientActuator;
        this.executionHistoryService = executionHistoryService;
    }


    /**
     * @param projectId 项目ID
     * @param envId 环境ID
     * 执行测试用例，这个方法对内使用，由其他执行器调用
     */
    public TestCaseExecuteResult executeTestCase(TestCaseBTO testCaseBto, Long envId, Long projectId) {
        try {
            LogQueueRuntime.addNewLog("[用例执行器] 开始执行单条用例数据！！");
            LogQueueRuntime.addNewLog(String.format("[用例执行器] 当前正在执行用例ID: %s 用例名： %s", testCaseBto.getTestCase().getId(), testCaseBto.getTestCase().getName()));
            //执行前置动作
            executeMove(testCaseBto.getTestCase().getMoveAfterCase()
                    , envId, projectId, null, MoveStrategy.PRE_MOVE);
            //向线程中设置当前正在执行的DataId
            LogQueueRuntime.setTestData(testCaseBto.getTestData().getId());
            //执行测试用例的本体内容
            HttpResponseDetail baseTestCaseResponseDetail = getHttpResponseDetail(
                    envId,
                    projectId,
                    testCaseBto.getTestCase(),
                    testCaseBto.getTestData());
            //执行中置动作
            executeMove(testCaseBto.getTestCase().getMoveAfterTest()
                    , envId, projectId, baseTestCaseResponseDetail.getResponseBody(), MoveStrategy.MID_MOVE);
            //进行检验
            CheckPointResult checkPointResult = getCheckPointResult(testCaseBto.getTestCase(),
                    testCaseBto.getTestData(),
                    envId,
                    projectId,
                    baseTestCaseResponseDetail);
            //执行后置动作
            executeMove(testCaseBto.getTestCase().getMoveAfterTest()
                    , envId, projectId, baseTestCaseResponseDetail.getResponseBody(), MoveStrategy.AFT_MOVE);
            TestCaseExecuteResult testCaseExecuteResult = new TestCaseExecuteResult(baseTestCaseResponseDetail, checkPointResult);
            LogQueueRuntime.addNewLog(JSON.toJSONString(testCaseExecuteResult));
            //将线程中正在执行的DataId清除
            LogQueueRuntime.clearTestData();
            return testCaseExecuteResult;
        } finally {
            LogQueueRuntime.remove(ThreadUtil.currentThreadId());
        }
    }


    /**
     * 执行一条测试用例的多条测试数据
     */
    public List<TestCaseExecuteResult> executeTestCases(TestCaseListDataBto testCaseBto, Long envId, Long projectId) {
        LogQueueRuntime.addNewLog("[用例执行器] 开始执行用例！！" );
        try {
            LogQueueRuntime.addNewLog(String.format("[用例执行器] 当前正在执行用例ID: %s 用例名： %s", testCaseBto.getTestCase().getId(), testCaseBto.getTestCase().getName()));
            //todo 黄夏豪 执行前置动作
            LogQueueRuntime.addNewLog("[用例执行器] 开始执行前置动作！！");
            //执行测试用例的本体内容
            List<TestCaseExecuteResult> resultList = new ArrayList<>();
            for (TestData testData : testCaseBto.getTestDataList()) {
                //向线程中设置当前正在执行的DataId
                LogQueueRuntime.setTestData(testData.getId());
                changeExecutionHistoryStatus(ExecutionHistoryStatus.RUN);
                LogQueueRuntime.addNewLog("[用例执行器] 开始执行数据组ID：" + testData.getId());
                HttpResponseDetail baseTestCaseResponseDetail = getHttpResponseDetail(
                        envId,
                        projectId,
                        testCaseBto.getTestCase(),
                        testData);
                LogQueueRuntime.addNewLog("[用例执行器] 即将开始执行中置动作");
                //todo 黄夏豪 执行测试后动作
                CheckPointResult checkPointResult = getCheckPointResult(testCaseBto.getTestCase(),
                        testData,
                        envId,
                        projectId,
                        baseTestCaseResponseDetail);
                TestCaseExecuteResult testCaseExecuteResult = new TestCaseExecuteResult(baseTestCaseResponseDetail, checkPointResult);
                resultList.add(testCaseExecuteResult);
                LogQueueRuntime.addNewLog(JSON.toJSONString(testCaseExecuteResult));
                //todo 黄夏豪 执行后置动作
                changeExecutionHistoryStatus(ExecutionHistoryStatus.FINISH);
            }
            //将线程中正在执行的DataId清除
            LogQueueRuntime.clearTestData();
            return resultList;
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 调用动作
     *
     * @param moveString     用例中的moveId组 例如 1,2,3,4
     * @param envId          环境id
     * @param projectId      项目id
     * @param caseResultData 测试用例执行后保留下的结果集,该参数在前置策略中可以为null,在中间/后置策略中必须要提供合理的结果集对象
     * @param strategy       动作的策略，即是前置/中间/后置,具体查看{@link MoveStrategy}
     */
    private void executeMove(String moveString, Long envId, Long projectId, String caseResultData, MoveStrategy strategy) {
        String[] moveIds = moveString.split(",");
        for (String moveId : moveIds) {
            getMoveActuator().runMove(
                    Long.valueOf(moveId),
                    envId,
                    projectId,
                    caseResultData,
                    strategy
            );
        }
    }

    private CheckPointResult getCheckPointResult(TestCase testCase
            , TestData testData
            , Long envId
            , Long projectId
            , HttpResponseDetail baseTestCaseResponseDetail) {
        if (testCase.getType().equals(TestCaseTypeEnum.HTTP.getValue())) {
            return
                    checkPointActuator.httpCheck(
                            baseTestCaseResponseDetail,
                            getCheckPointEntity(testData),
                            envId,
                            projectId
                    );
        }
        return null;
    }

    private HttpResponseDetail getHttpResponseDetail(
            Long envId, Long projectId, TestCase testCase, TestData testData) {
        if (testCase.getType().equals(TestCaseTypeEnum.HTTP.getValue())) {
            HttpRequestDetail httpRequestDetail = JSON.parseObject(testData.getDetail(), HttpRequestDetail.class);
            return httpClientActuator.sendHttpRequest(httpRequestDetail, envId, projectId);
        }
        return null;
    }

    private CheckPoint getCheckPointEntity(TestData testData) {
        CheckPoint checkPoint = new CheckPoint();
        checkPoint.setUseExceptionCheck(testData.getAbnormalCheckpoint() != 0);
        checkPoint.setUseNullCheck(testData.getNoEmptyCheckpoint() != 0);
        checkPoint.setContainCheckPoint(testData.getContainCheckpoint());
        checkPoint.setNoContainCheckPoint(testData.getNoContainCheckpoint());
        checkPoint.setJsonPathCheckPoint(testData.getJsonpathCheckpoint());
        return checkPoint;
    }

    private static MoveActuator getMoveActuator() {
        return SpringUtils.getBean("moveActuator");
    }

    /**
     * @param caseExecuteVo 执行所需要的详细信息
     * @param session       如果是从socket运行并希望获取实时日志请从这里运行
     *                      外界如果执行测试用例的话请走这个接口,执行TestCase，并控制运行态日志池，进行日志的生成。
     */
    public List<TestCaseExecuteResult> runTestCase(WebSocketSession session, CaseExecuteVo caseExecuteVo) {
        String unionKey = UUID.randomUUID().toString();
        Long currentThreadId = ThreadUtil.currentThreadId();
        try {
            //将websocketSession 加入到socket池子中
            TestCaseExecuteSocketPool.add(currentThreadId, session);
            List<TestCaseListDataBto> testCaseListDataBtoList = caseExecuteVo.getTestCaseListDataBtoList();
            //建立执行历史（ExecutionHistory）
            insertExecutionHistory(unionKey, caseExecuteVo);
            for (TestCaseListDataBto testCaseListDataBto : testCaseListDataBtoList) {
                LogQueueRuntime
                        .initTestCaseLog(
                                caseExecuteVo.getJobId(),
                                caseExecuteVo.getUserId()
                                , testCaseListDataBto.getTestCase().getId()
                                , caseExecuteVo.getType()
                                , unionKey
                        );

                //执行测试用例
                return executeTestCases(testCaseListDataBto, caseExecuteVo.getEnvId(), caseExecuteVo.getProjectId());
            }
        } catch (GlobalException e) {
            try {
                if (session != null && session.isOpen()) {
                    TestExecuteLog currentTestExecute = LogQueueRuntime.getCurrentTestExecute(ThreadUtil.currentThreadId());
                    if (currentTestExecute!=null){
                        LogQueueRuntime.addNewLog(e.getMessage());
                    }else {
                        //这里加-1.-1.-1是用来表明这里发出去的数据是不属于任何执行中的用例的
                        session.sendMessage(new TextMessage(String.format("-1.-1.-1.%s", e.getMessage())));
                    }
                }
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } finally {
            //将本次产生的数据清除
            TestCaseExecuteSocketPool.remove(currentThreadId);
            //将本次产生的日志从执行状态设为停止状态
            LogQueueRuntime.remove(currentThreadId);
            //todo 黄夏豪 将数据库中的执行历史状态设置为完成
        }
        return null;
    }

    public void insertExecutionHistory(String unionKey, CaseExecuteVo caseExecuteVo) {
        List<TestCaseListDataBto> testCaseListDataBtoList = caseExecuteVo.getTestCaseListDataBtoList();
        for (TestCaseListDataBto testCaseListDataBto : testCaseListDataBtoList) {
            List<TestData> testDataList = testCaseListDataBto.getTestDataList();
            TestCase testCase = testCaseListDataBto.getTestCase();
            for (TestData testData : testDataList) {
                ExecutionHistory executionHistory = new ExecutionHistory();
                executionHistory.setJobId(caseExecuteVo.getJobId());
                executionHistory.setCaseId(testCase.getId());
                executionHistory.setDataId(testData.getId());
                executionHistory.setUnionKey(unionKey);
                executionHistoryService.save(executionHistory);
            }
        }
    }

    public void changeExecutionHistoryStatus(
            ExecutionHistoryStatus executionHistoryStatus) {
        TestExecuteLog currentTestExecute = LogQueueRuntime.getCurrentTestExecute(ThreadUtil.currentThreadId());
        if (currentTestExecute!=null){
            ExecutionHistory executionHistory = new ExecutionHistory();
            executionHistory.setStatus(executionHistoryStatus);
            executionHistoryService.update(executionHistory, Wrappers
                    .lambdaUpdate(ExecutionHistory.class)
                    .eq(ExecutionHistory::getJobId,currentTestExecute.getJobId())
                    .eq(ExecutionHistory::getCaseId,currentTestExecute.getTestCaseId())
                    .eq(ExecutionHistory::getDataId,currentTestExecute.getTestDataId())
                    .eq(ExecutionHistory::getUnionKey,currentTestExecute.getUnionKey())
            );
        }


    }


}
