package com.engine.salary.entity.salaryacct.bo;

import com.engine.salary.constant.SalaryFormulaFieldConstant;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salaryformula.po.FormulaVar;
import com.engine.salary.entity.salaryitem.po.SalaryItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobBackItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobItemPO;
import com.engine.salary.enums.salaryformula.SalaryFormulaReferenceEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

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
 **/
public class SalaryAcctCalculatePriorityBO {

    /**
     * 公式中变量的fieldId的正则表达式
     */
    private static final String SALARY_REGEX = "(\\w+)" + SalaryFormulaFieldConstant.FIELD_ID_SEPARATOR + "(\\w+)";

    /**
     * 解析公式中变量的fieldId的正则表达式
     */
    private static final Pattern SALARY_PATTERN = Pattern.compile(SALARY_REGEX);

    /**
     * 计算优先级
     *
     * @param salarySobItems
     * @param salaryItems
     * @param expressFormulas
     * @return 根据计算优先级已经排好序，集合中是薪资账套中的薪资项目
     */
    public static List<List<Long>> calculatePriority(List<SalarySobItemPO> salarySobItems,
                                                     List<SalaryItemPO> salaryItems,
                                                     List<ExpressFormula> expressFormulas,
                                                     List<SalarySobBackItemPO> salarySobBackItems,
                                                     Set<String> issuedFieldIds) {
        // 公式详情
        Map<Long, List<FormulaVar>> formulaIdKeyMap = ExpressFormulaBO.buildFormulaVar(expressFormulas);
        // key：薪资项目的id，value：薪资项目的po
        Map<Long, SalaryItemPO> salaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getId);
        // key：薪资项目的code，value：薪资项目的po
        Map<String, SalaryItemPO> codeKeySalaryItemPOMap = SalaryEntityUtil.convert2Map(salaryItems, SalaryItemPO::getCode);
        // key：薪资项目的id，value：薪资账套下的薪资项目副本的po
        Map<Long, SalarySobItemPO> salaryItemIdKeySalarySobItemPOMap = SalaryEntityUtil.convert2Map(salarySobItems, SalarySobItemPO::getSalaryItemId);
        // 薪资账套项目+薪资回算项目
        Set<Long> salarySobItemsAndBackItems = SalaryEntityUtil.properties(salarySobItems, SalarySobItemPO::getSalaryItemId);
        salarySobItemsAndBackItems.addAll(SalaryEntityUtil.properties(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId));
        Map<Long, SalaryItemIdWithPriority> salaryItemIdWithPriorityMap = Maps.newHashMapWithExpectedSize(salarySobItemsAndBackItems.size());
        // key：薪资回算项目id，value：薪资回算项目副本PO
        Map<Long, SalarySobBackItemPO> salarySobBackItemPOMap = SalaryEntityUtil.convert2Map(salarySobBackItems, SalarySobBackItemPO::getSalaryItemId);
        for (Long salaryItemId : salarySobItemsAndBackItems) {
            calculate(salaryItemId, salaryItemPOMap, codeKeySalaryItemPOMap, salaryItemIdKeySalarySobItemPOMap, formulaIdKeyMap, salaryItemIdWithPriorityMap, null, salarySobBackItemPOMap, issuedFieldIds);
        }
        return SalaryEntityUtil.group2Map(salaryItemIdWithPriorityMap.values(), SalaryItemIdWithPriority::getPriority).values().stream()
                .sorted(Comparator.comparingInt(list -> list.get(0).getPriority()))
                .map(list -> SalaryEntityUtil.properties(list, SalaryItemIdWithPriority::getSalaryItemId, Collectors.toList()))
                .collect(Collectors.toList());
    }


    /**
     * 计算薪资账套中的薪资项目的计算优先级
     *
     * @param currentSalaryItemId
     * @param salaryItemPOMap
     * @param salaryItemIdKeySalarySobItemPOMap
     * @param codeKeySalaryItemPOMap
     * @param formulaIdKeyMap
     * @param salaryItemIdWithPriorityMap
     * @param pre
     */
    private static void calculate(Long currentSalaryItemId,
                                  Map<Long, SalaryItemPO> salaryItemPOMap,
                                  Map<String, SalaryItemPO> codeKeySalaryItemPOMap,
                                  Map<Long, SalarySobItemPO> salaryItemIdKeySalarySobItemPOMap,
                                  Map<Long, List<FormulaVar>> formulaIdKeyMap,
                                  Map<Long, SalaryItemIdWithPriority> salaryItemIdWithPriorityMap,
                                  SalaryItemIdWithPriority pre,
                                  Map<Long, SalarySobBackItemPO> salarySobBackItemPOMap,
                                  Set<String> issuedFieldIds) {
        List<Long> salaryItemIds = Lists.newArrayList();
        // 获取公式详情
        List<FormulaVar> formulaVars;
        if (salaryItemIdKeySalarySobItemPOMap.containsKey(currentSalaryItemId)) {
            // 如果薪资项目在薪资账套中有副本，则取薪资账套中设置的公式
            SalarySobItemPO salarySobItemPO = salaryItemIdKeySalarySobItemPOMap.get(currentSalaryItemId);
            formulaVars = formulaIdKeyMap.getOrDefault(salarySobItemPO.getFormulaId(), Collections.emptyList());
        } else if(salarySobBackItemPOMap.containsKey(currentSalaryItemId)){
            // 如果薪资项目在薪资账套中没有副本，则取薪资回算中设置的公式
            SalarySobBackItemPO salarySobBackItemPO = salarySobBackItemPOMap.get(currentSalaryItemId);
            formulaVars = formulaIdKeyMap.getOrDefault(salarySobBackItemPO.getFormulaId(), Collections.emptyList());
        } else {
            // 如果薪资项目在薪资账套及回算薪资项目中没有有副本，则取薪资项目中设置的公式
            SalaryItemPO salaryItemPO = salaryItemPOMap.get(currentSalaryItemId);
            formulaVars = formulaIdKeyMap.getOrDefault(salaryItemPO.getFormulaId(), Collections.emptyList());
        }
        // 解析公式详情中的变量，找出引用了哪些其他的薪资项目（需要先计算出引用的薪资项目才能计算当前的薪资项目）
        for (FormulaVar formulaVar : formulaVars) {
            String fieldId = formulaVar.getFieldId();
            if (StringUtils.isEmpty(fieldId)) {
                continue;
            }
            Matcher matcher = SALARY_PATTERN.matcher(fieldId);
            if (matcher.find()) {
                SalaryFormulaReferenceEnum referenceEnum = SalaryFormulaReferenceEnum.parseByValue(matcher.group(1));
                // 分析公式中的回算变量包含哪些
                loadSalaryCalcFormula(referenceEnum, issuedFieldIds, matcher.group(2));
                if (referenceEnum == SalaryFormulaReferenceEnum.SALARY_ITEM) {
                    SalaryItemPO salaryItemPO = codeKeySalaryItemPOMap.get(matcher.group(2));
                    if (salaryItemPO == null) {
                        continue;
                    }
                    salaryItemIds.add(salaryItemPO.getId());
                }
            }
        }
        if (CollectionUtils.isEmpty(salaryItemIds)) {
            SalaryItemIdWithPriority current = salaryItemIdWithPriorityMap.computeIfAbsent(currentSalaryItemId, k -> SalaryItemIdWithPriority.builder()
                    .priority(0)
                    .salaryItemId(currentSalaryItemId)
                    .preList(Collections.emptyList())
                    .build());
            addPre(current, pre, salaryItemPOMap);
            updatePriority(current);
            return;
        }
        for (Long salaryItemId : salaryItemIds) {
            SalaryItemIdWithPriority current = salaryItemIdWithPriorityMap.computeIfAbsent(currentSalaryItemId, k -> SalaryItemIdWithPriority.builder()
                    .priority(1)
                    .salaryItemId(currentSalaryItemId)
                    .preList(Collections.emptyList())
                    .build());
            addPre(current, pre, salaryItemPOMap);
            updatePriority(current);
            calculate(salaryItemId, salaryItemPOMap, codeKeySalaryItemPOMap, salaryItemIdKeySalarySobItemPOMap, formulaIdKeyMap, salaryItemIdWithPriorityMap, current, Collections.emptyMap(), issuedFieldIds);
        }
    }

    private static void loadSalaryCalcFormula(SalaryFormulaReferenceEnum referenceEnum, Set<String> issuedFieldIds, String fieldId) {
       if (referenceEnum == SalaryFormulaReferenceEnum.ISSUED) {
           issuedFieldIds.add(fieldId);
        }
    }

    /**
     * 薪资项目被哪些薪资项目引用
     *
     * @param current
     * @param pre
     */
    private static void addPre(SalaryItemIdWithPriority current, SalaryItemIdWithPriority pre, Map<Long, SalaryItemPO> salaryItemPOMap) {
        if (pre == null) {
            return;
        }
        checkLoop(current, pre, salaryItemPOMap);
        if (CollectionUtils.isEmpty(current.getPreList())) {
            current.setPreList(Lists.newArrayList(pre));
        } else {
            boolean isExist = current.getPreList().stream().anyMatch(e -> Objects.equals(e.getSalaryItemId(), pre.getSalaryItemId()));
            if (!isExist) {
                current.getPreList().add(pre);
            }
        }
    }

    /**
     * 检查薪资项目之间是否存在相互引用
     *
     * @param current
     */
    private static void checkLoop(SalaryItemIdWithPriority current, SalaryItemIdWithPriority pre, Map<Long, SalaryItemPO> salaryItemPOMap) {
        if (Objects.equals(pre.getSalaryItemId(), current.getSalaryItemId())) {
            SalaryItemPO preSalaryItemPO = salaryItemPOMap.get(pre.getSalaryItemId());
            SalaryItemPO currentSalaryItemPO = salaryItemPOMap.get(current.getSalaryItemId());
            String errMsg = SalaryI18nUtil.getI18nLabel(101426, "{0}和{1}的公式中存在相互引用")
                    .replace("{0}", Optional.ofNullable(preSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY))
                    .replace("{1}", Optional.ofNullable(currentSalaryItemPO).map(SalaryItemPO::getName).orElse(StringUtils.EMPTY));
            throw new SalaryRunTimeException(errMsg);
        }
        if (CollectionUtils.isEmpty(pre.getPreList())) {
            return;
        }
        for (SalaryItemIdWithPriority salaryItemIdWithPriority : pre.getPreList()) {
            checkLoop(current, salaryItemIdWithPriority, salaryItemPOMap);
        }
    }

    /**
     * 更新薪资账套中的薪资项目的计算优先级
     *
     * @param current
     */
    private static void updatePriority(SalaryItemIdWithPriority current) {
        List<SalaryItemIdWithPriority> preList = current.getPreList();
        if (CollectionUtils.isEmpty(preList)) {
            return;
        }
        preList.stream()
                .filter(e -> e.getPriority() <= current.getPriority())
                .forEach(e -> e.setPriority(current.getPriority() + 1));
    }


    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class SalaryItemIdWithPriority {

        /**
         * 薪资账套中的薪资项目的计算优先级（数字越小，计算优先级越高，从0开始计算）
         */
        private Integer priority;

        /**
         * 薪资项目的id
         */
        private Long salaryItemId;

        /**
         * 当前层级中的上层
         */
        private List<SalaryItemIdWithPriority> preList;
    }
}
