package com.ruoyi.bst.bonus.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.ruoyi.bst.areaJoin.domain.AreaJoinVO;
import com.ruoyi.bst.bonus.domain.Bonus;
import com.ruoyi.bst.bonus.domain.BonusVO;
import com.ruoyi.bst.bonus.domain.enums.BonusArrivalType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.MathUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.collection.CollectionUtils;

public class BonusUtil {

    /**
     * 获取初始分成
     */
    public static Bonus getInitBonus(BonusArrivalType arrivalType, Long arrivalId, String arrivalName,
            BigDecimal point, List<Long> viewUserIds) {
        if (arrivalType == null || arrivalId == null || point == null) {
            return null;
        }
        Bonus bonus = new Bonus();
        bonus.setArrivalType(arrivalType.getType());
        bonus.setArrivalId(arrivalId);
        bonus.setArrivalName(arrivalName);
        bonus.setPoint(point);
        if (viewUserIds != null) {
            bonus.setViewUserIds(viewUserIds.stream().filter(Objects::nonNull).collect(Collectors.toList()));
        }
        return bonus;
    }

    /**
     * 处理退款金额分配的误差
     *
     * @param refundList    退款列表
     * @param bonusList     原始分成列表
     * @param dividedAmount 已分配金额
     * @param refundAmount  需要退款总金额
     */
    public static void handleRefundAmountDiff(List<Bonus> refundList, List<BonusVO> bonusList, BigDecimal dividedAmount,
            BigDecimal refundAmount) {
        if (dividedAmount.compareTo(refundAmount) == 0) {
            return;
        }
        // 多个分成方共同承担误差
        BigDecimal remainingDiff = refundAmount.subtract(dividedAmount);
        for (Bonus refundBonus : refundList) {
            // 获取原始分成记录
            BonusVO originalBonus = bonusList.stream()
                    .filter(b -> b.getId().equals(refundBonus.getId()))
                    .findFirst()
                    .orElse(null);

            if (originalBonus == null) {
                continue;
            }

            if (remainingDiff.compareTo(BigDecimal.ZERO) > 0) {
                // 处理正数误差（少退了）：需要增加退款金额
                // 计算当前分成方的剩余可退金额
                BigDecimal availableRefund = MathUtils.subtractDecimal(originalBonus.getAmount(), originalBonus.getRefundAmount(), refundBonus.getRefundAmount());

                // 如果剩余可退金额足够承担误差
                if (availableRefund.compareTo(BigDecimal.ZERO) > 0) {
                    // 取可用退款金额与剩余误差中的较小值
                    BigDecimal adjustAmount = availableRefund.min(remainingDiff);
                    refundBonus.setRefundAmount(refundBonus.getRefundAmount().add(adjustAmount));
                    remainingDiff = remainingDiff.subtract(adjustAmount);
                }
            } else {
                // 处理负数误差（多退了）：需要减少退款金额
                // 计算当前退款金额
                BigDecimal currentRefund = refundBonus.getRefundAmount();
                if (currentRefund.compareTo(BigDecimal.ZERO) > 0) {
                    // 取当前退款金额与剩余需要减少金额的较小值
                    BigDecimal adjustAmount = currentRefund.min(remainingDiff.abs());
                    refundBonus.setRefundAmount(currentRefund.subtract(adjustAmount));
                    remainingDiff = remainingDiff.add(adjustAmount);
                }
            }

            // 如果误差已经分配完毕，则退出
            if (remainingDiff.compareTo(BigDecimal.ZERO) == 0) {
                return;
            }
        }

        // 如果仍然无法完全分配误差,抛出异常
        if (remainingDiff.compareTo(BigDecimal.ZERO) != 0) {
            throw new ServiceException("退款金额分配出错:无法处理误差金额，剩余误差：" + remainingDiff);
        }
    }

    /**
     * 构建退款列表
     *
     * @param bonusList    原始分成列表
     * @param refundAmount 需要退款总金额
     * @return 退款列表
     */
    public static List<Bonus> buildRefundList(List<BonusVO> bonusList, BigDecimal refundAmount) {
        if (CollectionUtils.isEmptyElement(bonusList) || refundAmount == null) {
            return Collections.emptyList();
        }
        List<Bonus> refundList = new ArrayList<>();
        BigDecimal dividedAmount = BigDecimal.ZERO; // 已分配金额

        for (BonusVO bonus : bonusList) {
            if (bonus == null) {
                continue;
            }

            // 计算该分成记录的可退金额 = 分配金额 - 已退金额
            BigDecimal availableRefund = MathUtils.subtractDecimal(bonus.getAmount(), bonus.getRefundAmount());
            if (availableRefund.compareTo(BigDecimal.ZERO) <= 0) {
                continue; // 跳过已无可退金额的记录
            }

            // 根据分成比例计算本次应退金额
            BigDecimal proportion = bonus.getPoint();
            BigDecimal shouldRefund = refundAmount.multiply(proportion).setScale(2, RoundingMode.HALF_UP);

            // 如果计算出的应退金额为0，但原始分成金额大于0，则使用剩余可退金额和退款金额中的较小值
            if (shouldRefund.compareTo(BigDecimal.ZERO) == 0 && bonus.getAmount().compareTo(BigDecimal.ZERO) > 0) {
                shouldRefund = availableRefund.min(refundAmount);
            }

            // 实际退款金额取可退金额与应退金额的较小值
            BigDecimal actualRefund = shouldRefund.min(availableRefund);
            if (actualRefund.compareTo(BigDecimal.ZERO) <= 0) {
                // 退款金额小于或等于0，不进行退款
                continue;
            }

            Bonus refundBonus = new Bonus();
            refundBonus.setId(bonus.getId());
            refundBonus.setStatus(bonus.getStatus());
            refundBonus.setRefundAmount(actualRefund);
            refundBonus.setArrivalId(bonus.getArrivalId());
            refundBonus.setArrivalType(bonus.getArrivalType());
            refundBonus.setBstId(bonus.getBstId());
            refundBonus.setBstType(bonus.getBstType());
            refundBonus.setToBalance(bonus.getToBalance());
            refundList.add(refundBonus);

            dividedAmount = dividedAmount.add(actualRefund);
        }

        // 处理误差
        BonusUtil.handleRefundAmountDiff(refundList, bonusList, dividedAmount, refundAmount);

        // 校验金额
        for (Bonus bonus : refundList) {
            ServiceUtil.assertion(bonus.getRefundAmount().compareTo(BigDecimal.ZERO) < 0, "退款金额不允许小于0");
        }
        BigDecimal sum = CollectionUtils.sumDecimal(refundList, Bonus::getRefundAmount);
        ServiceUtil.assertion(sum.compareTo(refundAmount) != 0, "退款金额分配出错");

        return refundList;
    }

    /**
     * 分成金额
     *
     * @param bonusList   分成列表
     * @param totalAmount 总金额
     */
    public static void partAmount(List<? extends Bonus> bonusList, BigDecimal totalAmount) {
        if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("分成总金额不允许小于0");
        }

        BigDecimal decimal100 = new BigDecimal("100");
        // 循环遍历，构造分成金额
        for (Bonus bonus : bonusList) {
            if (bonus != null && bonus.getPoint() != null) {
                BigDecimal amount = totalAmount.multiply(bonus.getPoint()).divide(decimal100, 2, RoundingMode.HALF_UP);
                setAmount(bonus, amount);
            }
        }

        // 处理误差
        BigDecimal dividedAmount = CollectionUtils.sumDecimal(bonusList, Bonus::getAmount);
        handlePartDiff(bonusList, totalAmount.subtract(dividedAmount));

        // 处理误差后的分配金额
        dividedAmount = CollectionUtils.sumDecimal(bonusList, Bonus::getAmount);
        ServiceUtil.assertion(dividedAmount.compareTo(totalAmount) != 0, "分成金额分配出错");

    }

    /**
     * 处理分成的误差
     */
    public static void handlePartDiff(List<? extends Bonus> bonusList, BigDecimal diff) {
        // 若误差金额为0，则不处理
        if (diff == null || diff.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }

        // 若误差金额为正数，则交给第一个分成方处理
        if (diff.compareTo(BigDecimal.ZERO) > 0) {
            Bonus bonus = bonusList.get(0);
            setAmount(bonus, bonus.getAmount().add(diff));
        }
        // 否则，若为负数，则多个分成方处理
        else {
            // 多个分成方共同承担误差
            BigDecimal remainingDiff = diff;
            for (Bonus bonus : bonusList) {
                if (bonus == null || bonus.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                    continue;
                }

                // 计算当前分成方可以承担的误差金额（确保不会导致金额为负）
                BigDecimal maxDeductible = bonus.getAmount(); // 最大可扣除金额
                BigDecimal adjustAmount = maxDeductible.min(remainingDiff.abs());

                // 直接调整金额
                setAmount(bonus, bonus.getAmount().subtract(adjustAmount));
                remainingDiff = remainingDiff.add(adjustAmount);

                // 如果误差已经分配完毕，则退出
                if (remainingDiff.compareTo(BigDecimal.ZERO) == 0) {
                    return;
                }
            }

            // 如果所有分成方都处理完了还有剩余误差，抛出异常
            if (remainingDiff.compareTo(BigDecimal.ZERO) != 0) {
                throw new ServiceException("分成金额误差处理失败：无法完全分配误差金额，剩余误差：" + remainingDiff);
            }
        }
    }

    private static void setAmount(Bonus bonus, BigDecimal amount) {
        ServiceUtil.assertion(amount.compareTo(BigDecimal.ZERO) < 0, "分成金额不允许小于0");
        bonus.setAmount(amount);
    }

    /**
     * 添加合伙人分成
     * @param result
     * @param partners
     * @param viewUserIds
     * @param totalPoint
     */
    public static void addPartnerBonus(List<Bonus> result, List<AreaJoinVO> partners, List<Long> viewUserIds) {
        if (CollectionUtils.isEmptyElement(partners)) {
            return;
        }
        for (AreaJoinVO partner : partners) {
            // 合伙人分成
            BonusUtil.addBonus(result, BonusArrivalType.PARTNER, partner.getUserId(),
                    partner.getUserName(), partner.getPoint(), viewUserIds);
        }
    }

    /**
     * 添加分成
     * @param result 分成列表
     * @param arrivalType 分成类型
     * @param arrivalId 分成ID
     * @param arrivalName 分成名称
     * @param point 分成比例
     * @param viewUserIds 可见用户ID列表
     */
    public static void addBonus(List<Bonus> result, BonusArrivalType arrivalType, Long arrivalId, String arrivalName,
            BigDecimal point, List<Long> viewUserIds) {
        if (arrivalId != null && point != null) {
            Bonus bonus = BonusUtil.getInitBonus(arrivalType, arrivalId, arrivalName,
                    point, viewUserIds);
            if (bonus != null) {
                result.add(bonus);
            }
        }
    }

    public static BigDecimal sumPoint(List<Bonus> result) {
        if (result == null) {
            return BigDecimal.ZERO;
        }
        return CollectionUtils.sumDecimal(result, Bonus::getPoint);
    }
}
