package com.engine.salary.service.impl;

import com.engine.core.impl.Service;
import com.engine.salary.encrypt.AESEncryptUtil;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salaryformula.po.FormulaVar;
import com.engine.salary.enums.salaryformula.ReferenceTypeEnum;
import com.engine.salary.formlua.core.QlExpress;
import com.engine.salary.formlua.entity.parameter.DataType;
import com.engine.salary.formlua.entity.standard.ExcelResult;
import com.engine.salary.service.FormulaRunService;
import com.engine.salary.sys.enums.OpenEnum;
import com.engine.salary.util.JsonUtil;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.util.StopWatch;
import weaver.conn.RecordSet;
import weaver.conn.RecordSetDataSource;
import weaver.general.BaseBean;
import weaver.general.Util;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class FormulaRunServiceImpl extends Service implements FormulaRunService {

    private static ExpressRunner runner = new ExpressRunner(true, false);

    private final BaseBean baseBean = new BaseBean();

    private final Boolean isLog = "true".equals(baseBean.getPropValue("hrmSalary", "log"));
    //超时提醒
    private final String formulaRunOvertimeThreshold = baseBean.getPropValue("hrmSalaryCustom", "formulaRunOvertimeThreshold");

    QlExpress express = new QlExpress();


    @Override
    public ExcelResult run(ExpressFormula expressFormula, List<FormulaVar> formulaVars, DataCollectionEmployee simpleEmployee) {
        ExcelResult result = new ExcelResult(false, "", "公式类型异常！");
        if (StringUtils.isNotBlank(formulaRunOvertimeThreshold)) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("run " + expressFormula.getName());
            if (ReferenceTypeEnum.parseByValue(expressFormula.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
                result = runFormula(expressFormula, formulaVars);
            }
            if (ReferenceTypeEnum.parseByValue(expressFormula.getReferenceType()) == ReferenceTypeEnum.SQL) {
                result = runSQL(expressFormula, formulaVars);
            }
            stopWatch.stop();
            long totalTimeMillis = stopWatch.getTotalTimeMillis();
            result.setRunTime(totalTimeMillis);
        } else {
            if (ReferenceTypeEnum.parseByValue(expressFormula.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
                result = runFormula(expressFormula, formulaVars);
            }
            if (ReferenceTypeEnum.parseByValue(expressFormula.getReferenceType()) == ReferenceTypeEnum.SQL) {
                result = runSQL(expressFormula, formulaVars);
            }
        }
        return result;
    }

    private ExcelResult runSQL(ExpressFormula expressFormula, List<FormulaVar> formulaVars) {
        if (isLog) {
            log.info("SQL ExpressFormula {}", expressFormula);
        }
        ExcelResult excelResult = new ExcelResult();

        //解析配置，获取返回值、数据源
        String extendParam = expressFormula.getExtendParam();
        String sqlReturnKey = "";
        String datasourceId = "";
        String openDecrypt = "";
        String result = "";
        try {
            Map<String, String> map = JsonUtil.parseMap(extendParam, String.class);
            //返回值配置
            sqlReturnKey = map.getOrDefault("sqlReturnKey", "");
            if (StringUtils.isNotBlank(sqlReturnKey)) {
                sqlReturnKey = sqlReturnKey.trim();
            }
            //数据源配置
            String datasourceJson = map.getOrDefault("datasource", "");
            if (StringUtils.isNotBlank(datasourceJson)) {
                Map<String, String> datasourceIdMap = JsonUtil.parseMap(datasourceJson, String.class);
                String datasourceIdNode = datasourceIdMap.getOrDefault("datasourceId", "");
                if (StringUtils.isNotBlank(datasourceIdNode)) {
                    datasourceId = datasourceIdNode;
                }
            }
            //是否需要解密
            String decrypt = map.get("openDecrypt");
            if (StringUtils.isNotBlank(decrypt)) {
                openDecrypt = decrypt.trim();
            }

            //解析sql
            String sql = expressFormula.getFormulaRunScript();
            for (FormulaVar formulaVar : formulaVars) {
                sql = sql.replaceAll(formulaVar.getFieldId(), "'" + formulaVar.getContent() + "'");
            }
            if (isLog) {
                log.info("ExpressFormula sql run {}", sql);
            }

            excelResult.setExpress(sql);

            //外部数据源
            if (StringUtils.isNotBlank(datasourceId)) {
                RecordSetDataSource rs = new RecordSetDataSource(datasourceId);
                log.error("执行外部sql,{},datasourceId{}", sql, datasourceId);
                if (rs.executeSql(sql)) {
                    if (rs.next()) {
                        result = rs.getString(sqlReturnKey);
                    }
                }
            } else {
                RecordSet rs = new RecordSet();
                if (rs.execute(sql)) {
                    if (rs.next()) {
                        result = rs.getString(sqlReturnKey);
                    }
                }
            }

            if (OpenEnum.OPEN.getValue().equals(openDecrypt)) {
                result = AESEncryptUtil.decrypt(result);
            }

            excelResult.setStatus(true);
            excelResult.setData(Util.null2String(result));
            return excelResult;

        } catch (Throwable e) {
            log.error("express run err sql", e);
            while (e.getCause() != null) {
                e = e.getCause();
            }
            excelResult.setStatus(false);
            excelResult.setErrorMsg(e.getMessage());
            return excelResult;
        }
    }

    private ExcelResult runFormula(ExpressFormula expressFormula, List<FormulaVar> formulaVars) {

        if (isLog) {
            log.info("FORMULA ExpressFormula {} {}", expressFormula.getFormula(), expressFormula.getFormulaRunScript());
        }

        //是否是自定义函数
        boolean isCustomFunction = true;

        String extendParam = expressFormula.getExtendParam();
        try {
            Map<String, String> map = JsonUtil.parseMap(extendParam, String.class);
            //返回值配置
            String isCustomFunctionNode = map.getOrDefault("isCustomFunction", "");
            if (StringUtils.isNotBlank(isCustomFunctionNode)) {
                isCustomFunction = StringUtils.equals(isCustomFunctionNode.trim(), "1");
            }
        } catch (Exception e) {
            log.error("express execute fail, extendParam parse fail {}", extendParam, e);
        }

        String formula = expressFormula.getFormulaRunScript();

        ExcelResult execute = new ExcelResult();
        if (isCustomFunction) {
            try {
                execute.setExpress(formula);
                DefaultContext<String, Object> context = new DefaultContext<String, Object>();
                formulaVars.forEach(v -> {
                    if (isLog) {
                        log.info("FORMULA formulaVar {} - {}", v.getFieldId(), v.getContent());
                    }
                    if (DataType.NUMBER.equals(v.getFieldType()) && NumberUtils.isCreatable(v.getContent())) {
                        context.put(v.getFieldId(), new BigDecimal(v.getContent()));
                    } else {
                        context.put(v.getFieldId(), v.getContent());
                    }
                });
                execute.setExpressContext(context);
                Object data = runner.execute(formula, context, null, true, false);

                if (data != null) {
                    execute.setData(data);
                    execute.setStatus(true);
                } else {
                    execute.setStatus(false);
                }

            } catch (Throwable e) {
                log.error("express run err isCustomFunction", e);
                while (e.getCause() != null) {
                    e = e.getCause();
                }
                execute.setStatus(false);
                execute.setErrorMsg(e.getMessage());
            }

            return execute;
        } else {
            Map<String, Object> context = new HashMap<>();
            formulaVars.forEach(v -> {
                if (isLog) {
                    log.info("FORMULA formulaVar {} - {}", v.getFieldId(), v.getContent());
                }
                if (DataType.NUMBER.equals(v.getFieldType()) && NumberUtils.isCreatable(v.getContent())) {
                    context.put(v.getFieldId(), new BigDecimal(v.getContent()));
                } else if (v.getContent().startsWith("[") && v.getContent().endsWith("]")) {
                    String content = v.getContent().substring(1);
                    content = content.substring(0, content.length() - 1);
                    context.put(v.getFieldId(), content.split(","));
                } else {
                    context.put(v.getFieldId(), v.getContent());
                }
            });
            execute = express.execute(formula, context);
            return execute;
        }
    }
}
