package cn.iocoder.yudao.module.pay.service.wallet;

import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT;
import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT_REFUND;

/**
 * 钱包 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
public class PayWalletServiceImpl implements PayWalletService {

    /**
     * 通知超时时间，单位：毫秒
     */
    public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;

    @Resource
    private PayWalletMapper walletMapper;
    @Resource
    private PayWalletLockRedisDAO lockRedisDAO;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayWalletTransactionService walletTransactionService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayOrderService orderService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayRefundService refundService;

    @Override
    public PayWalletDO getOrCreateWallet(Long userId, Integer userType) {
        PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType);
        if (wallet == null) {
            wallet = new PayWalletDO().setUserId(userId).setUserType(userType)
                    .setBalance(0).setTotalExpense(0).setTotalRecharge(0);
            wallet.setCreateTime(LocalDateTime.now());
            walletMapper.insert(wallet);
        }
        return wallet;
    }

    @Override
    public PayWalletDO getWallet(Long walletId) {
        return walletMapper.selectById(walletId);
    }

    @Override
    public PageResult<PayWalletDO> getWalletPage(PayWalletPageReqVO pageReqVO) {
        return walletMapper.selectPage(pageReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) {
        // 1. 判断支付交易拓展单是否存
        PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
        if (orderExtension == null) {
            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
        }
        PayWalletDO wallet = getOrCreateWallet(userId, userType);
        // 2. 扣减余额
        return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) {
        // 1.1 判断退款单是否存在
        PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
        if (payRefund == null) {
            throw exception(REFUND_NOT_FOUND);
        }
        // 1.2 校验是否可以退款
        Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo());
        PayWalletDO wallet = walletMapper.selectById(walletId);
        Assert.notNull(wallet, "钱包 {} 不存在", walletId);

        // 2. 增加余额
        return addWalletBalance(walletId, String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice);
    }

    /**
     * 校验是否能退款
     *
     * @param refundId 支付退款单 id
     * @param walletPayNo 钱包支付 no
     */
    private Long validateWalletCanRefund(Long refundId, String walletPayNo) {
        // 1. 校验钱包支付交易存在
        PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo);
        if (walletTransaction == null) {
            throw exception(WALLET_TRANSACTION_NOT_FOUND);
        }
        // 2. 校验退款是否存在
        PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction(
                String.valueOf(refundId), PAYMENT_REFUND);
        if (refundTransaction != null) {
            throw exception(WALLET_REFUND_EXIST);
        }
        return walletTransaction.getWalletId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
                                                      PayWalletBizTypeEnum bizType, Integer price) {
        // 1. 获取钱包
        PayWalletDO payWallet = getWallet(walletId);
        if (payWallet == null) {
            log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId);
            throw exception(WALLET_NOT_FOUND);
        }

        // 2. 加锁，更新钱包余额（目的：避免钱包流水的并发更新时，余额变化不连贯）
        return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
            // 2. 扣除余额
            int updateCounts;
            switch (bizType) {
                case PAYMENT: {
                    updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price);
                    break;
                }
                case RECHARGE_REFUND: {
                    updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
                    break;
                }
                default: {
                    // TODO 其它类型待实现
                    throw new UnsupportedOperationException("待实现");
                }
            }
            if (updateCounts == 0) {
                throw exception(WALLET_BALANCE_NOT_ENOUGH);
            }

            // 3. 生成钱包流水
            Integer afterBalance = payWallet.getBalance() - price;
            WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
                    .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
                    .setBizType(bizType.getType()).setTitle(bizType.getDescription());
            return walletTransactionService.createWalletTransaction(bo);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
                                                   PayWalletBizTypeEnum bizType, Integer price) {
        // 1. 获取钱包
        PayWalletDO payWallet = getWallet(walletId);
        if (payWallet == null) {
            log.error("[addWalletBalance][用户钱包({})不存在]", walletId);
            throw exception(WALLET_NOT_FOUND);
        }

        // 2. 加锁，更新钱包余额（目的：避免钱包流水的并发更新时，余额变化不连贯）
        return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
            // 2. 更新钱包金额
            switch (bizType) {
                case PAYMENT_REFUND: { // 退款更新
                    walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
                    break;
                }
                case RECHARGE: { // 充值更新
                    walletMapper.updateWhenRecharge(payWallet.getId(), price);
                    break;
                }
                case UPDATE_BALANCE: // 更新余额
                    walletMapper.updateWhenRecharge(payWallet.getId(), price);
                    break;
                default: {
                    // TODO 其它类型待实现
                    throw new UnsupportedOperationException("待实现");
                }
            }

            // 3. 生成钱包流水
            WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
                    .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
                    .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
            return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
        });
    }

    @Override
    public void freezePrice(Long id, Integer price) {
        int updateCounts = walletMapper.freezePrice(id, price);
        if (updateCounts == 0) {
            throw exception(WALLET_BALANCE_NOT_ENOUGH);
        }
    }

    @Override
    public void unfreezePrice(Long id, Integer price) {
        int updateCounts = walletMapper.unFreezePrice(id, price);
        if (updateCounts == 0) {
            throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH);
        }
    }

}
