package com.prolog.cs.biz.outbound.stage;

import cn.hutool.json.JSONUtil;
import com.prolog.cs.biz.outbound.model.entity.WaveBillDO;
import com.prolog.framework.core.exception.BizException;
import com.prolog.framework.utils.StringUtils;
import com.prolog.upcloud.base.strategy.domain.algorithm.Algorithm;
import com.prolog.upcloud.base.strategy.domain.core.StrategyContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class WorkStageAlgorithm extends Algorithm {

    private static Logger logger = LoggerFactory.getLogger(WorkStageAlgorithm.class);

    /**
     * 算法要求：
     * 1 尽量少的存区
     * 2 不能超过限制
     * 3 暂存取集中
     * @param availableStages 可选的暂存区
     * @return List<WorkStage> 被选用的暂存区
     */
    public static List<WorkStage> compute(List<WorkStage> availableStages, BigDecimal caseQty, List<WaveBillDO> waveBillDOS) {
        // 可用集货区排序
        List<WorkStage> sortedByCode = availableStages.stream().sorted(Comparator.comparing(WorkStage::getCode)).collect(Collectors.toList());
        String stageType = availableStages.get(0).getInfo().getStageType();
        BigDecimal totalVolume = waveBillDOS.stream().filter(t->t.getTotalVolume()!=null).map(WaveBillDO::getTotalVolume).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
        String failReason = "";
        logger.info("集货区比较信息:{}", JSONUtil.toJsonStr(sortedByCode));
        for (int i = 0; i < sortedByCode.size(); i++) {
            WorkStage workStage = sortedByCode.get(i);
            // 数量
            String stageCode = workStage.getInfo().getStageCode();
            if (workStage.getRemain().compareTo(caseQty)<0) {
                failReason = "集货区["+ stageCode +"]剩余箱数不够：订单["+caseQty+"],集货区["+workStage.getRemain()+"]";
                continue;
            }
            // 体积
            if (workStage.getRemainVol().compareTo(totalVolume)<0) {
                failReason = "集货区["+ stageCode +"剩余体积不够：订单["+totalVolume+"],集货区["+workStage.getRemainVol()+"]";
                continue;
            }
            // 合单数量
            if(workStage.getRemainNumOfBills().compareTo(BigDecimal.ONE)<0){
                failReason = "集货区["+ stageCode +"]剩余订单数不够：集货区["+workStage.getRemainNumOfBills()+"]";
                continue;
            }
            // 客户数量
            if (workStage.getBills()!=null && workStage.getBills().size()>0) {
                List<String> shipperIdList = workStage.getBills().stream().map(t -> t.getShipperId()).collect(Collectors.toList());
                if (!shipperIdList.contains(waveBillDOS.get(0).getShipperId())) {
                    if (workStage.getInfo().getMaxClientNum().compareTo(workStage.getNumOfClients())==0) {
                        failReason = "集货区["+ stageCode +"]剩余客户数不够：集货区["+workStage.getRemain()+"]";
                        continue;
                    }
                }
            }
            workStage.setResult(caseQty);
            return sortedByCode.subList(i,i+1);
        }
        if ("大".equals(stageType) && sortedByCode.size()>1) {
            for (int i = 1; i <= sortedByCode.size(); i++) {
                Optional<List<WorkStage>> workStages = workStageAlgorithm(sortedByCode, i, caseQty, waveBillDOS,true);
                if (workStages.isPresent()) {
                    return workStages.get();
                }
            }
            for (int i = 1; i <= sortedByCode.size(); i++) {
                Optional<List<WorkStage>> workStages = workStageAlgorithm(sortedByCode, i, caseQty, waveBillDOS,false);
                if (workStages.isPresent()) {
                    return workStages.get();
                }
            }
        }
        String message = StringUtils.isBlank(failReason) ? "订单体积、客户数、件数找不到集货区" : failReason;
        logger.error(message);
        throw new BizException(message);
    }

    private static Optional<List<WorkStage>> workStageAlgorithm(List<WorkStage> stages,int numOfStages, BigDecimal caseQty, List<WaveBillDO> waveBillDOS, boolean isEmpty) {
        BigDecimal totalVolume = waveBillDOS.stream().filter(t->t.getTotalVolume()!=null).map(WaveBillDO::getTotalVolume).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
        String failReason = "";
        for (int i = stages.size() - numOfStages; i >= 0; i--) {
            if (isEmpty) {
                WorkStage WorkStage=stages.get(i);
                if (WorkStage.getCaseQty().compareTo(BigDecimal.ZERO) != 0) {
                    continue;
                }
            }
            // 订单数量
            List<WorkStage> subList = stages.subList(i, i + numOfStages);
            BigDecimal remainBillCount = subList.stream().map(WorkStage::getRemainNumOfBills).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            if (remainBillCount.compareTo(BigDecimal.ONE)<0) {
                failReason = "大集货区累计可用订单数不够";
                continue;
            }
            // 体积
            BigDecimal remainVol = subList.stream().filter(t->t.getRemainVol()!=null).map(WorkStage::getRemainVol).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            if (remainVol.compareTo(totalVolume) < 0) {
                failReason = "大集货区累计可用体积不够";
                continue;
            }
            // 客户数量
            boolean shipperIdNumOk = true;
            for (int j = 0; j < subList.size(); j++) {
                WorkStage workStage = subList.get(j);
                if (workStage.getBills()!=null && workStage.getBills().size()>0) {
                    List<String> list = workStage.getBills().stream().map(t -> t.getShipperId()).collect(Collectors.toList());
                    if (!list.contains(waveBillDOS.get(0).getShipperId())) {
                        if (workStage.getInfo().getMaxClientNum().compareTo(workStage.getNumOfClients())==0) {
                            shipperIdNumOk = false;
                        }
                    }
                }
            }
            if (!shipperIdNumOk) {
                failReason = "大集货区累计可用客户数量不够";
                continue;
            }
            // 计算连续集货区的总余量是否满足要求
            BigDecimal sum = subList.stream().map(WorkStage::getRemain).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            if (caseQty.compareTo(sum)<=0) {
                for (int j = 0; j < subList.size(); j++) {
                    WorkStage workStage = subList.get(j);
                    caseQty = caseQty.subtract(workStage.getRemain());
                    if (caseQty.compareTo(workStage.getRemain())>0) {
                        workStage.setResult(workStage.getRemain());
                    } else {
                        workStage.setResult(caseQty);
                    }
                }
                return Optional.of(subList);
            }
        }
        if (!isEmpty && numOfStages == stages.size()) {
            throw new BizException(StringUtils.isBlank(failReason) ? "订单体积、客户数、件数找不到可用多个集货区":failReason);
        } else {
            return Optional.empty();
        }
    }

    /**
     * 计算结果每个暂存区应该放的箱数
     * @param workStages 集货区集合
     * @param caseQty 折合件数
     * @return List<WorkStage> 集货区集合
     */
    @Deprecated
    private static List<WorkStage> calculWorkStage(List<WorkStage> workStages, BigDecimal caseQty) {
        List<WorkStage> results = new ArrayList<>();
        BigDecimal left = caseQty;
        for (WorkStage workStage : workStages) {
            results.add(workStage);
            BigDecimal remain = workStage.getRemain();
            left = left.subtract(remain);
            // 判断数量是否已经满足要求
            if (left.compareTo(BigDecimal.ZERO) >= 0) {
                // 未满足继续
                workStage.setResult(remain);
                continue;
            } else {
                // 已经满足,跳出
                workStage.setResult(remain.add(left));
                break;
            }
        }
        return results;
    }

    @Override
    public void compute(StrategyContext strategyContext) {}
}
