
package com.tmerclub.cloud.user.manager;

import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.common.constant.ConfigNameConstant;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.order.constant.OrderType;
import com.tmerclub.cloud.common.order.vo.ShopCartItemDiscountVO;
import com.tmerclub.cloud.common.order.vo.ShopCartItemVO;
import com.tmerclub.cloud.common.order.vo.ShopCartOrderMergerVO;
import com.tmerclub.cloud.common.order.vo.ShopCartOrderVO;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.Arith;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.common.util.PriceUtil;
import com.tmerclub.cloud.user.model.UserExtension;
import com.tmerclub.cloud.user.service.UserExtensionService;
import com.tmerclub.cloud.user.vo.ScoreCompleteConfigVO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户订单积分抵扣优惠
 *
 * @author tmerclub
 * @date 2021/05/10
 */
@Component
public class UserScoreOrderManager {

    @DubboReference
    private ConfigFeignClient configFeignClient;
    @Autowired
    private UserExtensionService userExtensionService;


    public void calculateScoreDiscount(ShopCartOrderMergerVO shopCartOrderMerger) {
        Long userId = AuthUserContext.get().getUserId();
        UserExtension userExtension = userExtensionService.getByUserId(userId);
        // 积分订单处理
        if (Objects.equals(shopCartOrderMerger.getOrderType(), OrderType.SCORE)) {
            // 积分订单不需要处理积分抵扣， 直接返回
            shopCartOrderMerger.setUserHasScore(userExtension.getScore());
            return;
        }
        // 积分成长值设置
        String scoreConfigValue = configFeignClient.getConfig(ConfigNameConstant.SCORE_CONFIG).getData();
        ScoreCompleteConfigVO scoreParam = Json.parseObject(scoreConfigValue, ScoreCompleteConfigVO.class);
        // 如果未开启积分抵扣，那么直接返回
        if (scoreParam.getShoppingScoreSwitch() != null && !scoreParam.getShoppingScoreSwitch()) {
            return;
        }

        // 用户可用积分
        Long userHasScore = userExtension.getScore();
        shopCartOrderMerger.setUserHasScore(userHasScore);

        // 用户使用积分
        Long usableScore = userExtension.getScore();
//        // 如果没有说明使用多少积分或要求使用的积分大于用户已有的积分，则自动使用全部用户已有积分
//        if (usableScore == null || usableScore > userHasScore) {
//            usableScore = userHasScore;
//        }
        //如果是用户选择的积分数额，则用此积分数额
        if (shopCartOrderMerger.getUsableScore() != null && shopCartOrderMerger.getIsScorePay() == 1 && shopCartOrderMerger.getUsableScore() >= 0) {
            usableScore = Math.min(shopCartOrderMerger.getUsableScore(), usableScore);
        } else {
            usableScore = 0L;
        }

        // 购物积分抵现比例(x积分抵扣1元）
        long shoppingUseScoreRatio = Objects.isNull(scoreParam.getShoppingUseScoreRatio()) ? 0L : scoreParam.getShoppingUseScoreRatio();
        if (shoppingUseScoreRatio == 0) {
            return;
        }
        shopCartOrderMerger.setShoppingUseScoreRatio(shoppingUseScoreRatio);

        // 平台使用积分分类比例上限
        double useRatioLimit = Objects.isNull(scoreParam.getUseRatioLimit()) ? 0.0 : scoreParam.getUseRatioLimit();

        // 最大可抵现金额 = ((订单实际金额 - 订单最小金额) * 使用积分分类比例上限)
        long totalActualAmount = shopCartOrderMerger.getActualTotal() - shopCartOrderMerger.getTotalTransfee();
        // 积分最多抵现金额
        long orderMaxCanOffsetCashAmount = PriceUtil.divideByBankerRounding((long) Arith.mul(totalActualAmount, useRatioLimit), 100);

        // 计算用户可用积分
        long canUseScore = orderMaxCanOffsetCashAmount * shoppingUseScoreRatio / 100;
        long maxScore = Math.min(userHasScore, canUseScore);
        usableScore = Math.min(usableScore, canUseScore);
        // 计算可抵扣金额，然后算使用积分，再用使用积分算抵扣金额
        double totalScoreAmount = Arith.div(Arith.mul(usableScore, 100), shoppingUseScoreRatio);
        usableScore = PriceUtil.divideByBankerRounding((long) Arith.mul(totalScoreAmount, shoppingUseScoreRatio), 100);
        orderMaxCanOffsetCashAmount = PriceUtil.doublerDivideByBankerRounding(Arith.mul(usableScore, 100), shoppingUseScoreRatio);
        // 在极端积分比例情况下，最大可用积分和使用的积分有冲突
        long maxScoreAmount = PriceUtil.doublerDivideByBankerRounding(Arith.mul(maxScore, 100), shoppingUseScoreRatio);
        maxScore = maxScoreAmount * shoppingUseScoreRatio / 100;
        // 这里如果不使用最大可用积分，则后续的计算还需要乘以这个比例
        long scale = 100;
        if (usableScore < canUseScore) {
            // 应该是使用的积分和最大可用积分的比例，此时的最大可用积分是订单估计最大可用积分，而不是用户最大可用积分
            // 如果使用用户最大可用积分做比例就会有问题，所以这里直接用订单预计最大可用积分做比例
            scale = PriceUtil.doublerDivideByBankerRounding(Arith.mul(usableScore, 100), canUseScore);
        }
        calculatedAmount(shopCartOrderMerger, scoreParam, orderMaxCanOffsetCashAmount, scale, usableScore, maxScore);
    }

    /**
     * 重新计算分摊金额
     *
     * @param shopCartOrderMerger         购物车信息
     * @param scoreParam                  积分配置信息
     * @param orderMaxCanOffsetCashAmount 积分最多抵现金额
     * @param scale                       比例
     * @param usableScore                 用户使用积分
     * @param maxScore                    最大可用积分
     */
    private void calculatedAmount(ShopCartOrderMergerVO shopCartOrderMerger, ScoreCompleteConfigVO scoreParam,
                                  long orderMaxCanOffsetCashAmount, long scale, long usableScore, long maxScore) {
        // 用户选择积分抵现，组装积分信息
        List<ShopCartOrderVO> shopCartOrders = shopCartOrderMerger.getShopCartOrders();
        long totalScoreReduce = 0;
        double useDiscount = Objects.isNull(scoreParam.getUseRatioLimit()) ? 0.0 : scoreParam.getUseRatioLimit();
        long totalScore = 0;
        // 通过for i找出最后一项，将计算偏差的1积分给最后的最大的一项
        long maxAmount = 0;
        Long maxAmountSkuId = 0L;
        for (ShopCartOrderVO shopCartOrder : shopCartOrders) {
            for (ShopCartItemDiscountVO shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) {
                for (ShopCartItemVO shopCartItem : shopCartItemDiscount.getShopCartItems()) {
                    maxAmountSkuId = shopCartItem.getActualTotal() > maxAmount ? shopCartItem.getSkuId() : maxAmountSkuId;
                    maxAmount = Math.max(shopCartItem.getActualTotal(), maxAmount);
                }
            }
        }
        for (ShopCartOrderVO shopCartOrder : shopCartOrders) {
            long reduceSum = 0;
            long shopScore = 0;
            List<ShopCartItemDiscountVO> shopCartItemDiscounts = shopCartOrder.getShopCartItemDiscounts();
            for (ShopCartItemDiscountVO shopCartItemDiscount : shopCartItemDiscounts) {
                List<ShopCartItemVO> shopCartItems = shopCartItemDiscount.getShopCartItems();
                for (ShopCartItemVO shopCartItem : shopCartItems) {
                    // 如果是金额最大的一项，直接跳过，最后在处理
                    if (Objects.equals(shopCartItem.getSkuId(), maxAmountSkuId)) {
                        continue;
                    }
                    // 计算商品分摊的金额，需要乘以比例
                    long scoreReduceProd = PriceUtil.divideByBankerRounding((long) Arith.mul(shopCartItem.getActualTotal(), useDiscount), 100);

                    long useScore = PriceUtil.divideByBankerRounding(scoreReduceProd * scale * scoreParam.getShoppingUseScoreRatio(), 10000L);
                    scoreReduceProd = PriceUtil.doublerDivideByBankerRounding(Arith.mul(useScore, 100), scoreParam.getShoppingUseScoreRatio());
                    // 如果大于可用上限则直接等于，接将剩余的抵扣金额全部赋予最后一个，积分和金额直接等于 使用的 - 已经抵扣的
                    if (totalScoreReduce + scoreReduceProd > orderMaxCanOffsetCashAmount) {
                        // 减去当前总共的积分，减去店铺已分摊的积分
                        useScore = usableScore - totalScore - shopScore;
                        scoreReduceProd = orderMaxCanOffsetCashAmount - totalScoreReduce;
                    }

                    totalScoreReduce = totalScoreReduce + scoreReduceProd;
                    reduceSum = reduceSum + scoreReduceProd;
                    shopScore += useScore;
                    setShopCartItemInfo(shopCartOrderMerger, shopCartItem, useScore, scoreReduceProd);
                }
            }
            // 设置店铺的实际总值、积分优惠金额和订单优惠金额
            shopCartOrder.setScoreReduce(reduceSum);
            shopCartOrder.setActualTotal(shopCartOrder.getActualTotal() - reduceSum);
            // 放入优惠金额
            shopCartOrder.setShopReduce(shopCartOrder.getShopReduce() + reduceSum);
            // 放入平台优惠金额
            long orderScoreReduce = Objects.isNull(shopCartOrder.getPlatformAmount()) ? reduceSum : shopCartOrder.getPlatformAmount() + reduceSum;
            shopCartOrder.setPlatformAmount(orderScoreReduce);
            totalScore += shopScore;
            if (shopCartOrderMerger.getIsScorePay() != null && shopCartOrderMerger.getIsScorePay() == 1) {
                shopCartOrder.setUseScore(shopScore);
            }
        }
        // 处理最后一项
        for (ShopCartOrderVO shopCartOrder : shopCartOrders) {
            for (ShopCartItemDiscountVO shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) {
                for (ShopCartItemVO shopCartItem : shopCartItemDiscount.getShopCartItems()) {
                    // 如果不是金额最大的一项，直接跳过
                    if (!Objects.equals(shopCartItem.getSkuId(), maxAmountSkuId)) {
                        continue;
                    }
                    // 减去当前总共的积分，减去店铺已分摊的积分
                    long useScore = usableScore - totalScore;
                    long scoreReduceProd = orderMaxCanOffsetCashAmount - totalScoreReduce;
                    setShopCartItemInfo(shopCartOrderMerger, shopCartItem, useScore, scoreReduceProd);
                    // 设置店铺的实际总值、积分优惠金额和订单优惠金额
                    shopCartOrder.setScoreReduce(shopCartOrder.getScoreReduce() + scoreReduceProd);
                    shopCartOrder.setActualTotal(shopCartOrder.getActualTotal() - scoreReduceProd);
                    // 放入优惠金额
                    shopCartOrder.setShopReduce(shopCartOrder.getShopReduce() + scoreReduceProd);
                    // 放入平台优惠金额,如果用户等级免自营店运费也要放进去
                    long orderScoreReduce = Objects.isNull(shopCartOrder.getPlatformAmount()) ? scoreReduceProd : shopCartOrder.getPlatformAmount() + scoreReduceProd;
                    shopCartOrder.setPlatformAmount(orderScoreReduce);
                    if (shopCartOrderMerger.getIsScorePay() != null && shopCartOrderMerger.getIsScorePay() == 1) {
                        shopCartOrder.setUseScore(shopCartOrder.getUseScore() + useScore);
                    }
                    totalScoreReduce = totalScoreReduce + scoreReduceProd;
                    totalScore += useScore;
                    break;
                }
            }
        }
        setAmount(shopCartOrderMerger, scoreParam, maxScore, totalScoreReduce, totalScore);
    }

    private static void setAmount(ShopCartOrderMergerVO shopCartOrderMerger, ScoreCompleteConfigVO scoreParam, long maxScore, long totalScoreReduce, long totalScore) {
        // 设置订单的实际总值和订单优惠金额
        shopCartOrderMerger.setTotalScoreAmount(totalScoreReduce);
        shopCartOrderMerger.setShoppingUseScoreRatio(scoreParam.getShoppingUseScoreRatio());
        shopCartOrderMerger.setTotalUsableScore(totalScore);
        shopCartOrderMerger.setActualTotal(shopCartOrderMerger.getActualTotal() - totalScoreReduce);
        shopCartOrderMerger.setOrderReduce(shopCartOrderMerger.getOrderReduce() + totalScoreReduce);
        shopCartOrderMerger.setMaxUsableScore(maxScore);
    }

    private void setShopCartItemInfo(ShopCartOrderMergerVO shopCartOrderMerger, ShopCartItemVO shopCartItem, long useScore, long scoreReduceProd) {
        if (shopCartOrderMerger.getIsScorePay() != null && shopCartOrderMerger.getIsScorePay() == 1) {
            long platformReduce = shopCartItem.getPlatformShareReduce() == null ? 0 : shopCartItem.getPlatformShareReduce();
            shopCartItem.setPlatformShareReduce(platformReduce + scoreReduceProd);
            shopCartItem.setScoreReduce(scoreReduceProd);
            shopCartItem.setScorePrice(useScore);
            shopCartItem.setActualTotal(shopCartItem.getActualTotal() - scoreReduceProd);
        }
    }

    /**
     * 再次校验分摊金额
     *
     * @param shopCartOrderMerger   购物车信息
     * @param scoreAmountInOrder    积分在订单中占优惠的比例
     * @param actualTotalReduce     实际上使用积分优惠
     * @param shoppingUseScoreRatio 购物积分抵现比例(x积分抵扣1元）
     */
    private void verifyAmount(ShopCartOrderMergerVO shopCartOrderMerger, double scoreAmountInOrder, long actualTotalReduce, Long shoppingUseScoreRatio) {
        List<ShopCartOrderVO> shopCartOrders = shopCartOrderMerger.getShopCartOrders();
        List<ShopCartItemVO> allShopCartItem = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (ShopCartOrderVO shopCartOrder : shopCartOrders) {
            List<ShopCartItemDiscountVO> shopCartItemDiscounts = shopCartOrder.getShopCartItemDiscounts();
            for (ShopCartItemDiscountVO shopCartItemDiscount : shopCartItemDiscounts) {
                List<ShopCartItemVO> shopCartItems = shopCartItemDiscount.getShopCartItems();
                allShopCartItem.addAll(shopCartItems);
            }
        }

        // 订单项按金额从小到大
        allShopCartItem.sort(Comparator.comparingDouble(ShopCartItemVO::getActualTotal));

        long discountAmount = 0L;
        for (int i = 0; i < allShopCartItem.size(); i++) {
            ShopCartItemVO shopCartItem = allShopCartItem.get(i);
            long itemDiscount;
            // 最后一项
            if (i == allShopCartItem.size() - 1) {
                itemDiscount = actualTotalReduce - discountAmount;
                // 如果分到最后一项的钱太多了，比订单项目金额都要多，订单项的分摊金额就当作订单项金额咯
                if (itemDiscount > shopCartItem.getActualTotal()) {
                    itemDiscount = shopCartItem.getActualTotal();
                }
            }
            // 非最后一项
            else {
                itemDiscount = PriceUtil.divideByBankerRounding((long) Arith.mul(shopCartItem.getActualTotal(), scoreAmountInOrder), 100);

            }
            Long useScore = PriceUtil.divideByBankerRounding((long) Arith.mul(shoppingUseScoreRatio, itemDiscount), 100);
            // 非最后一项 这里金额重新计算了一遍，请勿随意修改顺序
            if (i != allShopCartItem.size() - 1) {
                discountAmount += PriceUtil.doublerDivideByBankerRounding(Arith.mul(useScore, 100), shoppingUseScoreRatio);
            }


            shopCartItem.setScoreReduce(PriceUtil.doublerDivideByBankerRounding(Arith.mul(useScore, 100), shoppingUseScoreRatio));
            shopCartItem.setScorePrice(useScore);
            shopCartItem.setActualTotal(shopCartItem.getActualTotal() - shopCartItem.getScoreReduce());
            shopCartItem.setPlatformShareReduce(shopCartItem.getPlatformShareReduce() + shopCartItem.getScoreReduce());
        }
        Map<Long, List<ShopCartItemVO>> shopCartOrderMap = allShopCartItem.stream().collect(Collectors.groupingBy(ShopCartItemVO::getShopId));
        long totalScoreAmount = 0L;
        for (ShopCartOrderVO shopCartOrder : shopCartOrders) {
            List<ShopCartItemVO> shopCartItems = shopCartOrderMap.get(shopCartOrder.getShopId());
            long scoreReduce = 0;
            long useScore = 0;

            for (ShopCartItemVO shopCartItem : shopCartItems) {
                scoreReduce += shopCartItem.getScoreReduce();
                useScore += shopCartItem.getScorePrice();
            }
            shopCartOrder.setUseScore(useScore);
            shopCartOrder.setScoreReduce(scoreReduce);
            shopCartOrder.setActualTotal(shopCartOrder.getActualTotal() - scoreReduce);
            long orderScoreReduce = Objects.isNull(shopCartOrder.getPlatformAmount()) ? scoreReduce : shopCartOrder.getPlatformAmount() + scoreReduce;
            shopCartOrder.setPlatformAmount(orderScoreReduce);
            shopCartOrder.setShopReduce(shopCartOrder.getShopReduce() + scoreReduce);
            totalScoreAmount += scoreReduce;
        }
        shopCartOrderMerger.setTotalScoreAmount(totalScoreAmount);
        shopCartOrderMerger.setOrderReduce(shopCartOrderMerger.getOrderReduce() + totalScoreAmount);
        shopCartOrderMerger.setActualTotal(shopCartOrderMerger.getActualTotal() - totalScoreAmount);
    }
}
