package com.hotwj.platform.operationManagement.hourPackagePurchase.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hotwj.platform.common.helper.DriverLoginContextHelper;
import com.hotwj.platform.driverManagement.driver.domain.HotDriver;
import com.hotwj.platform.operationManagement.hourPackage.domain.HotHourPackage;
import com.hotwj.platform.operationManagement.hourPackage.domain.bo.HotHourPackageBo;
import com.hotwj.platform.operationManagement.hourPackage.domain.vo.HotHourPackageVo;
import com.hotwj.platform.operationManagement.hourPackage.mapper.HotHourPackageMapper;
import com.hotwj.platform.operationManagement.hourPackage.service.IHotHourPackageService;
import com.hotwj.platform.operationManagement.hourPackagePurchase.controller.vo.HotHourPackageWalletPurchaseReqVO;
import com.hotwj.platform.operationManagement.hourPackagePurchase.controller.vo.HotHourPackageWalletPurchaseRespVO;
import com.hotwj.platform.operationManagement.hourPackagePurchase.domain.HotHourPackagePurchase;
import com.hotwj.platform.operationManagement.hourPackagePurchase.domain.bo.HotHourPackagePurchaseBo;
import com.hotwj.platform.operationManagement.hourPackagePurchase.domain.vo.HotHourPackagePurchaseVo;
import com.hotwj.platform.operationManagement.hourPackagePurchase.mapper.HotHourPackagePurchaseMapper;
import com.hotwj.platform.operationManagement.hourPackagePurchase.service.IHotHourPackagePurchaseService;
import com.hotwj.platform.operationManagement.hourPurchaseDetail.domain.HotHourPurchaseDetail;
import com.hotwj.platform.operationManagement.hourPurchaseDetail.mapper.HotHourPurchaseDetailMapper;
import com.hotwj.platform.pay.domain.PayApp;
import com.hotwj.platform.pay.domain.PayOrder;
import com.hotwj.platform.pay.domain.PayWallet;
import com.hotwj.platform.pay.domain.PayWalletTransaction;
import com.hotwj.platform.pay.enums.PayOrderBizTypeEnum;
import com.hotwj.platform.pay.enums.PayOrderStatusEnum;
import com.hotwj.platform.pay.mapper.PayAppMapper;
import com.hotwj.platform.pay.mapper.PayOrderMapper;
import com.hotwj.platform.pay.mapper.PayWalletMapper;
import com.hotwj.platform.pay.mapper.PayWalletTransactionMapper;
import com.hotwj.platform.resourceManagement.company.service.ISysUserLoginPortService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 学时套餐购买记录Service业务层处理
 *
 * @author shihongwei
 * @date 2026-04-28
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class HotHourPackagePurchaseServiceImpl implements IHotHourPackagePurchaseService {

    private static final Long PERSONAL_WALLET_TYPE = 1L;
    private static final Long COMPANY_WALLET_TYPE = 2L;
    private static final Long PAY_METHOD_WALLET = 4L;
    private static final Long PURCHASE_PAY_STATUS_PAID = 2L;
    private static final Long WALLET_FREEZE_STATUS_NORMAL = 0L;
    private static final Long WALLET_FREEZE_STATUS_FROZEN = 1L;
    private static final Long WALLET_BIZ_TYPE_HOUR_PACKAGE = 3L;
    private static final Long PURCHASE_DETAIL_STATUS_EFFECTIVE = 1L;

    private final HotHourPackagePurchaseMapper baseMapper;
    private final IHotHourPackageService hotHourPackageService;
    private final HotHourPackageMapper hotHourPackageMapper;
    private final HotHourPurchaseDetailMapper hotHourPurchaseDetailMapper;
    private final PayWalletMapper payWalletMapper;
    private final PayWalletTransactionMapper payWalletTransactionMapper;
    private final PayOrderMapper payOrderMapper;
    private final PayAppMapper payAppMapper;

    /**
     * 查询学时套餐购买记录
     *
     * @param id 主键
     * @return 学时套餐购买记录
     */
    @Override
    public HotHourPackagePurchaseVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询学时套餐购买记录列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 学时套餐购买记录分页列表
     */
    @Override
    public TableDataInfo<HotHourPackagePurchaseVo> queryPageList(HotHourPackagePurchaseBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<HotHourPackagePurchase> lqw = buildQueryWrapper(bo);
        Page<HotHourPackagePurchaseVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    @Override
    public TableDataInfo<HotHourPackageVo> queryAvailablePageList(HotHourPackageBo bo, PageQuery pageQuery) {
        assertSupportedBuyer(LoginHelper.getLoginUser());
        HotHourPackageBo queryBo = bo == null ? new HotHourPackageBo() : bo;
        queryBo.setStatus(1L);
        return hotHourPackageService.queryPageList(queryBo, pageQuery);
    }

    /**
     * 查询符合条件的学时套餐购买记录列表
     *
     * @param bo 查询条件
     * @return 学时套餐购买记录列表
     */
    @Override
    public List<HotHourPackagePurchaseVo> queryList(HotHourPackagePurchaseBo bo) {
        LambdaQueryWrapper<HotHourPackagePurchase> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    @Override
    public TableDataInfo<HotHourPackagePurchaseVo> queryCurrentUserPageList(HotHourPackagePurchaseBo bo, PageQuery pageQuery) {
        LoginUser loginUser = assertValidLoginUser(LoginHelper.getLoginUser());
        HotHourPackagePurchaseBo queryBo = bo == null ? new HotHourPackagePurchaseBo() : bo;
        if (isDriverPort(loginUser)) {
            queryBo.setUserId(loginUser.getUserId());
            queryBo.setWalletType(PERSONAL_WALLET_TYPE);
        } else {
            queryBo.setCompanyId(loginUser.getCompanyId());
            queryBo.setWalletType(COMPANY_WALLET_TYPE);
        }
        return queryPageList(queryBo, pageQuery);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HotHourPackageWalletPurchaseRespVO purchaseByWallet(HotHourPackageWalletPurchaseReqVO reqVO) {
        LoginUser loginUser = assertSupportedBuyer(LoginHelper.getLoginUser());
        Long quantity = reqVO.getQuantity() == null ? 1L : reqVO.getQuantity();
        if (quantity <= 0) {
            throw new ServiceException("购买数量必须大于0");
        }

        HotHourPackage hourPackage = hotHourPackageMapper.selectById(reqVO.getPackageId());
        if (hourPackage == null) {
            throw new ServiceException("学时套餐不存在");
        }
        if (!Objects.equals(hourPackage.getStatus(), 1L)) {
            throw new ServiceException("学时套餐未启用，暂不可购买");
        }
        if (hourPackage.getHourCount() == null || hourPackage.getHourCount() <= 0) {
            throw new ServiceException("学时套餐学时配置异常");
        }

        PurchaseContext context = resolvePurchaseContext(loginUser);
        PayWallet wallet = getWallet(context);
        ensureWalletAvailable(wallet);

        BigDecimal effectivePrice = resolveEffectivePrice(hourPackage);
        BigDecimal packageUnitPrice = resolvePackageUnitPrice(hourPackage, effectivePrice);
        BigDecimal totalAmount = effectivePrice.multiply(BigDecimal.valueOf(quantity)).setScale(2, RoundingMode.HALF_UP);
        Long totalHours = hourPackage.getHourCount() * quantity;
        BigDecimal balance = defaultAmount(wallet.getBalance());
        if (balance.compareTo(totalAmount) < 0) {
            throw new ServiceException("钱包余额不足");
        }

        Date now = new Date();
        String orderNo = buildNo("HPO");

        HotHourPackagePurchase purchase = new HotHourPackagePurchase();
        purchase.setCompanyId(loginUser.getCompanyId());
        purchase.setAccountId(wallet.getId());
        purchase.setUserId(loginUser.getUserId());
        purchase.setOrderNo(orderNo);
        purchase.setPackageId(hourPackage.getId());
        purchase.setPackageName(hourPackage.getPackageName());
        purchase.setHourType(hourPackage.getHourType());
        purchase.setHourCount(hourPackage.getHourCount());
        purchase.setOriginPrice(normalizeMoney(hourPackage.getOriginPrice()));
        purchase.setPackagePrice(normalizeNullableMoney(hourPackage.getPackagePrice()));
        purchase.setUnitPrice(packageUnitPrice);
        purchase.setQuantity(quantity);
        purchase.setTotalHours(totalHours);
        purchase.setTotalAmount(totalAmount);
        purchase.setPayMethod(PAY_METHOD_WALLET);
        purchase.setWalletType(context.walletType());
        purchase.setWalletId(wallet.getId());
        purchase.setPayStatus(PURCHASE_PAY_STATUS_PAID);
        purchase.setOrderCreateTime(now);
        purchase.setPayTime(now);
        purchase.setPackageSnapshot(buildPackageSnapshot(hourPackage));
        purchase.setRemark(reqVO.getRemark());
        if (baseMapper.insert(purchase) <= 0) {
            throw new ServiceException("创建学时套餐购买记录失败");
        }

        PayOrder payOrder = buildSuccessPayOrder(purchase, totalAmount, now, hourPackage.getPackageName());
        if (payOrderMapper.insert(payOrder) <= 0) {
            throw new ServiceException("创建支付订单失败");
        }

        HotHourPackagePurchase purchaseUpdate = new HotHourPackagePurchase();
        purchaseUpdate.setId(purchase.getId());
        purchaseUpdate.setOrderId(payOrder.getId());
        if (baseMapper.updateById(purchaseUpdate) <= 0) {
            throw new ServiceException("回写支付订单失败");
        }

        BigDecimal newBalance = balance.subtract(totalAmount).setScale(2, RoundingMode.HALF_UP);
        if (!deductWalletBalance(wallet, balance, totalAmount, newBalance)) {
            throw new ServiceException("钱包余额已变更，请刷新后重试");
        }

        PayWalletTransaction walletTransaction = buildWalletTransaction(wallet.getId(), orderNo, totalAmount, newBalance, reqVO.getRemark());
        if (payWalletTransactionMapper.insert(walletTransaction) <= 0) {
            throw new ServiceException("写入钱包流水失败");
        }

        HotHourPackagePurchase transactionUpdate = new HotHourPackagePurchase();
        transactionUpdate.setId(purchase.getId());
        transactionUpdate.setWalletTransactionId(walletTransaction.getId());
        transactionUpdate.setWalletTransactionNo(walletTransaction.getNo());
        if (baseMapper.updateById(transactionUpdate) <= 0) {
            throw new ServiceException("回写钱包流水失败");
        }

        HotHourPurchaseDetail purchaseDetail = buildPurchaseDetail(purchase, payOrder, wallet, hourPackage, context, quantity, totalHours, reqVO.getRemark());
        if (hotHourPurchaseDetailMapper.insert(purchaseDetail) <= 0) {
            throw new ServiceException("创建课时购买明细失败");
        }

        HotHourPackageWalletPurchaseRespVO respVO = new HotHourPackageWalletPurchaseRespVO();
        respVO.setPurchaseId(purchase.getId());
        respVO.setOrderId(payOrder.getId());
        respVO.setOrderNo(orderNo);
        respVO.setPayOrderId(payOrder.getId());
        respVO.setPayOrderNo(payOrder.getNo());
        respVO.setWalletType(context.walletType());
        respVO.setWalletId(wallet.getId());
        respVO.setWalletTransactionId(walletTransaction.getId());
        respVO.setWalletTransactionNo(walletTransaction.getNo());
        respVO.setTotalHours(totalHours);
        respVO.setTotalAmount(totalAmount);
        respVO.setWalletBalance(newBalance);
        return respVO;
    }

    private LambdaQueryWrapper<HotHourPackagePurchase> buildQueryWrapper(HotHourPackagePurchaseBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<HotHourPackagePurchase> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(HotHourPackagePurchase::getId);
        lqw.eq(bo.getCompanyId() != null, HotHourPackagePurchase::getCompanyId, bo.getCompanyId());
        lqw.eq(bo.getAccountId() != null, HotHourPackagePurchase::getAccountId, bo.getAccountId());
        lqw.eq(bo.getUserId() != null, HotHourPackagePurchase::getUserId, bo.getUserId());
        lqw.eq(bo.getOrderId() != null, HotHourPackagePurchase::getOrderId, bo.getOrderId());
        lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), HotHourPackagePurchase::getOrderNo, bo.getOrderNo());
        lqw.eq(bo.getPackageId() != null, HotHourPackagePurchase::getPackageId, bo.getPackageId());
        lqw.like(StringUtils.isNotBlank(bo.getPackageName()), HotHourPackagePurchase::getPackageName, bo.getPackageName());
        lqw.eq(bo.getHourType() != null, HotHourPackagePurchase::getHourType, bo.getHourType());
        lqw.eq(bo.getHourCount() != null, HotHourPackagePurchase::getHourCount, bo.getHourCount());
        lqw.eq(bo.getOriginPrice() != null, HotHourPackagePurchase::getOriginPrice, bo.getOriginPrice());
        lqw.eq(bo.getPackagePrice() != null, HotHourPackagePurchase::getPackagePrice, bo.getPackagePrice());
        lqw.eq(bo.getUnitPrice() != null, HotHourPackagePurchase::getUnitPrice, bo.getUnitPrice());
        lqw.eq(bo.getQuantity() != null, HotHourPackagePurchase::getQuantity, bo.getQuantity());
        lqw.eq(bo.getTotalHours() != null, HotHourPackagePurchase::getTotalHours, bo.getTotalHours());
        lqw.eq(bo.getTotalAmount() != null, HotHourPackagePurchase::getTotalAmount, bo.getTotalAmount());
        lqw.eq(bo.getPayMethod() != null, HotHourPackagePurchase::getPayMethod, bo.getPayMethod());
        lqw.eq(bo.getWalletType() != null, HotHourPackagePurchase::getWalletType, bo.getWalletType());
        lqw.eq(bo.getWalletId() != null, HotHourPackagePurchase::getWalletId, bo.getWalletId());
        lqw.eq(bo.getWalletTransactionId() != null, HotHourPackagePurchase::getWalletTransactionId, bo.getWalletTransactionId());
        lqw.eq(StringUtils.isNotBlank(bo.getWalletTransactionNo()), HotHourPackagePurchase::getWalletTransactionNo, bo.getWalletTransactionNo());
        lqw.eq(bo.getPayStatus() != null, HotHourPackagePurchase::getPayStatus, bo.getPayStatus());
        lqw.eq(bo.getOrderCreateTime() != null, HotHourPackagePurchase::getOrderCreateTime, bo.getOrderCreateTime());
        lqw.eq(bo.getPayTime() != null, HotHourPackagePurchase::getPayTime, bo.getPayTime());
        lqw.like(StringUtils.isNotBlank(bo.getPackageSnapshot()), HotHourPackagePurchase::getPackageSnapshot, bo.getPackageSnapshot());
        lqw.like(StringUtils.isNotBlank(bo.getCreateByName()), HotHourPackagePurchase::getCreateByName, bo.getCreateByName());
        lqw.like(StringUtils.isNotBlank(bo.getUpdateByName()), HotHourPackagePurchase::getUpdateByName, bo.getUpdateByName());
        lqw.eq(bo.getIsDeleted() != null, HotHourPackagePurchase::getIsDeleted, bo.getIsDeleted());
        return lqw;
    }

    private LoginUser assertSupportedBuyer(LoginUser loginUser) {
        loginUser = assertValidLoginUser(loginUser);
        if (isEnterprisePort(loginUser) || isDriverPort(loginUser)) {
            return loginUser;
        }
        throw new ServiceException("当前登录端口不支持购买学时套餐");
    }

    private LoginUser assertValidLoginUser(LoginUser loginUser) {
        if (loginUser == null || loginUser.getUserId() == null || loginUser.getCompanyId() == null) {
            throw new ServiceException("登录状态异常，请重新登录后再操作");
        }
        return loginUser;
    }

    private boolean isEnterprisePort(LoginUser loginUser) {
        return loginUser != null && StringUtils.equals(loginUser.getLoginPort(), ISysUserLoginPortService.ENTERPRISE_PORT);
    }

    private boolean isDriverPort(LoginUser loginUser) {
        return loginUser != null && StringUtils.equals(loginUser.getLoginPort(), ISysUserLoginPortService.DRIVER_PORT);
    }

    private PurchaseContext resolvePurchaseContext(LoginUser loginUser) {
        if (isEnterprisePort(loginUser)) {
            return new PurchaseContext(
                    COMPANY_WALLET_TYPE,
                    loginUser.getCompanyId(),
                    StringUtils.defaultIfBlank(loginUser.getNickname(), loginUser.getUsername()),
                    loginUser.getCompanyId(),
                    loginUser.getCompanyName()
            );
        }
        HotDriver driver = DriverLoginContextHelper.getCurrentDriver();
        return new PurchaseContext(
                PERSONAL_WALLET_TYPE,
                loginUser.getUserId(),
                StringUtils.defaultIfBlank(driver.getName(), StringUtils.defaultIfBlank(loginUser.getNickname(), loginUser.getUsername())),
                loginUser.getCompanyId(),
                loginUser.getCompanyName()
        );
    }

    private PayWallet getWallet(PurchaseContext context) {
        PayWallet wallet = payWalletMapper.selectOne(Wrappers.<PayWallet>lambdaQuery()
                .eq(PayWallet::getUserId, context.walletOwnerId())
                .eq(PayWallet::getWalletType, context.walletType())
                .last("limit 1"));
        if (wallet == null) {
            throw new ServiceException("钱包不存在，请先充值后再购买");
        }
        return wallet;
    }

    private void ensureWalletAvailable(PayWallet wallet) {
        if (wallet == null) {
            throw new ServiceException("钱包不存在");
        }
        if (Objects.equals(wallet.getFreezeStatus(), WALLET_FREEZE_STATUS_FROZEN)) {
            throw new ServiceException("钱包已冻结，无法购买");
        }
        if (!Objects.equals(wallet.getFreezeStatus(), WALLET_FREEZE_STATUS_NORMAL)) {
            throw new ServiceException("钱包状态异常，无法购买");
        }
    }

    private BigDecimal resolveEffectivePrice(HotHourPackage hourPackage) {
        BigDecimal price = hourPackage.getPackagePrice() != null ? hourPackage.getPackagePrice() : hourPackage.getOriginPrice();
        if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("学时套餐价格配置异常");
        }
        return normalizeMoney(price);
    }

    private BigDecimal resolvePackageUnitPrice(HotHourPackage hourPackage, BigDecimal effectivePrice) {
        if (hourPackage.getUnitPrice() != null && hourPackage.getUnitPrice().compareTo(BigDecimal.ZERO) > 0) {
            return normalizeMoney(hourPackage.getUnitPrice());
        }
        return effectivePrice.divide(BigDecimal.valueOf(hourPackage.getHourCount()), 2, RoundingMode.HALF_UP);
    }

    private PayOrder buildSuccessPayOrder(HotHourPackagePurchase purchase, BigDecimal totalAmount, Date now, String packageName) {
        PayApp payApp = getActivePayApp();
        PayOrder payOrder = new PayOrder();
        payOrder.setAppId(payApp.getId());
        payOrder.setMerchantOrderNo(purchase.getOrderNo());
        payOrder.setBizOrderType(PayOrderBizTypeEnum.HOUR_PACKAGE.getCode());
        payOrder.setBizOrderId(purchase.getId());
        payOrder.setSubject("课时套餐购买");
        payOrder.setBody("钱包购买课时套餐：" + packageName);
        // 钱包直购为同步成功单，不走第三方支付回调，但仍需满足支付订单表的非空约束。
        payOrder.setNotifyUrl("");
        payOrder.setReturnUrl("");
        payOrder.setUserIp(StringUtils.isNotBlank(ServletUtils.getClientIP()) ? ServletUtils.getClientIP() : "127.0.0.1");
        payOrder.setPrice(totalAmount);
        payOrder.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
        payOrder.setSuccessTime(now);
        payOrder.setNo(buildNo("PO"));
        return payOrder;
    }

    private boolean deductWalletBalance(PayWallet wallet, BigDecimal oldBalance, BigDecimal expenseAmount, BigDecimal newBalance) {
        PayWallet updateWallet = new PayWallet();
        updateWallet.setBalance(newBalance);
        updateWallet.setTotalExpense(defaultAmount(wallet.getTotalExpense()).add(expenseAmount).setScale(2, RoundingMode.HALF_UP));
        return payWalletMapper.update(updateWallet, Wrappers.<PayWallet>lambdaUpdate()
                .eq(PayWallet::getId, wallet.getId())
                .eq(PayWallet::getFreezeStatus, WALLET_FREEZE_STATUS_NORMAL)
                .eq(PayWallet::getBalance, oldBalance)) > 0;
    }

    private PayWalletTransaction buildWalletTransaction(Long walletId, String bizId, BigDecimal totalAmount, BigDecimal balance, String remark) {
        PayWalletTransaction walletTransaction = new PayWalletTransaction();
        walletTransaction.setWalletId(walletId);
        walletTransaction.setBizType(WALLET_BIZ_TYPE_HOUR_PACKAGE);
        walletTransaction.setBizId(bizId);
        walletTransaction.setNo(buildNo("WT"));
        walletTransaction.setTitle("购买课时套餐");
        walletTransaction.setPrice(totalAmount.negate().setScale(2, RoundingMode.HALF_UP));
        walletTransaction.setBalance(balance);
        walletTransaction.setRemark(remark);
        return walletTransaction;
    }

    private HotHourPurchaseDetail buildPurchaseDetail(HotHourPackagePurchase purchase, PayOrder payOrder, PayWallet wallet,
                                                      HotHourPackage hourPackage, PurchaseContext context, Long quantity,
                                                      Long totalHours, String remark) {
        HotHourPurchaseDetail detail = new HotHourPurchaseDetail();
        detail.setCompanyId(purchase.getCompanyId());
        detail.setAccountId(wallet.getId());
        detail.setUserId(purchase.getUserId());
        detail.setBuyerName(context.buyerName());
        detail.setBuyerCompanyId(context.buyerCompanyId());
        detail.setBuyerCompanyName(context.buyerCompanyName());
        detail.setPurchaseId(purchase.getId());
        detail.setOrderId(payOrder.getId());
        detail.setOrderNo(purchase.getOrderNo());
        detail.setPackageId(hourPackage.getId());
        detail.setPackageName(hourPackage.getPackageName());
        detail.setApplicableTypes(hourPackage.getApplicableTypes());
        detail.setPurchaseCount(quantity);
        detail.setTotalHours(totalHours);
        detail.setUsedHours(0L);
        detail.setRemainingHours(totalHours);
        detail.setWalletType(purchase.getWalletType());
        detail.setWalletId(wallet.getId());
        detail.setVersion(0L);
        detail.setStatus(PURCHASE_DETAIL_STATUS_EFFECTIVE);
        detail.setRemark(remark);
        return detail;
    }

    private String buildPackageSnapshot(HotHourPackage hourPackage) {
        Map<String, Object> snapshot = new LinkedHashMap<>();
        snapshot.put("packageId", hourPackage.getId());
        snapshot.put("packageCode", hourPackage.getPackageCode());
        snapshot.put("packageName", hourPackage.getPackageName());
        snapshot.put("hourType", hourPackage.getHourType());
        snapshot.put("hourCount", hourPackage.getHourCount());
        snapshot.put("originPrice", normalizeNullableMoney(hourPackage.getOriginPrice()));
        snapshot.put("packagePrice", normalizeNullableMoney(hourPackage.getPackagePrice()));
        snapshot.put("unitPrice", normalizeNullableMoney(hourPackage.getUnitPrice()));
        snapshot.put("applicableTypes", hourPackage.getApplicableTypes());
        snapshot.put("packageDesc", hourPackage.getPackageDesc());
        return JsonUtils.toJsonString(snapshot);
    }

    private PayApp getActivePayApp() {
        PayApp app = payAppMapper.selectOne(Wrappers.<PayApp>lambdaQuery()
                .eq(PayApp::getStatus, 1L)
                .orderByAsc(PayApp::getId)
                .last("limit 1"));
        if (app == null) {
            throw new ServiceException("未配置可用支付应用，请先维护支付应用");
        }
        return app;
    }

    private BigDecimal defaultAmount(BigDecimal amount) {
        return amount == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : amount.setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal normalizeMoney(BigDecimal amount) {
        if (amount == null) {
            throw new ServiceException("金额不能为空");
        }
        return amount.setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal normalizeNullableMoney(BigDecimal amount) {
        return amount == null ? null : amount.setScale(2, RoundingMode.HALF_UP);
    }

    private String buildNo(String prefix) {
        return prefix + IdUtil.getSnowflakeNextIdStr();
    }

    private record PurchaseContext(Long walletType, Long walletOwnerId, String buyerName,
                                   Long buyerCompanyId, String buyerCompanyName) {
    }

    /**
     * 新增学时套餐购买记录
     *
     * @param bo 学时套餐购买记录
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(HotHourPackagePurchaseBo bo) {
        HotHourPackagePurchase add = MapstructUtils.convert(bo, HotHourPackagePurchase.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改学时套餐购买记录
     *
     * @param bo 学时套餐购买记录
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(HotHourPackagePurchaseBo bo) {
        HotHourPackagePurchase update = MapstructUtils.convert(bo, HotHourPackagePurchase.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(HotHourPackagePurchase entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除学时套餐购买记录信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
}
