package org.matrix.actuators.sql;

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.matrix.actuators.Actuator;
import org.matrix.actuators.datasource.DataSourceDTO;
import org.matrix.actuators.datasource.IDataSourceService;
import org.matrix.actuators.env.EnvironmentActuator;
import org.matrix.actuators.httpclient.HttpClientActuator;
import org.matrix.actuators.httpclient.HttpRequestDetail;
import org.matrix.actuators.httpclient.HttpResponseDetail;
import org.matrix.actuators.usecase.CaseActuator;
import org.matrix.database.entity.DynamicVariable;
import org.matrix.database.entity.TestCaseBTO;
import org.matrix.database.service.IConnectService;
import org.matrix.database.service.IDynamicVariableService;
import org.matrix.database.service.ITestDataService;
import org.matrix.enums.DynamicVarType;
import org.matrix.exception.GlobalException;
import org.matrix.socket.queue.LogQueueRuntime;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.matrix.enums.DynamicVarType.*;
import static org.matrix.enums.ModuleType.SQL_ACTUATOR;
import static org.springframework.util.CollectionUtils.isEmpty;

/**
 * SqlExpParse.
 * SQL表达式解析器
 *
 * @author Matrix <xhyrzldf@gmail.com>
 * @since 2022/1/7 at 5:49 PM
 * Suffering is the most powerful teacher of life.
 */
@SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
@Slf4j
@Component
@AllArgsConstructor
public class SqlExpActuator implements Actuator {

    /**
     * 用于正则找出形如${id} 或者 ${id}[1] 这样的SQL变量
     */
    public static final String DYNAMIC_VAR_EXP = "\\$\\{(\\w*)}\\[?(\\d*)]?";

    //language=RegExp
    /**
     * 用于replaceAll的正则，目的是替换掉形如${id} 或者是 ${id}[1] 这样的SQL变量
     */
    public static final String REPLACE_VAR_EXP = "\\$\\{%s}(\\[\\w*])?";

    private final JdbcTemplate jdbcTemplate;
    private final CaseActuator caseActuator;
    private final HttpClientActuator httpActuator;
    private final EnvironmentActuator envActuator;
    private final IDynamicVariableService varService;
    private final IConnectService connectService;
    private final IDataSourceService dataSourceService;
    private final ITestDataService dataService;

    /**
     * 解析并运行SQL语句，可以包含别的SQL变量,例如 'select * from user where user.id = ${user_id} '
     *
     * @param sqlDetail sql的poolId与sql语句的对象
     * @param envId     环境id
     * @param projectId 项目id
     * @return 运行SQL后取完数值的结果, 是一张数据表, 以LIST<MAP>的形式呈现
     */
    public List<Map<String, Object>> parseSql(SqlExpDetail sqlDetail, Long envId, Long projectId) {
        // 找到第一层的SQL变量，递归替换
        String sqlExp = sqlDetail.getSqlExp();
        Long connectId = sqlDetail.getPoolId();
        List<SqlRegularObject> varList = findDynamicVarList(sqlExp);
        for (SqlRegularObject sqlReg : varList) {
            sqlExp = sqlExp.replaceAll(sqlReg.getVarName(), parseVarByName(sqlReg.getVarName(), envId, projectId));
        }

        // 运行SQL
        DataSourceDTO dataSourceDTO = Optional.of(connectService.getById(connectId))
                .orElseThrow(() -> new GlobalException(String.format("没有找到id = %d 的连接池connect对象", connectId)))
                .toDataSourceDTO();

        // 替换环境共享变量
        sqlExp = envActuator.replaceEnvVar(sqlExp, envId);

        // 校验dynamicVar里的detail是否是可以直接执行的SQL
        boolean allVarParsed = findDynamicVarList(sqlExp).size() == 0;
        if (allVarParsed) {
            // 切换数据源,执行SQL,获取数值
            Set<String> dataSources = dataSourceService.switchDataSource(dataSourceDTO);
            log.info("当前存在的数据源 {}", dataSources);
            List<Map<String, Object>> resultMap = jdbcTemplate.queryForList(sqlExp);
            dataSourceService.switchMainDataSource();
            return resultMap;
        } else {
            throw new GlobalException(String.format("SQL解析异常,SQL语句为%s,连接池id为%d", sqlDetail.getSqlExp(), sqlDetail.getPoolId()));
        }

    }

    /**
     * 解析给定的动态变量ByName
     *
     * @param varNameString 变量名,形如${id}或者是 ${id}[1]这样的字符串,默认下标为0
     * @param projectId     该变量所在的项目ID
     * @return 变量递归解析后的值
     */
    public String parseVarByName(String varNameString, Long envId, Long projectId) {
        LogQueueRuntime.addNewLog(this.getClass(), SQL_ACTUATOR, String.format("初始化即系变量,变量名=%s,环境id=%d,项目id=%d", varNameString, envId, projectId));
        List<SqlRegularObject> varList = findDynamicVarList(varNameString);
        if (varList.size() == 1) {
            SqlRegularObject sqlReg = varList.get(0);
            DynamicVariable variable = varService.getByName(sqlReg.getVarName(), projectId)
                    .map(DynamicVariable::parseSqlDetail)
                    .orElseThrow(() -> new GlobalException(String.format("没有找到project id = %d 且 name = %s 的动态变量", projectId, sqlReg.getVarName())));
            return parseVar(envId, sqlReg.getIndex(), projectId, variable);
        } else {
            throw new GlobalException("varNameString 参数请一次只输入一个动态变量! 你的提供的参数为: " + varNameString);
        }
    }

    /**
     * 解析给定的动态变量ById，取的下标默认为0
     *
     * @param varId     动态变量ID
     * @param envId     环境ID - 环境共享变量需要
     * @param projectId 项目id，防止SzxQL表达式中带的嵌合动态变量名字重复
     * @return 变量递归解析后的值
     */
    public String parseVarById(Long varId, Long envId, Long projectId) {
        DynamicVariable dynamicVar = Optional.of(varService.getById(varId))
                .map(DynamicVariable::parseSqlDetail)
                .orElseThrow(() -> new GlobalException(String.format("没有找到ID = %d 的动态变量", varId)));

        return parseVar(envId, 0, projectId, dynamicVar);
    }


    /**
     * 依据不同的变量类型调用不同的解析Handler
     *
     * @param envId      环境ID - 环境共享变量需要
     * @param takenIndex 使用的结果集下标,递归传递
     * @param projectId  项目id，防止SQL表达式中带的嵌合动态变量名字重复
     * @param dynamicVar {@link DynamicVariable}
     * @return 变量递归解析后的值
     */
    private String parseVar(Long envId, Integer takenIndex, Long projectId, DynamicVariable dynamicVar) {
        DynamicVarType varType = dynamicVar.getType();
        LogQueueRuntime.addNewLog(this.getClass(), SQL_ACTUATOR, "当前解析的动态变量是: " + JSON.toJSONString(dynamicVar));
        // 依据变量类型的不同调用不同的Handler
        if (varType == CONSTANT_VARIABLE) {
            return dynamicVar.getDetail();
        } else if (varType == SQL_VARIABLE) {
            return sqlVarHandler(envId, takenIndex, projectId, dynamicVar);
        } else if (varType == HTTP_VARIABLE) {
            return httpVarHandler(envId, projectId, dynamicVar);
        } else if (varType == CASE_VARIABLE) {
            return caseHandler(envId, projectId, dynamicVar);
        } else {
            throw new GlobalException("不支持的动态变量类型: " + varType);
        }
    }

    /**
     * 用例处理器
     *
     * @param envId      环境ID - 环境共享变量需要
     * @param projectId  项目id，防止SQL表达式中带的嵌合动态变量名字重复
     * @param dynamicVar {@link DynamicVariable}
     * @return 解析后的字符串
     */
    private String caseHandler(Long envId, Long projectId, DynamicVariable dynamicVar) {
        long caseDataId = Long.parseLong(dynamicVar.getDetail());
        TestCaseBTO caseBTO = dataService.toCaseBTO(caseDataId);
        HttpResponseDetail responseDetail = (HttpResponseDetail) caseActuator.executeTestCase(caseBTO, envId, projectId)
                .getBaseTestCaseRequestDetail();
        return responseDetail.getResponseBody();
    }

    /**
     * HTTP请求处理器
     *
     * @param envId      环境ID - 环境共享变量需要
     * @param projectId  项目id，防止SQL表达式中带的嵌合动态变量名字重复
     * @param dynamicVar {@link DynamicVariable}
     * @return 解析后的字符串
     */
    private String httpVarHandler(Long envId, Long projectId, DynamicVariable dynamicVar) {
        HttpRequestDetail httpRequestDetail = JSON.parseObject(dynamicVar.getDetail(), HttpRequestDetail.class);
        return httpActuator.sendHttpRequest(httpRequestDetail, envId, projectId).getResponseBody();
    }

    /**
     * SQL表达式处理器
     *
     * @param envId      环境ID - 环境共享变量需要
     * @param projectId  项目id，防止SQL表达式中带的嵌合动态变量名字重复
     * @param dynamicVar {@link DynamicVariable}
     * @return 解析后的字符串
     */
    private String sqlVarHandler(Long envId, Integer takenIndex, Long projectId, DynamicVariable dynamicVar) {
        // 首先替换掉#{id}这类的共享变量(如果有的话)
        String sqlExp = dynamicVar.getSqlExpDetail().getSqlExp();
        sqlExp = envActuator.replaceEnvVar(sqlExp, envId);

        String detail = dynamicVar.getDetail();
        List<SqlRegularObject> dynamicVarList = findDynamicVarList(detail);
        // 解析SQL表达式，判断是可以直接执行的SQL还是需要再递归解析动态变量
        if (!isEmpty(dynamicVarList)) {
            // 如果还存在动态变量,则继续递归解析
            for (SqlRegularObject sqlRegular : dynamicVarList) {
                DynamicVariable recVar = varService.getByName(sqlRegular.getVarName(), projectId)
                        .map(DynamicVariable::parseSqlDetail)
                        .orElseThrow(() -> new GlobalException(
                                String.format("没有找到项目id = %d 下,name = %s的变量", projectId, sqlRegular.getVarName())));
                String calculatedValue = parseVar(envId, sqlRegular.getIndex(), projectId, recVar);
                LogQueueRuntime.addNewLog(this.getClass(), SQL_ACTUATOR, String.format("正在进行SQL变量计算 变量名: %s 计算后的替换值: %s", sqlRegular.getVarName(), calculatedValue));
                sqlExp = sqlExp.replaceAll(String.format(REPLACE_VAR_EXP, sqlRegular.getVarName()), calculatedValue);
                dynamicVar.getSqlExpDetail().setSqlExp(sqlExp);
            }
        }
        // 执行SQL
        List<Map<String, Object>> resultList = runSql(dynamicVar, envId);
        //正则
        return takenResultString(resultList, dynamicVar.getTakenField(), Math.toIntExact(takenIndex));
    }

    /**
     * @param resultList 初始查询的结果集(表)
     * @param takenField 要取用的字段(列)
     * @param index      要取用的行数,从0开始算第一行
     * @return 取到的字段的结果
     * @throws GlobalException 如果找不到对应行列的数据(index与takenField),会抛出这个异常,建议把上层的SQL语句也返回到信息里
     */
    private String takenResultString(List<Map<String, Object>> resultList, String takenField, int index) throws GlobalException {
        Map<String, Object> resultMap = Optional.of(resultList.get(index))
                .orElseThrow(() -> new GlobalException(String.format("没有在 %s 中找到第 %d 行 的数据",
                        JSON.toJSON(resultList), index + 1)));

        return Optional.of(resultMap.get(takenField))
                .orElseThrow(() -> new GlobalException(String.format("%s 数据集中不存在属性名为 %s 的字段",
                        JSON.toJSON(resultMap), takenField))).toString();
    }


    /**
     * 执行一段SQL，该SQL表达式必须要是可以直接执行的语句
     *
     * @param dynamicVar 动态变量对象(必须是可以直接执行SQL 例`select * from user where id = 5`)
     * @param envId      环境ID
     * @return 运行SQL后取完数值的结果, 是一张数据表, 以LIST<MAP>的形式呈现
     */
    private List<Map<String, Object>> runSql(DynamicVariable dynamicVar, Long envId) {
        String sqlExp = dynamicVar.getSqlExpDetail().getSqlExp();
        Long connectId = dynamicVar.getSqlExpDetail().getPoolId();
        DataSourceDTO dataSourceDTO = Optional.of(connectService.getById(connectId))
                .orElseThrow(() -> new GlobalException(String.format("没有找到id = %d 的连接池connect对象", connectId)))
                .toDataSourceDTO();

        // 替换环境共享变量
        sqlExp = envActuator.replaceEnvVar(sqlExp, envId);

        // 校验dynamicVar里的detail是否是可以直接执行的SQL
        if (dynamicVar.getType() == SQL_VARIABLE && findDynamicVarList(sqlExp).size() == 0) {
            // 切换数据源,执行SQL,获取数值
            LogQueueRuntime.addNewLog(this.getClass(),SQL_ACTUATOR, String.format("当前执行的SQL语句: %s, 使用的数据源: %s", sqlExp, dataSourceDTO));
            Set<String> dataSources = dataSourceService.switchDataSource(dataSourceDTO);
            log.info("当前存在的数据源 {}", dataSources);
            List<Map<String, Object>> resultMap = jdbcTemplate.queryForList(sqlExp);
            dataSourceService.switchMainDataSource();
            return resultMap;
        } else {
            throw new GlobalException(String.format("动态变量的类型必须要是SQL类型且DETAIL内的SQL语句必须为元SQL语句,当前type : %s , 当前detail : %s",
                    dynamicVar.getType(), sqlExp));
        }

    }

    /**
     * 将含有动态变量的字符串语句中的形如{$id}动态变量语句给找到
     * 如果使用者写了诸如{$id}[1]的下标,则会使用下标值,否则使用默认值0
     *
     * @param dynamicString SQL字符串或者动态变量
     * @return SQL动态变量对象, 分别找出其中的变量名与对应的下标序列值(默认为0)
     */
    private List<SqlRegularObject> findDynamicVarList(String dynamicString) {
        Pattern pattern = Pattern.compile(DYNAMIC_VAR_EXP);
        Matcher matcher = pattern.matcher(dynamicString);
        List<SqlRegularObject> sqlRegObjects = new ArrayList<>();
        // 如果使用者写了诸如{$id}[1]的下标,则会使用下标值,否则使用默认值0
        while (matcher.find()) {
            if (matcher.groupCount() != 2) {
                throw new GlobalException("给定的动态变量/SQL表达式 不符合语法,给定的值是: " + dynamicString);
            }
            String varName = matcher.group(1);
            int index = StringUtils.isEmpty(matcher.group(2)) ? 0 : Integer.parseInt(matcher.group(2));
            sqlRegObjects.add(new SqlRegularObject(varName, index));
        }
        return sqlRegObjects;
    }
}

