package com.jeequan.jeepay.core.model.applyment;

import cn.hutool.core.collection.CollUtil;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.AmountUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.MutableTriple;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 支付方式的产品费率定义
 *
 * @author terrfly
 * @date 2022/1/6 17:04
 */
@Data
@Slf4j
public class PaywayFee implements Comparable<PaywayFee> {

    /**
     * 单笔固定 、 单笔费率、 单笔阶梯
     **/
    public static final String FEE_TYPE_FIX = "FIX";
    public static final String FEE_TYPE_SINGLE = "SINGLE";
    public static final String FEE_TYPE_LEVEL = "LEVEL";
    public static final String FEE_TYPE_FIXANDRATE = "FIXANDRATE"; // 单笔 + 费率（ 目前 仅服务商提现使用该模式 ）
    public static final String FEE_TYPE_TRANSMODE = "TRANSMODE"; // 转账模式：  单笔 + 费率（ 并且可配置最大和保底费用 ）

    /**
     * 状态 0-关闭 1-开启
     **/
    private Byte state;

    /**
     * 支付方式代码
     */
    private String wayCode;

    /**
     * 进件是否显示（仅渠道商配置）
     */
    private Byte applymentSupport;

    /**
     * 是否可编辑（仅渠道商配置）
     */
    private Byte editable;

    /**
     * 费率方式
     */
    private String feeType;

    /**
     * 免收手续费额度  单位： 分
     */
    private Long freeLimit;

    /**
     * 固定费用， 仅FIX生效  单位： 分
     */
    private Long fixFee;

    /**
     * 费率（存储的为实际费率值， 如费率为： 0.6% 则存储值为： 0.006）
     */
    private BigDecimal feeRate;

    /**
     * 费率计算最低收取值 / 阶梯价格1  单位： 分
     */
    private Long minFee;

    /**
     * 费率计算最高收取值 / 阶梯价格2  单位： 分
     */
    private Long maxFee;

    /**
     * 阶梯定价列表
     **/
    List<PaywayFee> levelList;

    /**
     * 主model里保存的是： 借记卡（储蓄卡）的费率设置值
     * creditCardPaywayFee： 贷记卡（信用卡）的费率设置值
     **/
    private PaywayFee creditCardPaywayFee;

    /**
     * 计算费率值
     **/
    public Long calFee(Long orderAmount) {
        return this.calFeeAndSnapshot(orderAmount).right;
    }

    /**
     * 转换为： MutableTriple<手续费快照, 商户实际手续费, 本次退还手续费>
     **/
    public static MutableTriple<String, Long, Long> convert2MutableTriple(MutablePair<String, Long> mutablePair, Long oldMchFee) {
        return MutableTriple.of(mutablePair.getLeft(), mutablePair.getRight(), AmountUtil.minZero(oldMchFee - mutablePair.getRight()));
    }


    /**
     * 计算金额的费率费用 和 快照字符串
     **/
    public MutablePair<String, Long> calFeeAndSnapshot(Long orderAmount) {

        // 单笔费率
        if (FEE_TYPE_SINGLE.equals(this.feeType)) {
            String str = String.format("%s单笔费率：%s", genFreeLimitStr(), AmountUtil.convertPercentage(feeRate, 4));
            return MutablePair.of(str, calFreeLimitAmount(orderAmount, AmountUtil.calPercentageFee(orderAmount, feeRate)));
        }

        // 单笔固定
        if (FEE_TYPE_FIX.equals(this.feeType)) {
            String str = String.format("%s单笔固定：%s元", genFreeLimitStr(), AmountUtil.convertCent2Dollar(this.fixFee));
            return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.fixFee));
        }

        // 单笔固定 + 费率
        if (FEE_TYPE_FIXANDRATE.equals(this.feeType)) {
            String str = String.format("%s[%s元 + %s]", genFreeLimitStr(), AmountUtil.convertCent2Dollar(this.fixFee), AmountUtil.convertPercentage(feeRate, 4));
            return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.fixFee + AmountUtil.calPercentageFee(orderAmount, feeRate)));
        }

        // 转账模式
        if (FEE_TYPE_TRANSMODE.equals(this.feeType)) {
            String str = String.format("单笔[%s元 + %s], 保底%s元, 封顶%s元",
                    AmountUtil.convertCent2Dollar(this.fixFee), AmountUtil.convertPercentage(feeRate, 4),
                    AmountUtil.convertCent2Dollar(this.getMinFee()), AmountUtil.convertCent2Dollar(this.getMaxFee())
            );

            Long fee = calFreeLimitAmount(orderAmount, this.fixFee + AmountUtil.calPercentageFee(orderAmount, feeRate));

            // 费用超过 最大费率
            if (this.maxFee != null && fee > this.maxFee) {
                return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.maxFee));
            }

            // 费用小于 保底费率
            if (this.minFee != null && fee < this.minFee) {
                return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.minFee));
            }

            return MutablePair.of(str, fee);
        }

        // 阶梯费率
        // 0 - 100 区间值：  ( ]   左侧大于， 右侧小于等于。
        // 100 - 200

        for (PaywayFee paywayFeeItem : levelList) {

            // ( ]   左侧大于， 右侧小于等于。
            if (paywayFeeItem.getMinFee() < orderAmount && orderAmount <= paywayFeeItem.getMaxFee()) {

                Long fee = AmountUtil.calPercentageFee(orderAmount, paywayFeeItem.getFeeRate());

                String str = String.format("%s(%s元-%s元]阶梯费率: %s, 保底%s元, 封顶%s元",
                        genFreeLimitStr(),
                        AmountUtil.convertCent2Dollar(paywayFeeItem.getMinFee()),
                        AmountUtil.convertCent2Dollar(paywayFeeItem.getMaxFee()),
                        AmountUtil.convertPercentage(paywayFeeItem.getFeeRate(), 4),
                        AmountUtil.convertCent2Dollar(this.getMinFee()),
                        AmountUtil.convertCent2Dollar(this.getMaxFee())
                );


                // 费用超过 最大费率
                if (this.maxFee != null && fee > this.maxFee) {
                    return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.maxFee));
                }

                // 费用小于 保底费率
                if (this.minFee != null && fee < this.minFee) {
                    return MutablePair.of(str, calFreeLimitAmount(orderAmount, this.minFee));
                }

                return MutablePair.of(str, calFreeLimitAmount(orderAmount, fee));
            }

        }

        // 无费用
        return MutablePair.of("未匹配到费率阶梯", 0L);
    }


    /**
     * 按照阶梯费率 排序
     **/
    @Override
    public int compareTo(PaywayFee o) {
        return this.minFee.compareTo(o.minFee);
    }


    /**
     * 比较两个费率的配置 是否满足 传入的 预期值 expect：
     * 单笔比较费率，  阶梯比较每个阶梯的费率值和最大、 最小收费。
     * 如配置错误， 或比较不正确， 直接抛异常。
     * 0 ： 相等
     * 1： 当前 > 传入
     * -1： 当前 < 传入
     * 10： 当前 >= 传入
     * -10： 当前 <= 传入
     **/

    public void compareByFee(PaywayFee compareFee, int expect, String infoName) {

        // 费率方式不同
        if (!Objects.equals(this.feeType, compareFee.getFeeType())) {
            return;
//            throw new BizException(String.format("[%s]的费率计算方式与[%s]的配置不一致", this.wayCode, infoName));
        }

        // 单笔费率
        if (PaywayFee.FEE_TYPE_SINGLE.equals(this.feeType)) {

            // 比较费率值即可
            int result = this.feeRate.compareTo(compareFee.getFeeRate());

            if (judgeResult(expect, result)) { // 不满足规则
                throw new BizException(String.format("[%s]设置费率{%s} 需要【%s】【%s】的当前配置值：{%s}",
                        this.wayCode, AmountUtil.convertPercentage(this.feeRate, 4), expect2Str(expect), infoName, AmountUtil.convertPercentage(compareFee.feeRate, 4)));
            }

            // 满足条件
            return;
        }

        // 转账模式费率
        if (PaywayFee.FEE_TYPE_TRANSMODE.equals(this.feeType)) {

            // 判断 固定金额
            if (judgeResult(expect, this.fixFee.compareTo(compareFee.fixFee))) {
                throw new BizException(String.format("[%s]设置费率固定费用{%s} 需要【%s】【%s】的当前配置值：{%s}",
                        this.wayCode, AmountUtil.convertCent2Dollar(this.fixFee), expect2Str(expect), infoName, AmountUtil.convertCent2Dollar(compareFee.fixFee)));
            }

            int result = this.feeRate.compareTo(compareFee.getFeeRate());

            if (judgeResult(expect, result)) { // 不满足规则
                throw new BizException(String.format("[%s]设置费率{%s} 需要【%s】【%s】的当前配置值：{%s}",
                        this.wayCode, AmountUtil.convertPercentage(this.feeRate, 4), expect2Str(expect), infoName, AmountUtil.convertPercentage(compareFee.feeRate, 4)));
            }

            // 判断 最大值
            if (judgeResult(expect, this.maxFee.compareTo(compareFee.maxFee))) {
                throw new BizException(String.format("[%s]设置封顶费用{%s} 需要【%s】【%s】的当前配置值：{%s}",
                        this.wayCode, AmountUtil.convertCent2Dollar(this.maxFee), expect2Str(expect), infoName, AmountUtil.convertCent2Dollar(compareFee.maxFee)));
            }

            // 判断 最小值
            if (judgeResult(expect, this.minFee.compareTo(compareFee.minFee))) {
                throw new BizException(String.format("[%s]设置保底费用{%s} 需要【%s】【%s】的当前配置值：{%s}",
                        this.wayCode, AmountUtil.convertCent2Dollar(this.minFee), expect2Str(expect), infoName, AmountUtil.convertCent2Dollar(compareFee.minFee)));
            }

            // 满足条件
            return;
        }

        // 以下为： 阶梯费率

        // 两侧 数量不一致
        if (CollUtil.size(this.levelList) != CollUtil.size(compareFee.getLevelList())) {
            throw new BizException(String.format("[%s]的阶梯费率数量与[%s]的配置数量不一致", this.wayCode, infoName));
        }

        // 判断 最大值
        if (judgeResult(expect, this.maxFee.compareTo(compareFee.maxFee))) {
            throw new BizException(String.format("[%s]设置封顶费用{%s} 需要【%s】【%s】的当前配置值：{%s}",
                    this.wayCode, AmountUtil.convertCent2Dollar(this.maxFee), expect2Str(expect), infoName, AmountUtil.convertCent2Dollar(compareFee.maxFee)));
        }

        // 判断 最小值
        if (judgeResult(expect, this.minFee.compareTo(compareFee.minFee))) {
            throw new BizException(String.format("[%s]设置保底费用{%s} 需要【%s】【%s】的当前配置值：{%s}",
                    this.wayCode, AmountUtil.convertCent2Dollar(this.minFee), expect2Str(expect), infoName, AmountUtil.convertCent2Dollar(compareFee.minFee)));
        }

        // 阶梯费率 全部为空的
        if (CollUtil.isEmpty(this.levelList)) {
            return;
        }

        // 对比阶梯费率
        checkList(this.levelList, compareFee.levelList, expect, wayCode, infoName);

        // 比较贷记卡费率
        boolean isUnequals = this.creditCardPaywayFee == null ^ compareFee.creditCardPaywayFee == null;
        if (isUnequals) {
            throw new BizException(String.format("[%s]银联模式和[%s]的配置不一致", this.wayCode, infoName));
        }

        if (this.creditCardPaywayFee != null) {
            this.creditCardPaywayFee.setWayCode(this.getWayCode());
            this.creditCardPaywayFee.compareByFee(compareFee.creditCardPaywayFee, expect, infoName);
        }
    }

    /**
     * 比较阶梯费率
     */
    public static void checkList(List<PaywayFee> srcList, List<PaywayFee> compareFeeList, int expect,
                                 String wayCode, String infoName) {
        // 按照阶梯排序
        Collections.sort(srcList);
        Collections.sort(compareFeeList);

        log.info("====校验阶梯费率");
        for (int i = 0; i < srcList.size(); i++) {
            PaywayFee c1 = srcList.get(i);
            PaywayFee c2 = compareFeeList.get(i);

            // 阶梯费率的阶梯金额不一致
            if (c1.minFee.longValue() != c2.minFee.longValue()) {
                throw new BizException(String.format("[%s]的第[%s]阶梯起始金额与[%s]的配置不一致", wayCode, (i + 1), infoName));
            }

            // 阶梯费率的阶梯金额不一致
            if (c1.maxFee.longValue() != c2.maxFee.longValue()) {
                throw new BizException(String.format("[%s]的第[%s]阶梯结束金额与[%s]的配置不一致", wayCode, (i + 1), infoName));
            }

            // 判断阶梯费率
            if (judgeResult(expect, c1.feeRate.compareTo(c2.feeRate))) {
                throw new BizException(String.format("[%s]的第[%s]阶梯设置费率{%s} 需要【%s】【%s】的阶梯配置值：{%s}",
                        wayCode, (i + 1), AmountUtil.convertPercentage(c1.feeRate, 4), expect2Str(expect), infoName, AmountUtil.convertPercentage(c2.feeRate, 4)));
            }
        }
    }

    /**
     * 判断预期是否满足
     */
    private static boolean judgeResult(int expect, int result) {

        // 相等
        if (expect == result) {
            return false;
        }

        // 或者相等
        if (expect == -10 && result <= 0) {
            return false;
        }

        // 或者相等
        if (expect == 10 && result >= 0) {
            return false;
        }

        // 不满足
        return true;
    }

    private static String expect2Str(int expect) {

        if (expect == 0) {
            return "等于";
        } else if (expect == 1) {
            return "大于";
        } else if (expect == -1) {
            return "小于";
        } else if (expect == 10) {
            return "大于等于";
        } else if (expect == -10) {
            return "小于等于";
        }

        return null;
    }


    /**
     * 获取免费字符串
     **/
    private String genFreeLimitStr() {

        if (this.freeLimit == null) {
            return "";
        }

        return AmountUtil.convertCent2Dollar(this.freeLimit) + "元内免手续费, ";
    }

    /**
     * 获取是否免费
     **/
    private Long calFreeLimitAmount(Long orderAmount, Long fee) {

        if (this.freeLimit == null) {
            return fee;
        }

        if (orderAmount <= this.freeLimit) {
            return 0L;
        }
        return fee;
    }

    public static PaywayFee toEmptyData(PaywayFee paywayFee) {
        paywayFee.setFeeRate(null);
        Optional.ofNullable(paywayFee.getLevelList()).ifPresent(r -> {
            for (PaywayFee fee : r) {
                fee.setFeeRate(null);
            }
        });

        Optional.ofNullable(paywayFee.getCreditCardPaywayFee()).map(PaywayFee::getLevelList)
                .ifPresent(r -> {
                            for (PaywayFee fee : r) {
                                fee.setFeeRate(null);
                            }
                        }
                );

        return paywayFee;
    }

    /**
     * 费率
     * @param self  目标费率
     * @param parentModel   父级模板费率
     */
    public static void resetData(Map<String, PaywayFee> self, Map<String, PaywayFee> parentModel) {
        if (CollUtil.isEmpty(parentModel)) {
            return;
        }

        for (Map.Entry<String, PaywayFee> parentDefRateItem : parentModel.entrySet()) {
            String wayCode = parentDefRateItem.getKey();
            PaywayFee item = parentDefRateItem.getValue();

            Optional.of(self).ifPresent(r -> {
                PaywayFee paywayFee = r.get(wayCode);
                if (paywayFee == null) {
                    PaywayFee emptyData = PaywayFee.toEmptyData(item);
                    emptyData.setState(item.getState());
                    emptyData.setApplymentSupport(item.getApplymentSupport());
                    r.put(wayCode, emptyData);
                } else {
                    if (!Objects.equals(paywayFee.getFeeType(), item.getFeeType())) {
                        // 费率模式不一样，也返回重置的数据
                        PaywayFee emptyData = PaywayFee.toEmptyData(item);
                        emptyData.setState(item.getState());
                        emptyData.setApplymentSupport(item.getApplymentSupport());
                        r.put(wayCode, emptyData);
                    }

                    if ((paywayFee.getLevelList() == null ^ item.getLevelList() == null)
                        || (paywayFee.getCreditCardPaywayFee() == null ^ item.getCreditCardPaywayFee() == null)) {
                        // 银联模式不同步，也返回重置数据
                        PaywayFee emptyData = PaywayFee.toEmptyData(item);
                        emptyData.setState(item.getState());
                        emptyData.setApplymentSupport(item.getApplymentSupport());
                        r.put(wayCode, emptyData);
                    }
                }
            });
        }
    }

    /**
     * 计算极限金额下产生最小的分润的交易金额
     * @param calBaseAmount
     * @return
     */
    public Long calMinTradeAmt(Long calBaseAmount) {
        BigDecimal minProfitAmt = BigDecimal.valueOf(0.01);
        return minProfitAmt.divide(feeRate,2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).longValue();
    }
}
