package com.engine.salary.service.impl;

import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salaryformula.param.SalaryFormulaMockParam;
import com.engine.salary.entity.salaryformula.param.SalaryFormulaSaveParam;
import com.engine.salary.entity.salaryformula.po.FormulaPO;
import com.engine.salary.entity.salaryformula.po.FormulaVar;
import com.engine.salary.enums.salaryformula.ReferenceTypeEnum;
import com.engine.salary.enums.salaryformula.ReturnTypeEnum;
import com.engine.salary.enums.salaryformula.ValidateTypeEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.formlua.entity.standard.ExcelResult;
import com.engine.salary.mapper.formula.FormulaMapper;
import com.engine.salary.mapper.formula.FormulaVarMapper;
import com.engine.salary.service.FormulaRunService;
import com.engine.salary.service.RemoteExcelService;
import com.engine.salary.service.SalaryFormulaService;
import com.engine.salary.util.JsonUtil;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.valid.ValidUtil;
import com.google.common.collect.Lists;
import com.engine.salary.util.db.IdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeanUtils;
import weaver.hrm.User;

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

/**
 * 薪酬管理公式编辑器
 * <p>Copyright: Copyright (c) 2022</p>
 * <p>Company: 泛微软件</p>
 *
 * @author qiantao
 * @version 1.0
 **/
@Slf4j
public class SalaryFormulaServiceImpl extends Service implements SalaryFormulaService {

    private FormulaMapper getFormulaMapper() {
        return MapperProxyFactory.getProxy(FormulaMapper.class);
    }

    private FormulaVarMapper getFormulaVarMapper() {
        return MapperProxyFactory.getProxy(FormulaVarMapper.class);
    }

    private FormulaRunService getFormulaRunService(User user) {
        return ServiceUtil.getService(FormulaRunServiceImpl.class, user);
    }

    private RemoteExcelService getRemoteExcelService(User user) {
        return ServiceUtil.getService(RemoteExcelServiceImpl.class, user);
    }

    @Override
    public List<ExpressFormula> listExpressFormula(Collection<Long> formulaIds) {
        formulaIds = formulaIds.stream().filter(id -> id != null && id > 0).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(formulaIds)) {
            return Collections.emptyList();
        }
        try {
            // 当前租户自己新建的公式
            List<FormulaPO> expressFormulas = getFormulaMapper().listByIds(Lists.newArrayList(formulaIds));

            return expressFormulas.stream().filter(Objects::nonNull).map(m -> {
                ExpressFormula expressFormula = new ExpressFormula();
                BeanUtils.copyProperties(m, expressFormula);
                List<FormulaVar> formulaVarPOS = getFormulaVarMapper().listSome(FormulaVar.builder().formulaId(m.getId()).build());
                expressFormula.setParameters(formulaVarPOS);
                return expressFormula;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            log.info("获取公示详情失败", e);
            throw new SalaryRunTimeException("获取公示详情失败");
        }
    }

    @Override
    public ExpressFormula getExpressFormula(Long formulaId) {
        if (formulaId == null || formulaId <= 0) {
            return null;
        }
        try {
            // 当前租户自己新建的公式
            FormulaPO formulaPO = getFormulaMapper().getById(formulaId);
            ExpressFormula expressFormula = new ExpressFormula();
            BeanUtils.copyProperties(formulaPO, expressFormula);
            List<FormulaVar> formulaVarPOS = getFormulaVarMapper().listSome(FormulaVar.builder().formulaId(formulaId).build());
            expressFormula.setParameters(formulaVarPOS);

            return expressFormula;
        } catch (Exception e) {
            log.info("获取公示详情失败", e);
            throw new SalaryRunTimeException("获取公示详情失败");
        }
    }

    @Override
    public FormulaPO save(SalaryFormulaSaveParam param) {
        ValidUtil.doValidator(param);
        if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == null) {
            throw new SalaryRunTimeException("引用类型异常");
        }
        if (ReturnTypeEnum.parseByValue(param.getReturnType()) == null) {
            throw new SalaryRunTimeException("返回类型异常");
        }
        if (ValidateTypeEnum.parseByValue(param.getValidateType()) == null) {
            throw new SalaryRunTimeException("校验类型异常");
        }

        //将select因XSS过滤造成的异常字符转换回来
        ValidUtil.modify(param);

        // 解析公式中的参数
        if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
            List<FormulaVar> parameterList = parseFormulaParameters(param.getFormula(), ReferenceTypeEnum.FORMULA);
            param.setParameters(parameterList);
        } else if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL) {
            List<FormulaVar> parameterList = parseFormulaParameters(param.getFormula(), ReferenceTypeEnum.SQL);
            param.setParameters(parameterList);
        }
        int orderIndex = 0;
        for (FormulaVar parameter : param.getParameters()) {
            parameter.setOrderIndex(orderIndex++);
        }

        List<FormulaVar> parameters = param.getParameters();
        //防止参数名和字段名呈现一对多的问题
        if (CollectionUtils.isNotEmpty(parameters)) {
            List<String> notRepeatingNameSize = parameters.stream().map(FormulaVar::getFieldName).distinct().collect(Collectors.toList());
            if (notRepeatingNameSize.size() < parameters.size()) {
                throw new SalaryRunTimeException("公式参数配置异常！参数名称重复，请清空公式内容后，再重新打开编辑");
            }
        }

        String extendParam = param.getExtendParam();
        if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL) {
            if (extendParam == null) {
                throw new SalaryRunTimeException("未设置SQL返回值");
            }

            String sqlReturnKey = "";
            try {
                Map<String, String> map = JsonUtil.parseMap(extendParam, String.class);
                sqlReturnKey = map.getOrDefault("sqlReturnKey", "");
            } catch (Exception e) {
                log.error("express execute fail, sql extendParam parse fail", e);
            }

            if (StringUtils.isBlank(sqlReturnKey)) {
                throw new SalaryRunTimeException("未设置SQL返回值");
            }
        }

        //试运行公式
        checkRun(param);

        FormulaPO formulaPO = new FormulaPO();
        String formula = param.getFormula();
        long formulaId = IdGenerator.generate();
        formulaPO.setId(formulaId);
        formulaPO.setName(param.getName());
        formulaPO.setDescription(param.getDescription());
        formulaPO.setModule(param.getModule());
        formulaPO.setUseFor(param.getUseFor());
        formulaPO.setReferenceType(param.getReferenceType());
        formulaPO.setReturnType(param.getReturnType());
        formulaPO.setValidateType(param.getValidateType());
        formulaPO.setExtendParam(extendParam);
        formulaPO.setFormula(formula);
        formulaPO.setDeleteType(NumberUtils.INTEGER_ZERO);

        Date now = new Date();
        formulaPO.setCreateTime(now);
        formulaPO.setUpdateTime(now);
        formulaPO.setCreator((long) user.getUID());

        /*
         公式内容以如下显示方式存储
         {薪资项目.输入项1}+{薪资项目.输入项2}
         转换为实际的运行脚本
         */
        String formulaRunScript = formula;
        for (FormulaVar po : parameters) {
            formulaRunScript = formulaRunScript.replace(po.getFieldName(), po.getFieldId());
        }
        formulaPO.setFormulaRunScript(formulaRunScript);

        getFormulaMapper().insertIgnoreNull(formulaPO);

        for (FormulaVar po : parameters) {
            po.setId(IdGenerator.generate());
            po.setFormulaId(formulaId);
            po.setDeleteType(NumberUtils.INTEGER_ZERO);
            po.setCreator((long) user.getUID());
            po.setCreateTime(now);
            po.setUpdateTime(now);
            getFormulaVarMapper().insertIgnoreNull(po);
        }
        return formulaPO;
    }

    public List<FormulaVar> parseFormulaParameters(String formula, ReferenceTypeEnum referenceTypeEnum) {
        List<FormulaVar> parameters = new ArrayList<>();
        // 获取公式中所有可选的变量项目
        Map<String, List<FormulaVar>> allParameters = getRemoteExcelService(user).allFieldList(referenceTypeEnum);
        List<String> groups = new ArrayList<>(allParameters.keySet());
        StringBuilder reg = new StringBuilder("(");
        for (int i = 0; i < groups.size(); i++) {
            reg.append("\\{" + groups.get(i) + "\\.");
            if (i + 1 != groups.size()) {
                reg.append("|");
            }
        }
        reg.append(").*?}");
        // 提取公式中所有的变量
        Pattern pattern = Pattern.compile(reg.toString());
        Matcher matcher = pattern.matcher(formula);
        List<String> vars = new ArrayList<>();
        while (matcher.find()) {
            String var = matcher.group(0).replaceAll("\\{", "").replaceAll("}", "");
            if (StringUtils.isNotBlank(var)) {
                vars.add(var);
            }
        }
        // 去重
        vars = vars.stream().distinct().collect(Collectors.toList());
        for (String var : vars) {
            String[] split = var.split("\\.");
            if (split.length == 2) {
                List<FormulaVar> formulaVars = allParameters.get(split[0]);
                if (formulaVars == null) {
                    throw new SalaryRunTimeException("保存失败，公式变量" + split[0] + "输入有误！");
                }
                Optional<FormulaVar> field = formulaVars.stream().filter(v -> Objects.equals(v.getName(), split[1])).findFirst();
                if (field.isPresent()) {
                    FormulaVar formulaVar = field.get();
                    String fieldName = "{" + split[0] + "." + split[1] + "}";
                    formulaVar.setFieldName(fieldName);
                    parameters.add(formulaVar);
                } else {
                    throw new SalaryRunTimeException("保存失败，公式变量" + split[0] + split[1] + "输入有误！");
                }
            } else if (split.length > 2) {
                // 变量名称中包含.
                List<FormulaVar> formulaVars = allParameters.get(split[0]);
                if (formulaVars == null) {
                    throw new SalaryRunTimeException("保存失败，公式变量" + split[0] + "输入有误！");
                }
                StringBuilder field = new StringBuilder("");
                for (int i = 1; i < split.length; i++) {
                    if (i != 1) {
                        field.append(".");
                    }
                    field = field.append(split[i]);
                }
                String finalField = field.toString();
                Optional<FormulaVar> optional = formulaVars.stream().filter(v -> Objects.equals(v.getName(), finalField)).findFirst();
                if (optional.isPresent()) {
                    FormulaVar formulaVar = optional.get();
                    String fieldName = "{" + split[0] + "." + field + "}";
                    formulaVar.setFieldName(fieldName);
                    parameters.add(optional.get());
                } else {
                    throw new SalaryRunTimeException("保存失败，公式变量" + split[0] + split[1] + "输入有误！");
                }
            }
        }
        return parameters;
    }

    /**
     * 模拟运行公式
     *
     * @param param
     */
    private void checkRun(SalaryFormulaSaveParam param) {
        //返回类型
        String returnType = param.getReturnType();
        ReturnTypeEnum returnTypeEnum = ReturnTypeEnum.parseByValue(returnType);

        /*
         公式内容以如下显示方式存储
         {薪资项目.输入项1}+{薪资项目.输入项2}
         转换为实际的运行脚本
         */
        String formulaRunScript = param.getFormula();
//
//        List<FormulaVar> parameters = param.getParameters();
//        for (int i = 0; i < parameters.size(); i++) {
//            FormulaVar po = parameters.get(i);
//            formulaRunScript = formulaRunScript.replace(po.getFieldName(), po.getFieldId());
//            po.setContent(String.valueOf(i + 1));
//        }
//        //验证公式是否可运行
//        if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
//            ExpressFormula test = ExpressFormula.builder().formulaRunScript(formulaRunScript).extendParam(param.getExtendParam()).referenceType(param.getReferenceType()).build();
//            ExcelResult run = getFormulaRunService(user).run(test, parameters, DataCollectionEmployee.builder().employeeId((long) user.getUID()).build());
//
//            if (run.isStatus()) {
//                //返回结果不是数字
//                if (returnTypeEnum == ReturnTypeEnum.NUMBER && !NumberUtils.isCreatable(String.valueOf(run))) {
//                    throw new SalaryRunTimeException("返回结果不是数值");
//                }
//            }else {
//                throw new SalaryRunTimeException("公式配置异常");
//            }
//        }

        if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL) {
            if (formulaRunScript.contains(";") || formulaRunScript.contains("--")) {
                throw new SalaryRunTimeException("SQL配置异常,请去除';'或者'--'");
            }
        }
    }


    @Override
    public Object mock(SalaryFormulaMockParam param) {

        ValidUtil.doValidator(param);

        FormulaPO po = getFormulaMapper().getById(param.getId());

        //验证公式是否可运行
        ExpressFormula test = ExpressFormula.builder().formulaRunScript(po.getFormulaRunScript()).extendParam(po.getExtendParam()).referenceType(po.getReferenceType()).build();
        ExcelResult run = getFormulaRunService(user).run(test, param.getParameters(), DataCollectionEmployee.builder().employeeId((long) user.getUID()).build());
        if (run.isStatus()) {
            return run.getData();
        } else {
            return run.getErrorMsg();
        }
    }

    @Override
    public void initFunction() {

    }

    @Override
    public void deleteNotIn(List<Long> formulaIds) {
        Date today = new Date();
        Calendar c = Calendar.getInstance();
        c.setTime(today);
        c.add(Calendar.DAY_OF_MONTH, -1);
        Date yesterday = c.getTime();
        if (CollectionUtils.isNotEmpty(formulaIds)) {
            List<FormulaPO> allFormula = getFormulaMapper().listAll();
            //待删除的公式
            List<Long> needDeleteIds = allFormula.stream().map(FormulaPO::getId).filter(id -> !formulaIds.contains(id)).collect(Collectors.toList());
            List<List<Long>> partition = Lists.partition(needDeleteIds, 1000);
            partition.forEach(list -> {
                getFormulaMapper().deleteIn(list, yesterday);
                getFormulaVarMapper().deleteInFormulaIds(list, yesterday);
            });
        }
    }

    @Override
    public List<FormulaVar> listVarByFormulaIds(List<Long> effectiveFormulaIds) {
        List<FormulaVar> vars = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(effectiveFormulaIds)) {
            List<List<Long>> partition = Lists.partition(effectiveFormulaIds, 999);
            partition.forEach(list -> {
                vars.addAll(getFormulaVarMapper().listSome(FormulaVar.builder().formulaIds(list).build()));
            });
        }
        return vars;
    }

    @Override
    public List<FormulaVar> listByCode(String code) {
        return getFormulaVarMapper().listSome(FormulaVar.builder().fieldId(code).build());
    }

    @Override
    public void updateVar(FormulaVar formulaVar) {
        getFormulaVarMapper().updateIgnoreNull(formulaVar);
    }

    @Override
    public List<FormulaPO> listByIds(List<Long> formulaIds) {
        if (CollectionUtils.isEmpty(formulaIds)) {
            return new ArrayList<>();
        }
        return getFormulaMapper().listByIds(formulaIds);
    }

    @Override
    public void update(FormulaPO formulaPO) {
        getFormulaMapper().updateIgnoreNull(formulaPO);
    }

}
