package com.ruoyi.ss.transactionBill.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.PageHelper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.enums.ServiceCode;
import com.ruoyi.common.enums.WithdrawServiceType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.iot.domain.response.CommandResponse;
import com.ruoyi.iot.service.IotService;
import com.ruoyi.ss.account.domain.AccountVO;
import com.ruoyi.ss.bonus.domain.Bonus;
import com.ruoyi.ss.bonus.domain.BonusQuery;
import com.ruoyi.ss.bonus.domain.BonusVO;
import com.ruoyi.ss.bonus.domain.enums.BonusArrivalType;
import com.ruoyi.ss.bonus.domain.enums.BonusStatus;
import com.ruoyi.ss.bonus.service.BonusConverter;
import com.ruoyi.ss.bonus.service.BonusService;
import com.ruoyi.ss.channelWithdraw.domain.ChannelWithdrawVO;
import com.ruoyi.ss.channelWithdraw.service.ChannelWithdrawService;
import com.ruoyi.dashboard.domain.vo.BillCountVo;
import com.ruoyi.ss.device.domain.vo.DeviceVO;
import com.ruoyi.ss.device.service.DeviceService;
import com.ruoyi.ss.payBill.domain.PayBillQuery;
import com.ruoyi.ss.payBill.domain.PayBillVO;
import com.ruoyi.ss.payBill.domain.dto.PayBillRefundDTO;
import com.ruoyi.ss.payBill.domain.enums.PayBillBstType;
import com.ruoyi.ss.payBill.domain.vo.DoPayVO;
import com.ruoyi.ss.payBill.service.PayBillConverter;
import com.ruoyi.ss.payBill.service.PayBillService;
import com.ruoyi.ss.receiveBill.domain.ReceiveBill;
import com.ruoyi.ss.receiveBill.domain.enums.ReceiveBillStatus;
import com.ruoyi.ss.receiveBill.domain.enums.ReceiveBillType;
import com.ruoyi.ss.receiveBill.service.ReceiveBillService;
import com.ruoyi.ss.record.time.domain.enums.RecordTimeType;
import com.ruoyi.ss.record.time.service.IRecordTimeService;
import com.ruoyi.ss.record.time.service.RecordTimeConverter;
import com.ruoyi.ss.recordBalance.domain.enums.RecordBalanceBstType;
import com.ruoyi.ss.risk.service.RiskService;
import com.ruoyi.ss.store.domain.StoreVo;
import com.ruoyi.ss.suit.domain.SuitVO;
import com.ruoyi.ss.suit.domain.enums.SuitFeeMode;
import com.ruoyi.ss.suit.domain.enums.SuitFeeType;
import com.ruoyi.ss.suit.domain.enums.SuitTimeUnit;
import com.ruoyi.ss.transactionBill.domain.TransactionBill;
import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery;
import com.ruoyi.ss.transactionBill.domain.bo.*;
import com.ruoyi.ss.transactionBill.domain.dto.*;
import com.ruoyi.ss.transactionBill.domain.vo.*;
import com.ruoyi.ss.transactionBill.domain.enums.*;
import com.ruoyi.ss.transactionBill.mapper.TransactionBillMapper;
import com.ruoyi.ss.transactionBill.service.*;
import com.ruoyi.ss.transfer.domain.TransferVO;
import com.ruoyi.ss.transfer.interfaces.AfterTransfer;
import com.ruoyi.ss.transfer.service.TransferConverter;
import com.ruoyi.ss.transfer.service.TransferService;
import com.ruoyi.ss.user.domain.SmUserVO;
import com.ruoyi.ss.user.service.ISmUserService;
import com.ruoyi.common.pay.wx.service.WxPayService;
import com.ruoyi.system.domain.enums.config.ConfigKey;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.task.bill.BillDelayedManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 充值记录Service业务层处理
 */
@Service("transactionBillService")
@Slf4j
public class TransactionBillServiceImpl implements TransactionBillService, AfterTransfer {
    @Autowired
    private TransactionBillMapper transactionBillMapper;

    @Autowired
    private ChannelWithdrawService channelWithdrawService;

    @Autowired
    private TransactionBillValidator transactionBillValidator;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private ISmUserService userService;

    @Autowired
    private TransferService transferService;

    @Autowired
    private TransferConverter transferConverter;

    @Autowired
    private BillDelayedManager billDelayedManager;


    @Autowired
    private RedisLock redisLock;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    @Value("${sm.withdraw.password.enable}")
    private Boolean smWithdrawPasswordEnable;   // 是否开启提现密码

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private IRecordTimeService recordTimeService;

    @Autowired
    private RecordTimeConverter recordTimeConverter;

    @Autowired
    private PayBillService payBillService;

    @Autowired
    private PayBillConverter payBillConverter;

    @Autowired
    private IotService iotService;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private TransactionBillConverter transactionBillConverter;

    @Autowired
    private WithdrawValidator withdrawValidator;

    @Autowired
    private BonusConverter bonusConverter;

    @Autowired
    private BonusService bonusService;

    @Autowired
    private TransactionAssembler transactionAssembler;

    @Autowired
    private ReceiveBillService receiveBillService;

    @Autowired
    private RiskService riskService;

    /**
     * 查询充值记录
     *
     * @param billId 充值记录主键
     * @return 充值记录
     */
    @Override
    public TransactionBillVO selectSmTransactionBillByBillId(Long billId)
    {
        return transactionBillMapper.selectSmTransactionBillByBillId(billId);
    }

    /**
     * 查询充值记录列表
     *
     * @param smTransactionBillQuery 充值记录查询条件
     * @return 充值记录
     */
    @Override
    public List<TransactionBillVO> selectSmTransactionBillList(TransactionBillQuery smTransactionBillQuery)
    {
        return transactionBillMapper.selectSmTransactionBillList(smTransactionBillQuery);
    }

    /**
     * 新增充值记录
     *
     * @param transactionBill 充值记录
     * @return 结果
     */
    @Override
    public int insertSmTransactionBill(TransactionBill transactionBill)
    {
        transactionBill.setCreateTime(DateUtils.getNowDate());
        transactionBill.setBillNo(String.valueOf(SnowFlakeUtil.newId()));
        return transactionBillMapper.insertSmTransactionBill(transactionBill);
    }

    /**
     * 修改充值记录
     *
     * @param transactionBill 充值记录
     * @return 结果
     */
    @Override
    public int updateSmTransactionBill(TransactionBill transactionBill)
    {
        return transactionBillMapper.updateSmTransactionBill(transactionBill);
    }

    /**
     * 批量删除充值记录
     *
     * @param billIds 需要删除的充值记录主键
     * @return 结果
     */
    @Override
    public int deleteSmTransactionBillByBillIds(Long[] billIds)
    {
        return transactionBillMapper.deleteSmTransactionBillByBillIds(billIds);
    }

    /**
     * 删除充值记录信息
     *
     * @param billId 充值记录主键
     * @return 结果
     */
    @Override
    public int deleteSmTransactionBillByBillId(Long billId)
    {
        return transactionBillMapper.deleteSmTransactionBillByBillId(billId);
    }

    /**
     * 查询账单统计
     * @param dto
     * @return
     */
    @Override
    public List<BillCountVo> selectCount(TransactionBillQuery dto) {
        return transactionBillMapper.selectCount(dto);
    }

    @Override
    public String addOrder(RechargeBO bo) {
        ServiceUtil.assertion(bo == null || bo.getDevice() == null || bo.getDevice().getDeviceId() == null, "设备不存在");
        Long deviceId = bo.getDevice().getDeviceId();

        // 加锁
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.ADD_RECHARGE_ORDER, deviceId), "当前使用该设备人数较多，请稍后再试");
        try {
            // 拉取设备信息
            deviceService.pullDeviceInfo(Collections.singletonList(bo.getDevice().getDeviceId()));

            // 生成订单
            TransactionBill order = parseToOrder(bo);
            List<Bonus> bonusList = bo.getDevice().getBonusList();

            transactionTemplate.execute(status -> {
                // 收取月费
                int monthFee = receiveBillService.genBillByMonthAndPay(bo.getMch(), bo.getDevice());
                ServiceUtil.assertion(monthFee != 1, "月费收取失败，请联系商户处理");

                // 新增订单
                int insert = this.insertSmTransactionBill(order);
                ServiceUtil.assertion(insert != 1, "下单失败");

                // 插入分成列表
                for (Bonus bonus : bonusList) {
                    bonus.setBillId(order.getBillId());
                    bonus.setBillNo(order.getBillNo());
                }
                int bonusInsert = bonusService.batchInsert(bonusList);
                ServiceUtil.assertion(bonusInsert != bonusList.size(), "创建分成失败");

                return insert;
            });

            // 加入延时队列，超时取消
            billDelayedManager.add(order.getBillNo(), order.getExpireTime().getTime());
            return order.getBillNo();
        } finally {
            // 解锁
            redisLock.unlock(RedisLockKey.ADD_RECHARGE_ORDER, deviceId);
        }
    }

    // 处理充值服务费
    private void handleRechargeService(RechargePayBO bo) {

//        TransactionBillVO order = bo.getOrder();
//
//        List<Bonus> bonusList = bonusConverter.toPo(bo);

        // 获取商户的服务费配置
//        UserRechargeServiceVO userRechargeService = this.getMchRechargeService(bo.getChannel(), bo.getMch(), bo.getDevice());
//        String serviceType = userRechargeService.getServiceType();  // 服务费类型
//        BigDecimal serviceRate = userRechargeService.getServiceRate();   // 服务费

        // 若收取月费
//        if (ServiceType.MONTH.getType().equals(serviceType)) {
//            // 商户的分成
//            Bonus mchBonus = bonusList.stream()
//                    .filter(item -> item.getArrivalId().equals(bo.getMch().getUserId())
//                            && BonusArrivalType.userList().contains(item.getArrivalType()))
//                    .findFirst().orElse(null);
//            // 服务费 = 0
//            order.setServiceCharge(BigDecimal.ZERO);
//            // 商户到账金额 = 交易金额
//            order.setArrivalAmount(order.getMoney());
//            int count = receiveBillService.genBillByMonthAndPay(bo.getMch(), bo.getDevice(), bo.getOrder().getArrivalAmount() , serviceRate);
//            ServiceUtil.assertion(count == 0, "商户出账失败，请刷新后重试");
//        }
        // 设备订单比例
//        else if(ServiceType.PERCENT.getType().equals(serviceType)){
//
//            bonusConverter.toPo(bo);
//
//            // 服务费
//            BigDecimal serviceCharge = serviceRate.multiply(order.getMoney()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
//            // 非分时订单，处理最低服务费
//            if (!SuitFeeType.timingList().contains(order.getSuitFeeType())) {
//                BigDecimal minService = sysConfigService.getBigDecimal(ConfigKey.RECHARGE_MIN_SERVICE);
////                ServiceUtil.assertion(order.getMoney().compareTo(minService) < 0, "当前套餐金额小于最低服务费，请联系商户处理");
//                // 当服务费不满足最低服务费时，设置为最低服务费
//                if (serviceCharge.compareTo(minService) < 0) {
//                    serviceCharge = minService;
//                }
//                // 当金额不满足服务费时，将全收
//                if (order.getMoney().compareTo(serviceCharge) < 0) {
//                    serviceCharge = order.getMoney();
//                }
//            }
//            order.setServiceCharge(serviceCharge);
//
//            // 商户最终到账的金额 = 交易金额 - 服务费
//            order.setArrivalAmount(order.getMoney().subtract(serviceCharge));
//        } else {
//            throw new ServiceException("未知的服务费类型");
//        }

    }

    @Override
    public int fix(Long billId) {
        TransactionBillVO bill = selectSmTransactionBillByBillId(billId);
        return fix(bill);
    }

    @Override
    public int fixAll() {
        TransactionBillQuery query  = new TransactionBillQuery();
        query.setStatusList(TransactionBillStatus.payedOrder());
        query.setVersion(1);
        List<TransactionBillVO> list = selectSmTransactionBillList(query);
        if (CollectionUtils.isEmptyElement(list)) {
            return 0;
        }

        for (TransactionBillVO bill : list) {
            try {
                this.fix(bill);
            } catch (Exception e) {
                log.error("修复订单数据失败：{}, {}", bill.getBillNo(), e.getMessage());
            }
        }

        return 1;
    }

    @Override
    public int receiveMchShowMobileAmount(TransactionBillVO bill) {
        ServiceUtil.assertion(bill == null, "订单不存在");
        // 若未开启该功能，则跳过
        if (bill.getMchShowMobile() == null || !bill.getMchShowMobile()) {
            return 1;
        }

        Integer result = transactionTemplate.execute(status -> {
            // 创建应收账
            ReceiveBill receive = new ReceiveBill();
            receive.setUserId(bill.getMchId());
            receive.setDeviceId(bill.getDeviceId());
            receive.setType(ReceiveBillType.MOBILE.getType());
            receive.setStatus(ReceiveBillStatus.PAID.getStatus());
            receive.setBillTime(LocalDateTime.now());
            receive.setAmount(bill.getMchShowMobilePrice());
            receive.setDescription("商户获取用户手机号订单：" + bill.getBillNo());
            receive.setReceivedAmount(bill.getMchShowMobilePrice());
            int insert = receiveBillService.insertReceiveBill(receive);
            ServiceUtil.assertion(insert != 1, "创建收款失败");

            // 收款
            userService.subtractBalance(
                    receive.getUserId(),
                    receive.getAmount(),
                    false,
                    receive.getDescription(),
                    RecordBalanceBstType.RECEIVABLE,
                    receive.getBillId()
            );

            // 修改订单缴费状态
            TransactionBill data = new TransactionBill();
            data.setMchShowMobileStatus(RechargeBillShowMobileStatus.PAID.getStatus());
            TransactionBillQuery query = new TransactionBillQuery();
            query.setBillId(bill.getBillId());
            query.setMchShowMobileStatus(RechargeBillShowMobileStatus.UNPAID.getStatus());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改订单缴费状态失败，请刷新后重试");

            return update;
        });

        return result == null ? 0 :result;
    }

    @Override
    public List<TransactionBillVO> selectStaffDeviceList(TransactionBillQuery query) {
        query.setInStaff(true);
        return this.selectSmTransactionBillList(query);
    }

    @Override
    public BigDecimal selectSumOfMoneyWithoutRefund(TransactionBillQuery query) {
        return transactionBillMapper.selectSumOfMoneyWithoutRefund(query);
    }

    @Override
    public List<TransactionDailyAmountVO> selectDailyAmount(TransactionBillQuery query) {
        return transactionBillMapper.selectDailyAmount(query);
    }

    @Override
    public int batchUpdateSuitEndTime(List<Long> billIds, LocalDateTime time) {
        return transactionBillMapper.batchUpdateSuitEndTime(billIds, time);
    }

    @Override
    public List<JSONObject> selectCountGroupBy(TransactionBillQuery query, String groupBy) {
        return transactionBillMapper.selectCountGroupBy(query, groupBy);
    }

    private int fix(TransactionBillVO bill) {
        if (bill == null || bill.getBillId() == null) {
            log.warn("修复订单数据失败：订单不存在或订单ID为空");
            return 0;
        }
        // 仅可以修复旧订单
        ServiceUtil.assertion(bill.getVersion() == null || bill.getVersion() != 1, "该订单版本无法修复");
        ServiceUtil.assertion(!TransactionBillStatus.payedOrder().contains(bill.getStatus()), "订单未支付，无法修复");

        // 获取订单分成数据，判断是否已有分成数据
        BonusQuery query = new BonusQuery();
        query.setBillId(bill.getBillId());
        int bonusCount = bonusService.selectCount(query);
        ServiceUtil.assertion(bonusCount > 0, "订单已存在分成数据，无法重复修复");

        // 根据订单生成新的分成数据
        List<Bonus> bonusList = bonusConverter.toPo(bill);
        ServiceUtil.assertion(CollectionUtils.isEmptyElement(bonusList), "订单生成分成数据失败");

        // 插入数据
        Integer result = transactionTemplate.execute(status -> {
            int insert = bonusService.batchInsert(bonusList);
            ServiceUtil.assertion(insert != bonusList.size(), "订单分成数据插入失败");
            return insert;
        });

        return result == null ? 0 : result;
    }

    // 转换为订单所需的数据
    private TransactionBill parseToOrder(RechargeBO bo) {
        // 校验
        ServiceUtil.assertion(transactionBillValidator.preAddOrder(bo));
        RechargeDTO dto = bo.getParams();
        DeviceVO device = bo.getDevice();
        SmUserVO mch = bo.getMch();
        SuitVO suit = bo.getSuit();
        SmUserVO user = bo.getUser();
        StoreVo store = bo.getStore();

        // 创建订单
        TransactionBill order = new TransactionBill();
        bo.setOrder(order);

        // 基础信息
        order.setType(TransactionBillType.RECHARGE.getType());       // 类型：充值订单
        order.setDeviceRechargeStatus(TransactionBillDeviceRechargeStatus.UN_RECHARGE.getStatus()); // 设备状态：未充值
        order.setVersion(2);    // 订单版本
        order.setCloseStatus(RechargeCloseStatus.SUCCESS.getStatus());
        order.setCloseResult("预计成功");

        if (SuitFeeType.timingList().contains(suit.getFeeType())) {
            // 当为分时段收费，状态为未支付押金
            order.setSuitPrice(dto.getSuitDeposit());
            order.setSuitDeposit(dto.getSuitDeposit());
            order.setStatus(TransactionBillStatus.UNPAID_DEPOSIT.getStatus());  // 未支付押金
        } else {
            // 否则为未支付
            if (SuitFeeMode.SMART.getMode().equals(suit.getFeeMode())) {
                // 智能收费，收取押金
                order.setMoney(dto.getSuitDeposit());
                order.setSuitDeposit(dto.getSuitDeposit());
            } else {
                // 单次收费，收取本金
                order.setMoney(dto.getMoney());
            }
            order.setStatus(TransactionBillStatus.UNPAID.getStatus());   // 状态：未支付
        }

        // 用户信息
        order.setUserId(user.getUserId());
        order.setUserMobile(user.getPhonenumber());

        // 商户信息
        order.setMchId(mch.getUserId());
        order.setMchShowMobile(mch.getShowBillMobile());
        order.setMchShowMobilePrice(mch.getShowBillMobilePrice());
        order.setMchShowMobileStatus(RechargeBillShowMobileStatus.UNPAID.getStatus());

        // 套餐信息
        order.setSuitId(suit.getSuitId());
        order.setSuitTime(suit.getValue());
        order.setSuitTimeUnit(suit.getTimeUnit());
        order.setSuitFeeType(suit.getFeeType());
        order.setSuitFeeMode(suit.getFeeMode());
        order.setSuitPrice(suit.getPrice());
        order.setSuitGearAmount(suit.getGearAmount());
        order.setSuitGearTime(suit.getGearTime());
        order.setSuitName(suit.getName());
        order.setSuitEnableLowPowerClose(suit.getEnabledLowPowerClose());
        order.setSuitLowPower(suit.getLowPower());
        order.setSuitEnabledVoid(suit.getEnabledVoice());
        order.setSuitVoidMinute(suit.getVoiceMinutes());

        // 设备信息
        order.setDeviceNo(device.getDeviceNo());
        order.setDeviceId(device.getDeviceId());
        order.setDeviceName(device.getDeviceName());
        order.setDeviceMac(device.getMac());
        order.setDeviceMac2(device.getMac2());
        order.setDeviceProductId(device.getModelProductId());
        order.setDeviceServiceMode(device.getServiceMode());
        order.setAgentId(device.getAgentId());
        order.setAgentMobile(device.getAgentMobile());

        // 店铺信息
        if (store != null) {
            order.setStoreId(store.getStoreId());
            order.setStoreName(store.getName());
            order.setStoreAddress(String.format("%s%s%s%s", store.getProvince(), store.getCity(), store.getCounty(), store.getAddress()));
        }

        // 支付过期时间 5min
        long expireTime = TimeUnit.MILLISECONDS.convert(Constants.BILL_UNPAID_TIMEOUT, Constants.BILL_UNPAID_TIMEUNIT) + System.currentTimeMillis();
        order.setExpireTime(new Date(expireTime));

        return order;
    }

    @Override
    public String addWithdraw(WithdrawBO bo) {
        // 参数校验
        Long userId = bo.getDto().getUserId();
        ServiceUtil.assertion(userId == null, "参数错误：用户id为空");
        // 加锁
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.ADD_WITHDRAW, userId), "请勿频繁操作提现");
        try {
            WithdrawDTO dto = bo.getDto();
            ChannelWithdrawVO channel = bo.getChannel();
            SmUserVO user = bo.getUser();
            AccountVO account = bo.getAccount();

            ServiceUtil.assertion(user == null, "用户不存在");
            ServiceUtil.assertion(user.getIsReal() == null || !user.getIsReal(), "用户未实名认证，无法提现");

            // 判断用户是否被限制提现
            withdrawValidator.checkWithdrawRisk(user);

            // 风控规则，判断用户是否有风险
            boolean enabled = sysConfigService.getBoolean(ConfigKey.RISK_WITHDRAW_ENABLED); // 是否开启提现风控

            // 判断是否有风险
            boolean hasRisk = enabled && withdrawValidator.hasRisk(bo);
            if (hasRisk) {
                // 若用户风险次数已达到阈值，将用户标记为提现风险
                int riskWithdrawCount = sysConfigService.getInt(ConfigKey.RISK_WITHDRAW_COUNT);
                if (user.getRiskCount() + 1 >= riskWithdrawCount) {
                    riskService.limitWithdraw(userId, "根据风控规则判断，您的提现具有风险");
                    // 返回错误
                    throw new ServiceException("提现具有风险，无法提现", ServiceCode.RISK_WITHDRAW.getCode());
                }
            }

            // 判断今天提现成功和正在审核中的提现是否超过限额
            String dailyLimitStr = sysConfigService.selectConfigByKey(ConfigKey.DAILY_WITHDRAW_AMOUNT.getKey());
            String dailyLimitCountStr = sysConfigService.selectConfigByKey(ConfigKey.DAILY_WITHDRAW_COUNT.getKey());
            if (StringUtils.hasText(dailyLimitStr) || StringUtils.hasText(dailyLimitCountStr)) {
                // 查询条件
                TransactionBillQuery query = new TransactionBillQuery();
                query.setType(TransactionBillType.WITHDRAW.getType());
                query.setStatusList(TransactionBillStatus.countOfLimit());
                query.setCreateDate(DateUtils.getNowDate());
                query.setUserId(user.getUserId());

                // 每日限额
                if (StringUtils.hasText(dailyLimitStr)) {
                    BigDecimal dailyLimit = new BigDecimal(dailyLimitStr);
                    BigDecimal current = this.selectSumOfMoney(query);
                    if (current == null) {
                        current = BigDecimal.ZERO;
                    }
                    ServiceUtil.assertion(current.add(dto.getMoney()).compareTo(dailyLimit) > 0, "当前用户提现金额已超过每日限额：" + dailyLimit + "元");
                }
                // 每日限次
                if (StringUtils.hasText(dailyLimitCountStr)) {
                    int dailyLimitCount = Integer.parseInt(dailyLimitCountStr);
                    int count = this.selectSimpleCount(query);
                    ServiceUtil.assertion(count + 1 > dailyLimitCount, "当前用户提现次数已超过每日限制：" + dailyLimitCount + "次");
                }
            }

            // 处理提现手续费
            ServiceUtil.assertion(channel == null, "提现渠道不存在");
            ServiceUtil.assertion(channel.getEnabled() == null || !channel.getEnabled(), "提现渠道不可用");
            ServiceUtil.assertion(channel.getMinAmount().compareTo(dto.getMoney()) > 0, String.format("提现金额不能低于%s元", channel.getMinAmount()));
            ServiceUtil.assertion(channel.getMaxAmount().compareTo(dto.getMoney()) < 0, String.format("提现金额不能高于%s元", channel.getMaxAmount()));
            UserWithdrawServiceVO serviceInfo = this.getUserWithdrawService(user, channel);
            ServiceUtil.assertion(serviceInfo == null, "服务费配置出错，请联系管理员处理");
            ServiceUtil.assertion(StringUtils.isBlank(serviceInfo.getServiceType()), "服务费配置出错，请联系管理员处理");
            ServiceUtil.assertion(serviceInfo.getServiceRate() == null, "服务费配置出错，请联系管理员处理");
            ServiceUtil.assertion(!Objects.equals(serviceInfo.getServiceType(), dto.getServiceType()), "服务费收取方式发生变化，请刷新后重试");
            ServiceUtil.assertion(serviceInfo.getServiceRate().compareTo(dto.getServiceRate()) != 0, "服务费发生变化，请刷新后重试");

            // 计算服务费
            BigDecimal serviceRecharge = this.calcWithdrawServiceCharge(serviceInfo, dto.getMoney());
            ServiceUtil.assertion(BigDecimal.ZERO.compareTo(serviceRecharge) > 0, "服务费不允许小于0");
            ServiceUtil.assertion(serviceRecharge.compareTo(dto.getServiceCharge()) != 0, "服务费计算不一致，请重新申请");
            // 到账金额
            BigDecimal arrivalAmount = dto.getMoney().subtract(serviceRecharge);   // 到账金额
            ServiceUtil.assertion(arrivalAmount.compareTo(dto.getArrivalAmount()) != 0, "到账金额计算不一致，请重新申请");

            // 创建申请订单
            TransactionBill bill = new TransactionBill();
            bill.setUserId(userId);
            bill.setType(TransactionBillType.WITHDRAW.getType());
            bill.setMchId(userId);
            bill.setMoney(dto.getMoney());
            bill.setArrivalAmount(arrivalAmount);
            bill.setServiceCharge(serviceRecharge);
            bill.setStatus(TransactionBillStatus.WITHDRAW_APPROVING.getStatus());   // 审核中
            bill.setAccountNo(account.getAccountNo());
            bill.setChannelId(channel.getChannelId());
            bill.setWithdrawType(WithdrawType.ONLINE.getType());
            bill.setChannelCost(channel.getCostRate().multiply(bill.getMoney()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));   // 渠道成本
            bill.setAfterBalance(user.getBalance().subtract(dto.getMoney())); // 剩余金额

            transactionTemplate.execute(status -> {
                // 提现申请
                int insert = this.insertSmTransactionBill(bill);
                ServiceUtil.assertion(insert != 1, "提现申请失败");

                // 减少余额，并判断提现金额是否超额（减少的是交易金额）
                userService.subtractBalance(userId, bill.getMoney(), String.format("提现申请:%s", bill.getBillNo()), RecordBalanceBstType.WITHDRAW, bill.getBillId());

                // 若本次提现具有风险，则累计一次风险次数
                if (hasRisk) {
                    userService.addRiskCount(userId, 1);
                }
                return insert;
            });

            // 异步操作：当提现申请金额免审核且为线上打款时，直接通过
            try {
                String noverifyAmountStr = sysConfigService.selectConfigByKey(ConfigKey.NOVERIFY_WITHDRAW_SINGLE.getKey());
                if (StringUtils.hasText(noverifyAmountStr)) {
                    BigDecimal noverifyAmount = new BigDecimal(noverifyAmountStr);
                    if (noverifyAmount.compareTo(bill.getMoney()) >= 0) {
                        scheduledExecutorService.schedule(() -> {
                            WithdrawApprovalDTO passDto = new WithdrawApprovalDTO();
                            passDto.setBillId(bill.getBillId());
                            passDto.setWithdrawType(WithdrawType.ONLINE.getType());
                            passDto.setRemark("免审核通过");
                            this.passWithdraw(passDto);
                        }, 0, TimeUnit.SECONDS);
                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }

            return bill.getBillNo();
        } finally {
            redisLock.unlock(RedisLockKey.ADD_WITHDRAW, userId);
        }

    }

    private BigDecimal calcWithdrawServiceCharge(UserWithdrawServiceVO serviceInfo, BigDecimal money) {
        BigDecimal serviceRecharge;
        if (WithdrawServiceType.PERCENT.getType().equals(serviceInfo.getServiceType())) {
            serviceRecharge = money.multiply(serviceInfo.getServiceRate()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); // 服务费
        } else if (WithdrawServiceType.FIXED.getType().equals(serviceInfo.getServiceType())) {
            serviceRecharge = serviceInfo.getServiceRate();
        } else {
            throw new ServiceException("服务费配置出错，请联系管理员处理");
        }
        return serviceRecharge;
    }

    /**
     * 获取用户提现服务费
     * 用户 > 渠道
     */
    private UserWithdrawServiceVO getUserWithdrawService(SmUserVO user, ChannelWithdrawVO channel) {
        if (user != null && StringUtils.hasText(user.getWithdrawServiceType()) && user.getWithdrawServiceRate() != null ) {
            return new UserWithdrawServiceVO(user.getWithdrawServiceType(), user.getWithdrawServiceRate());
        } else if (channel != null && StringUtils.hasText(channel.getServiceType()) && channel.getServiceRate() != null) {
            return new UserWithdrawServiceVO(channel.getServiceType(), channel.getServiceRate());
        }
        return null;
    }

    /**
     * 提现审核通过
     *
     * @param dto id
     */
    @Override
    public int passWithdraw(WithdrawApprovalDTO dto) {
        ServiceUtil.assertion(dto.getBillId() == null, "提现申请ID不允许为空");
        Long lockKey = dto.getBillId();
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.APPROVAL_WITHDRAW, lockKey), "该提现申请正在审核，请刷新后重试");
        try {
            Integer result = transactionTemplate.execute(status -> {
                int update = 0;
                // 线下打款，修改状态为已打款
                if (WithdrawType.OFFLINE.getType().equals(dto.getWithdrawType())) {
                    update = this.approvalWithdraw(dto, TransactionBillStatus.WITHDRAW_SUCCESS);
                }
                // 线上打款，修改状态为打款中
                else if (WithdrawType.ONLINE.getType().equals(dto.getWithdrawType())) {
                    update = this.approvalWithdraw(dto, TransactionBillStatus.WITHDRAW_PAYING);
                } else {
                    throw new ServiceException("不支持的提现打款方式");
                }
                ServiceUtil.assertion(update != 1, "提现审核失败，请刷新后重试");

                // 线上打款，发起打款
                if (WithdrawType.ONLINE.getType().equals(dto.getWithdrawType())) {
                    TransactionBillVO withdraw = this.selectWithdrawById(dto.getBillId());
                    boolean pay = this.startPayOnline(withdraw);
                    ServiceUtil.assertion(!pay, "发起打款失败");
                }

                return update;
            });

            return result == null ? 0 : result;
        } finally {
            redisLock.unlock(RedisLockKey.APPROVAL_WITHDRAW, lockKey);
        }
    }

    /**
     * 提现线上打款
     */
    private boolean startPayOnline(TransactionBillVO bill) {
        ServiceUtil.assertion(bill == null, "单据不存在");
        ServiceUtil.assertion(TransactionBillStatus.WITHDRAW_PASSED.getStatus().equals(bill.getStatus()), "当前提现单据状态异常");
        ServiceUtil.assertion(StringUtils.isBlank(bill.getAccountNo()), "提现账号异常");

        // 生成转账单
        TransferVO vo = transferConverter.toVo(bill);

        Boolean result = transactionTemplate.execute(status -> {
            // 插入数据
            int insert = transferService.insertWithDetail(vo);
            ServiceUtil.assertion(insert != 1, "新增转账单失败");

            // 发起转账
            boolean requestResult = transferService.requestTransfer(vo.getBatchNo());
            ServiceUtil.assertion(!requestResult, "发起转账失败");

            return true;
        });

        return result != null && result;
    }

    /**
     * 审核操作
     */
    private int approvalWithdraw(WithdrawApprovalDTO dto, TransactionBillStatus status) {
        Integer result = transactionTemplate.execute(s -> {
            TransactionBill data = new TransactionBill();
            data.setStatus(status.getStatus());
            data.setRemark(dto.getRemark());
            data.setPayPicture(dto.getPayPicture());
            data.setWithdrawType(dto.getWithdrawType());
            data.setOfflineImage(dto.getOfflineImage());

            // 线下打款并且状态为已打款，则修改支付时间为当前
            if (WithdrawType.OFFLINE.getType().equals(dto.getWithdrawType()) && TransactionBillStatus.WITHDRAW_SUCCESS.equals(status)) {
                data.setPayTime(DateUtils.getNowDate());
            }

            TransactionBillQuery query = new TransactionBillQuery();
            query.setBillId(dto.getBillId());
            query.setType(TransactionBillType.WITHDRAW.getType());
            query.setStatus(TransactionBillStatus.WITHDRAW_APPROVING.getStatus());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "提现审核失败");

            return update;
        });

        return result == null ? 0 : result;
    }

    @Override
    public boolean rejectWithdraw(WithdrawApprovalDTO dto) {
        ServiceUtil.assertion(dto.getBillId() == null, "提现申请ID不允许为空");
        Long lockKey = dto.getBillId();
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.APPROVAL_WITHDRAW, lockKey), "该提现申请正在审核，请刷新后重试");
        try {
            transactionTemplate.execute(status -> {
                // 审核
                int updateCount = this.approvalWithdraw(dto, TransactionBillStatus.WITHDRAW_REJECTED);
                ServiceUtil.assertion(updateCount != 1, "提现审核失败，请刷新后重试");

                // 返还客户余额
                TransactionBill bill = transactionBillMapper.selectSmTransactionBillByBillId(dto.getBillId());
                ServiceUtil.assertion(bill == null || bill.getUserId() == null, "数据不存在");
                userService.addBalance(bill.getUserId(), bill.getMoney(), String.format("提现驳回: %s", bill.getBillNo()), RecordBalanceBstType.WITHDRAW, bill.getBillId());

                return updateCount;
            });

            return true;
        } finally {
            redisLock.unlock(RedisLockKey.APPROVAL_WITHDRAW, lockKey);
        }
    }

    @Override
    public List<BillCountVo> selectLandlordCount(TransactionBillQuery dto) {
        List<BillCountVo> list = this.selectCount(dto);

        // 若startDate与endDate不为空，则将其中间数据补全，不让其为空
        if (dto.getStartDate() != null && dto.getEndDate() != null && Objects.equals(dto.getGroupBy(), TransactionBillGroupBy.create_date.name())) {
            List<BillCountVo> newList = new ArrayList<>();
            LocalDate i = dto.getStartDate();
            while (!i.isAfter(dto.getEndDate())) {
                Date finalI = DateUtils.toDate(i);
                BillCountVo count = list.stream().filter(item -> DateUtils.getYYYY_MM_DD(item.getCreateDate()).equals(DateUtils.getYYYY_MM_DD(finalI))).findFirst().orElse(null);
                if (count == null) {
                    count = new BillCountVo();
                    count.setCreateDate(finalI);
                    count.setServiceCharge(BigDecimal.ZERO);
                    count.setWithdraw(BigDecimal.ZERO);
                    count.setRecharge(BigDecimal.ZERO);
                    count.setCreateYear(DateUtils.getYear(finalI));
                    count.setCreateMonth(DateUtils.getMonth(finalI));
                    count.setCreateDay(DateUtils.getDay(finalI));
                    count.setCreateYearMonth(DateUtils.getYYYY_MM(finalI));
                }
                newList.add(count);
                i = i.plusDays(1);
            }

            // 列表排序
            newList.sort(Comparator.comparing(BillCountVo::getCreateDate));
            list = newList;
        }

        return list;
    }

    /**
     * 刷新支付结果
     *
     * @param billId
     * @return
     */
    @Transactional
    public int refreshPayResult(Long billId) {
        return this.refreshPayResult(this.selectSmTransactionBillByBillId(billId));
    }

    public int refreshPayResult(TransactionBillVO bill) {
        return this.refreshPayResult(Collections.singletonList(bill));
    }

    /**
     * 订单设备充值
     *
     * @param billId 订单ID
     */
    @Override
    public boolean rechargeDevice(Long billId) {
        ServiceUtil.assertion(billId == null, "参数错误,billId不允许为空");
//        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.RECHARGE_DEVICE, billId), "当前设备充值请求过于频繁，请等待");
        try {
            TransactionBillVO bill = transactionBillMapper.selectSmTransactionBillByBillId(billId);
            ServiceUtil.assertion(bill == null || !TransactionBillType.RECHARGE.getType().equals(bill.getType()), "不存在的充值订单");
            ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单未支付");

            if (TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus())) {
                log.warn( "设备已充值成功，不允许再次充值:billId={}", billId);
                return false;
            }

            ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已选择蓝牙充值，请使用蓝牙进行充值");

            // 设备开启状态描述
            AtomicReference<String> openMsg = new AtomicReference<>("成功");
            // 获取最新设备信息
            DeviceVO device = deviceService.selectById(bill.getDeviceId());
            deviceService.pullDeviceInfo(device);

            Boolean result = transactionTemplate.execute(status -> {
                // 更新订单套餐使用信息
                int updateInfo = this.updateSuitInfoBeforeDevice(bill, device);
                ServiceUtil.assertion(updateInfo != 1, "更新套餐使用信息失败");

                // 更新订单设备充值状态：成功
                int updateRecharge = transactionBillMapper.updateDeviceRechargeStatus(
                        bill.getBillId(),
                        TransactionBillDeviceRechargeStatus.SUCCESS.getStatus(),
                        openMsg.get()
                );
                ServiceUtil.assertion(updateRecharge != 1, "设备充值状态发生变化，请稍后重试");

                // 充值设备
                try {
                    if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) {
                        // 设备设置时长
                        long seconds = transactionBillConverter.toRechargeSeconds(bill);
                        boolean addTime = deviceService.addTime(device, seconds, true, "充值订单：" + bill.getBillNo());
                        ServiceUtil.assertion(!addTime, "设备时长充值失败");
                    } else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) {
                        // 设备设置电量
                        BigDecimal ele = transactionBillConverter.toRechargeEle(bill);
                        boolean addEle = deviceService.addEle(device, ele, true, "充值订单：" + bill.getBillNo());
                        ServiceUtil.assertion(!addEle, "设备时长充值失败");
                    } else {
                        throw new ServiceException("不支持的套餐类型");
                    }
                } catch (Exception e) {
                    // 失败，回滚
                    status.setRollbackOnly();
                    openMsg.set(e.getMessage());
                    return false;
                }

                return true;
            });

            // 是否成功
            boolean success = result != null && result;

            if (!success) {
                // 修改设备充值状态为失败
                transactionBillMapper.updateDeviceRechargeStatus(
                        billId,
                        TransactionBillDeviceRechargeStatus.FAIL.getStatus(),
                        openMsg.get()
                );
            }

            return success;
        } finally {
//            redisLock.unlock(RedisLockKey.RECHARGE_DEVICE, billId);
        }
    }

    /**
     * 在设备充值成功前，更新套餐使用信息
     */
    private int updateSuitInfoBeforeDevice(TransactionBillVO bill, DeviceVO device) {
        if (bill == null || device == null) {
            return 0;
        }

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = device.getExpireTime() == null || now.isAfter(device.getExpireTime()) ? now : device.getExpireTime();
        BigDecimal startEle = device.getTotalElectriQuantity();

        TransactionBill data = new TransactionBill();
        data.setBillId(bill.getBillId());
        data.setSuitStartTime(startTime);
        data.setSuitStartEle(startEle);

        // 充值时长
        if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) {
            // 计算开始使用的时间
            LocalDateTime endTime = startTime.plusSeconds(transactionBillConverter.toRechargeSeconds(bill));
            data.setSuitEndTime(endTime);
            data.setSuitExpireTime(endTime);
        }
        // 充值电量
        else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) {
            BigDecimal endEle = startEle.add(transactionBillConverter.toRechargeEle(bill));
            data.setSuitEndEle(endEle);
        }
        return transactionBillMapper.updateSmTransactionBill(data);
    }

    /**
     * 尝试设置语音播报
     */
    @Override
    public void trySetVoice(TransactionBillVO bill, int tryCount) {
        if (tryCount <= 0) {
            return;
        }
        try {
            transactionTemplate.execute(status -> {
                // 设置为成功
                int update = this.updateVoiceResult(bill.getBillId(), RechargeVoiceStatus.SUCCESS, "成功");
                ServiceUtil.assertion(update != 1, "更新订单信息失败");

                // 发送命令
                boolean res = false;
                if (bill.getSuitEnabledVoid() == null || !bill.getSuitEnabledVoid() || bill.getSuitVoidMinute() == null) {
                    res = iotService.setVoice(bill, 0L, "关闭订单语音:" + bill.getBillNo());
                } else {
                    res = iotService.setVoice(bill, bill.getSuitVoidMinute().multiply(BigDecimal.valueOf(60)).longValue(), "开启订单语音:" + bill.getBillNo());
                }
                ServiceUtil.assertion(!res, "设备设置语音失败");

                return res;
            });
        } catch (Exception e) {
            log.info("订单设置设备语音失败：{}, 剩余次数:{}", bill.getBillNo(), tryCount - 1);
            if (tryCount <= 1) {
                // 设置为失败
                int update = this.updateVoiceResult(bill.getBillId(), RechargeVoiceStatus.FAIL, e.getMessage());
                ServiceUtil.assertion(update != 1, "更新订单信息失败");
            } else {
                this.trySetVoice(bill, tryCount - 1);
            }
        }
    }

    private int updateVoiceResult(Long billId, RechargeVoiceStatus status, String msg) {
        TransactionBill data = new TransactionBill();
        data.setBillId(billId);
        data.setSuitVoidResult(status.getStatus());
        data.setSuitVoidMsg(msg);
        return transactionBillMapper.updateSmTransactionBill(data);
    }

    /**
     * 异步尝试充值设备
     * @param billId
     * @param tryCount
     */
    @Override
    public void tryRechargeDevice(Long billId, int tryCount) {
        if (tryCount <= 0) {
            return;
        }
        try {
            boolean result = rechargeDevice(billId);
            ServiceUtil.assertion(!result, String.format("尝试充值设备失败:billId=%s:剩余次数:%s,", billId, tryCount - 1));
        } catch (Exception e) {
            this.tryRechargeDevice(billId, tryCount - 1);
        }
    }

    @Override
    public DoPayVO payDeposit(RechargePayDepositBO bo) {
        // 校验
        ServiceUtil.assertion(transactionBillValidator.prePayDeposit(bo));
        // 锁
        Long lockKey = bo.getOrder().getBillId();
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.PREPAY_DEPOSIT, lockKey), "当前订单押金正在支付，请稍后重试");
        try {
            // 创建支付订单并发起支付
            return payBillService.createPayBill(payBillConverter.toPoByRechargeDeposit(bo));
        } finally {
            redisLock.unlock(RedisLockKey.PREPAY_DEPOSIT, lockKey);
        }
    }

    public EndUseVO endTimingUse(EndUseBO bo, boolean withDevice) {
        if (bo == null) {
            return EndUseVO.fail();
        }
        TransactionBillVO order = bo.getOrder();
        DeviceVO device = bo.getDevice();
        ServiceUtil.assertion(order == null, "订单不存在");
        ServiceUtil.assertion(!TransactionBillStatus.SUCCESS_DEPOSIT.getStatus().equals(order.getStatus()), "当前订单状态不允许结束");
        ServiceUtil.assertion(order.getIsFinished() != null && order.getIsFinished(), "当前订单已结束，无法操作");
        ServiceUtil.assertion(device == null, "设备不存在");

        LocalDateTime endTime = LocalDateTime.now();    // 结束使用的时间

        // 结束使用的电量：若蓝牙传过来的值为空或者小于当前电量，则使用当前电量，否则使用蓝牙传输的电量
        BigDecimal totalEle = this.calcTotalEle(device, bo.getTotalEle());

        // 计算金额
        BigDecimal money = this.calcTimingAmount(order, endTime, totalEle);

        Integer result = transactionTemplate.execute(status -> {
            // 修改订单信息
            TransactionBill data = new TransactionBill();
            data.setSuitEndEle(totalEle);
            data.setSuitEndTime(endTime);
            data.setStatus(TransactionBillStatus.UNPAID.getStatus());
            data.setMoney(money);
            TransactionBillQuery query = new TransactionBillQuery();
            query.setStatus(TransactionBillStatus.SUCCESS_DEPOSIT.getStatus());
            query.setBillId(order.getBillId());
            query.setIsFinished(false);
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改订单状态失败，请刷新后重试");

            return update;
        });

        // 尝试关闭设备
        if (result != null && result == 1) {
            try {
                transactionTemplate.execute(status -> {
                    // 记录设备关闭状态
                    int update = this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.SUCCESS, "成功");
                    ServiceUtil.assertion(update != 1, "记录设备关闭状态失败，请刷新后重试");

                    String reason = "分时段订单结束使用:" + order.getBillNo();

                    if (withDevice) {
                        if (SuitFeeType.TIMING_COUNT.getType().equals(order.getSuitFeeType())) {
                            deviceService.resetEle(device, false, reason);
                        } else if(SuitFeeType.TIMING_TIME.getType().equals(order.getSuitFeeType())){
                            deviceService.resetTime(device, false, reason);
                        }
                        iotService.close(device, reason);
                    }

                    return update;
                });
            } catch (Exception e) {
                log.error("尝试关闭设备失败: {}", e.getMessage());
                this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.FAIL, e.getMessage());
                return EndUseVO.toResult(result, false, e.getMessage());
            }
        }

        return EndUseVO.toResult(result, true, null);
    }

    private BigDecimal calcTotalEle(DeviceVO device, BigDecimal totalEle) {
        if (totalEle == null || totalEle.compareTo(device.getTotalElectriQuantity()) < 0) {
            return device.getTotalElectriQuantity();
        } else {
            return totalEle;
        }
    }

    private BigDecimal calcTimingAmount(TransactionBillVO order, LocalDateTime endTime, BigDecimal totalEle) {
        ServiceUtil.assertion(order == null, "计算金额出错，订单不存在");
        BigDecimal totalAmount;
        if (SuitFeeType.TIMING_TIME.getType().equals(order.getSuitFeeType())) {
            totalAmount = this.calcTimingTimeAmount(order, endTime);
        } else if (SuitFeeType.TIMING_COUNT.getType().equals(order.getSuitFeeType())) {
            totalAmount = this.calcTimingCountAmount(order, endTime, totalEle);
        } else {
            throw new ServiceException("计算金额出错，套餐收费类型不支持");
        }

        // 最低金额不允许低于最低服务费
        BigDecimal minService = sysConfigService.getBigDecimal(ConfigKey.RECHARGE_MIN_SERVICE);
        if (totalAmount.compareTo(minService) < 0) {
            totalAmount = minService;
        }

        return totalAmount;
    }

    public EndUseVO endSmartUse(EndUseBO bo, boolean withDevice) {
        if (bo == null) {
            return EndUseVO.fail();
        }
        TransactionBillVO order = bo.getOrder();
        DeviceVO device = bo.getDevice();
        ServiceUtil.assertion(order == null, "订单不存在");
        ServiceUtil.assertion(!SuitFeeMode.SMART.getMode().equals(order.getSuitFeeMode()), "当前订单非智能收费订单，无法提前结束");
        ServiceUtil.assertion(order.getIsFinished() != null && order.getIsFinished(), "当前订单已结束，无法操作");
        ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(order.getStatus()), "当前订单状态不允许结束");
        ServiceUtil.assertion(device == null, "设备不存在");

        LocalDateTime endTime = LocalDateTime.now();    // 结束使用的时间

        // 结束使用的电量：若蓝牙传过来的值为空或者小于当前电量，则使用当前电量，否则使用蓝牙传输的电量（谁大用谁）
        BigDecimal totalEle = this.calcTotalEle(device, bo.getTotalEle());

        // 若结束时间 > 订单结束时间 或者 结束电量 > 订单结束电量，则表示已经结束使用
        ServiceUtil.assertion(order.getIsFinished() != null && order.getIsFinished(), "当前订单已结束，无法操作");

        Integer result = transactionTemplate.execute(status -> {
            // 修改结束使用的时间
            TransactionBill data = new TransactionBill();
            data.setSuitEndTime(endTime);
            data.setSuitEndEle(totalEle);
            TransactionBillQuery query = new TransactionBillQuery();
            query.setBillId(order.getBillId());
            query.setStatus(TransactionBillStatus.SUCCESS.getStatus());
            query.setIsFinished(false);
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改订单信息失败，请重试：" + order.getBillNo());

            return update;
        });

        boolean success = result != null && result == 1;    // 是否成功

        if (success) {
            // 计算需要退款的金额,若金额 > 0.01 则申请退款
            BigDecimal refundAmount = this.calcRefundAmount(order, endTime, totalEle);
            if (BigDecimal.valueOf(0.01).compareTo(refundAmount) < 0) {
                scheduledExecutorService.schedule(() -> {
                    // 申请退款
                    BillRefundDTO refundDto = new BillRefundDTO();
                    refundDto.setBillId(order.getBillId());
                    refundDto.setRefundAmount(refundAmount);
                    refundDto.setRefundReason(String.format("充值订单%s智能退款%s元", order.getBillNo(), refundAmount));
                    int refund = this.refund(refundDto);
                    ServiceUtil.assertion(refund != 1, "申请退款失败");
                }, 10, TimeUnit.SECONDS);
            }

            // 清零设备
            try {
                transactionTemplate.execute(status -> {
                    // 记录设备关闭状态：成功
                    int update = this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.SUCCESS, "成功");
                    ServiceUtil.assertion(update != 1, "更新设备关闭状态失败");


                    // 尝试设备清零时长、电量
                    String reason = String.format("智能订单结束使用: %s", order.getBillNo());
                    if (withDevice) {
                        if (SuitFeeType.TIME.getType().equals(order.getSuitFeeType())) {
                            deviceService.resetTime(device, false, reason);
                        } else if (SuitFeeType.COUNT.getType().equals(order.getSuitFeeType())) {
                            deviceService.resetEle(device, false, reason);
                        }
                    }

                    return update;
                });
            } catch (Exception e) {
                log.warn("设备归零失败: {}", e.getMessage());
                // 记录设备关闭状态
                this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.FAIL, e.getMessage());
                return EndUseVO.toResult(result, false, e.getMessage());
            }
        }

        return EndUseVO.toResult(result, true, null);
    }

    private int updateCloseStatus(Long billId, RechargeCloseStatus status, String msg) {
        TransactionBill data = new TransactionBill();
        data.setBillId(billId);
        data.setCloseStatus(status.getStatus());
        data.setCloseResult(msg);
        return transactionBillMapper.updateSmTransactionBill(data);
    }

    @Override
    public BigDecimal calcAmount(Long billId, LocalDateTime endTime, BigDecimal totalEle) {

        TransactionBillVO bill = selectSmTransactionBillByBillId(billId);
        ServiceUtil.assertion(bill == null, "计算金额出错，订单不存在");

        // 计量收费，则获取最新的设备信息
        if(SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType()) || SuitFeeType.TIMING_COUNT.getType().equals(bill.getSuitFeeType())) {
            DeviceVO device = deviceService.selectById(bill.getDeviceId());
            deviceService.pullDeviceInfo(device);
            totalEle = this.calcTotalEle(device, totalEle);
        }

        // 智能计费
        if (SuitFeeMode.SMART.getMode().equals(bill.getSuitFeeMode())) {
            if (SuitFeeType.timingList().contains(bill.getSuitFeeType())) {
                // 分时段计费
                return this.calcTimingAmount(bill, endTime, totalEle);
            } else {
                // 非分时段计费
                return this.calcSmartAmount(bill, endTime, totalEle);
            }
        }
        // 非智能计费
        else {
            return bill.getMoney();
        }
    }

    @Override
    public BigDecimal selectSumOfMoney(TransactionBillQuery query) {
        return transactionBillMapper.selectSumOfMoney(query);
    }

    public EndUseVO endUseSingle(EndUseBO bo, boolean withDevice) {
        if (bo == null) {
            return EndUseVO.fail();
        }
        TransactionBillVO order = bo.getOrder();
        DeviceVO device = bo.getDevice();
        ServiceUtil.assertion(order == null, "订单不存在");
        ServiceUtil.assertion(order.getIsFinished() != null && order.getIsFinished(), "当前订单已结束，无法操作");
//        ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(order.getStatus()), "当前订单状态不允许结束");
        ServiceUtil.assertion(device == null, "设备不存在");

        LocalDateTime endTime = LocalDateTime.now();    // 结束使用的时间

        // 结束使用的电量：若蓝牙传过来的值为空或者小于当前电量，则使用当前电量，否则使用蓝牙传输的电量（谁大用谁）
        BigDecimal totalEle = this.calcTotalEle(device, bo.getTotalEle());

        Integer result = transactionTemplate.execute(status -> {
            // 修改结束使用的时间
            TransactionBill data = new TransactionBill();
            data.setSuitEndTime(endTime);
            data.setSuitEndEle(totalEle);
            TransactionBillQuery query = new TransactionBillQuery();
            query.setBillId(order.getBillId());
            query.setStatus(TransactionBillStatus.SUCCESS.getStatus());
            query.setIsFinished(false);
//            if (SuitFeeType.TIME.getType().equals(order.getSuitFeeType())) {
//                query.setStartSuitEndTime(endTime); // 计时的话，需要结束时间在这之后的订单
//            } else if (SuitFeeType.COUNT.getType().equals(order.getSuitFeeType())) {
//                query.setStartSuitEndEle(totalEle); // 计量的话，需要结束电量比这个大的订单
//            }
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改订单信息失败，请重试：" + order.getBillNo());

            return update;
        });

        // 尝试清零设备
        if (result != null && result == 1) {
            try {
                transactionTemplate.execute(status -> {
                    // 记录设备关闭状态：成功
                    int update = this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.SUCCESS, "成功");
                    ServiceUtil.assertion(update != 1, "更新设备关闭状态失败");

                    // 尝试设备清零时长、电量
                    String reason = String.format("单次订单结束使用: %s", order.getBillNo());
                    if (withDevice) {
                        if (SuitFeeType.COUNT.getType().equals(order.getSuitFeeType())) {
                            deviceService.resetEle(device, false, reason);
                        } else if(SuitFeeType.TIME.getType().equals(order.getSuitFeeType())){
                            deviceService.resetTime(device, false, reason);
                        }
                    }

                    return update;
                });
            } catch (Exception e) {
                log.error("尝试清零设备失败: {}", e.getMessage());
                // 记录设备关闭状态
                this.updateCloseStatus(order.getBillId(), RechargeCloseStatus.FAIL, e.getMessage());
                return EndUseVO.toResult(result, false, e.getMessage());
            }
        }

        return EndUseVO.toResult(result, true, null);
    }

    /**
     * 计算退款金额
     */
    private BigDecimal calcRefundAmount(TransactionBillVO order, LocalDateTime endTime, BigDecimal totalEle) {
        BigDecimal refund = order.getMoney().subtract(this.calcSmartAmount(order, endTime, totalEle));

        // 退款金额不允许大于订单金额
        if (refund.compareTo(order.getMoney()) > 0) {
            return order.getMoney();
        }
        // 退款金额不允许小于0
        if (refund.compareTo(BigDecimal.ZERO) < 0) {
            return BigDecimal.ZERO;
        }
        return refund;
    }

    /**
     * 计算智能收费实际使用金额
     */
    private BigDecimal calcSmartAmount(TransactionBillVO order, LocalDateTime endTime, BigDecimal totalEle) {
        // 智能收费时长计费
        if (SuitFeeType.TIME.getType().equals(order.getSuitFeeType())) {
            return this.calcSmartTimeAmount(order, endTime);
        }
        // 智能收费计量收费
        else if (SuitFeeType.COUNT.getType().equals(order.getSuitFeeType())) {
            return this.calcSmartCountAmount(order, totalEle);
        }
        else {
            throw new ServiceException("不支持的智能收费方式");
        }
    }

    @Override
    public List<TransactionBillVO> selectUnpaidTimingBill(TransactionBillQuery query) {
        query.setStatus(TransactionBillStatus.UNPAID.getStatus());
        query.setSuitFeeTypes(SuitFeeType.timingList());
        query.setSuitFeeMode(SuitFeeMode.SMART.getMode());
        return this.selectSmTransactionBillList(query);
    }

    // 计算智能计量使用金额
    private BigDecimal calcSmartCountAmount(TransactionBillVO order, BigDecimal totalEle) {
        // 若订单不存在，或者 订单开始电量为空，表示未开始使用，则直接返回0
        if (order == null || order.getSuitStartEle() == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal startEle = order.getSuitStartEle();
        BigDecimal usedEle = totalEle.subtract(startEle);   // 已使用的电量
        BigDecimal suitEle = new BigDecimal(order.getSuitTime());   // 套餐每周期的电量

        // 计算总使用的周期数，不满足一个周期也算一个周期
        BigDecimal totalRound = usedEle.divide(suitEle, 0, RoundingMode.UP);
        if (totalRound.compareTo(BigDecimal.ZERO) <= 0 ) {
            totalRound = BigDecimal.ONE;
        }
        // 使用金额 =  使用周期数 * 单价
        return totalRound.multiply(order.getSuitPrice());
    }

    // 计算智能时长的使用金额
    private BigDecimal calcSmartTimeAmount(TransactionBillVO order, LocalDateTime endTime) {
        // 若订单不存在，或者 订单开始时间为空，表示未开始使用，则直接返回0
        if (order == null || order.getSuitStartTime() == null) {
            return BigDecimal.ZERO;
        }
        Duration between = Duration.between(order.getSuitStartTime(), endTime);

        SuitTimeUnit unit = SuitTimeUnit.getByValue(order.getSuitTimeUnit());
        ServiceUtil.assertion(unit == null, "订单套餐单位无效");

        // 计算总使用的周期数，不满足一个周期也算一个周期
        long totalSeconds = between.getSeconds(); // 总使用时长
        long secondSuitTime = transactionBillConverter.toSecondSuitTime(order);
        long totalRound = totalSeconds / secondSuitTime + (totalSeconds % secondSuitTime != 0 ? 1 : 0);
        if (totalRound <= 0) {
            totalRound = 1;
        }

        // 使用金额 = 使用周期数 * 单价
        return BigDecimal.valueOf(totalRound).multiply(order.getSuitPrice());
    }

    // 计算分时段计量金额
    private BigDecimal calcTimingCountAmount(TransactionBillVO order, LocalDateTime endTime, BigDecimal totalEle) {
        BigDecimal total = BigDecimal.ZERO;

        // 计算每小时平均用电量
        LocalDateTime startTime = order.getSuitStartTime() == null ? LocalDateTime.now() : order.getSuitStartTime();
        Duration betweenAvg = Duration.between(startTime, endTime);
        long hours = betweenAvg.toHours();
        if (hours == 0) {
            hours = 1;
        }
        BigDecimal startEle = order.getSuitStartEle() == null ? BigDecimal.ZERO : order.getSuitStartEle();
        BigDecimal usedEle = totalEle == null ? BigDecimal.ZERO : totalEle.subtract(startEle);
        BigDecimal avgEle = usedEle.divide(BigDecimal.valueOf(hours), 2, RoundingMode.HALF_UP); // 平均每小时的用电量

        // 初始时间
        List<BigDecimal> suitGearAmount = order.getSuitGearAmount();
        List<Integer> suitGearTime = order.getSuitGearTime();
        int hour = startTime.getHour() ;

        // 计算每个小时的金额
        for (int i = 0; i < hours; i++) {
            BigDecimal hourAmount = suitGearAmount.get(suitGearTime.get(hour)).multiply(avgEle);
            total = total.add(hourAmount);

            hour = (hour + 1) % 24;
        }

        return total;
    }

    // 计算分时段计时金额
    private BigDecimal calcTimingTimeAmount(TransactionBillVO order, LocalDateTime endTime) {
        // 订单不存在 或者 开始时间为空，则表示未开始使用，返回0
        if (order == null || order.getSuitStartTime() == null) {
            return BigDecimal.ZERO;
        }
        LocalDateTime startTime = order.getSuitStartTime();
        List<BigDecimal> suitGearAmount = order.getSuitGearAmount();
        List<Integer> suitGearTime = order.getSuitGearTime();

        // 总金额
        BigDecimal total = BigDecimal.ZERO;

        // 初始时间
        LocalDateTime i = startTime.withMinute(0).withSecond(0);

        // 累加1小时
        while (i.isBefore(endTime)) {
            LocalDateTime plusI = i.plusHours(1);

            // 当前每分钟平均金额
            BigDecimal minuteAmount = suitGearAmount.get(suitGearTime.get(i.getHour())).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);

            // 计算时间段的金额
            LocalDateTime timeA = i.isAfter(startTime) ? i : startTime;
            LocalDateTime timeB = plusI.isBefore(endTime) ? plusI : endTime;
            Duration between = Duration.between(timeA, timeB);
            total = total.add(minuteAmount.multiply(BigDecimal.valueOf(between.toMinutes())));


            i = plusI;
        }

        return total;
    }

    /**
     * 记录下操作后的余额
     * @param billId 订单id
     * @param balance 余额
     */
    private void updateAfterBalance(Long billId, BigDecimal balance) {
        TransactionBill data = new TransactionBill();
        data.setBillId(billId);
        data.setAfterBalance(balance);
        transactionBillMapper.updateSmTransactionBill(data);
    }

    /**
     * 取消充值订单
     * @param billNo 订单编号
     * @param status 取消后的状态
     */
    @Override
    public int cancelRecharge(String billNo, TransactionBillStatus status) {
        ServiceUtil.assertion(!TransactionBillStatus.isCancel(status.getStatus()), "参数错误：状态并非取消状态");

        // 刷新支付结果
        this.refreshPayResult(billNo);

        // 查询数据
        TransactionBill bill = transactionBillMapper.selectSmTransactionBillByBillNo(billNo);
        ServiceUtil.assertion(bill == null, "订单不存在");
        ServiceUtil.assertion(!TransactionBillType.RECHARGE.getType().equals(bill.getType()), "非充值订单，不允许取消");

        // 允许取消的状态
        List<String> allowCancel = Arrays.asList(
                TransactionBillStatus.UNPAID.getStatus(),
                TransactionBillStatus.UNPAID_DEPOSIT.getStatus()
        );
        ServiceUtil.assertion(!allowCancel.contains(bill.getStatus()), "当前订单状态不允许取消");

        // 若是未支付的分时订单，则不允许取消
        ServiceUtil.assertion(TransactionBillStatus.UNPAID.getStatus().equals(bill.getStatus())
                && SuitFeeType.timingList().contains(bill.getSuitFeeType()), "未支付的分时段订单不允许取消");

        Integer result = transactionTemplate.execute(s -> {
            // 执行取消订单
            int cancel = transactionBillMapper.cancelRecharge(billNo, status.getStatus());
            ServiceUtil.assertion(cancel != 1, "取消订单失败：状态已发生变化");

            // 取消支付订单
            boolean cancelPay = false;
            if (TransactionBillStatus.UNPAID.getStatus().equals(bill.getStatus())) {
                cancelPay = payBillService.closeByBstId(bill.getBillId(), PayBillBstType.RECHARGE_ORDER.getType());
            } else if (TransactionBillStatus.UNPAID_DEPOSIT.getStatus().equals(bill.getStatus())) {
                cancelPay = payBillService.closeByBstId(bill.getBillId(), PayBillBstType.RECHARGE_ORDER_DEPOSIT.getType());
            }
            ServiceUtil.assertion(!cancelPay, "取消支付订单失败");

            return cancel;
        });

        return result == null ? 0 : result;
    }

    @Override
    public TransactionBillVO selectSmTransactionBillByBillNo(String billNo) {
        return transactionBillMapper.selectSmTransactionBillByBillNo(billNo);
    }

    @Override
    public List<TransactionBillVO> selectNoPayedRecharge() {
        TransactionBillQuery dto = new TransactionBillQuery();
        dto.setStatusList(Arrays.asList(TransactionBillStatus.UNPAID.getStatus(), TransactionBillStatus.PAYING.getStatus(), TransactionBillStatus.UNPAID_DEPOSIT.getStatus()));
        dto.setType(TransactionBillType.RECHARGE.getType());
        return this.selectSmTransactionBillList(dto);
    }

    /**
     * 查询设备充值失败列表
     *
     * @param userId
     * @return
     */
    @Override
    public List<TransactionBillVO> selectDeviceRechargeFailList(Long userId) {
        if (userId == null) {
            return Collections.emptyList();
        }
        // 查询充值失败列表
        TransactionBillQuery dto = new TransactionBillQuery();
        dto.setUserId(userId);
        dto.setStatus(TransactionBillStatus.SUCCESS.getStatus());
        dto.setDeviceRechargeStatus(TransactionBillDeviceRechargeStatus.FAIL.getStatus());
        dto.setType(TransactionBillType.RECHARGE.getType());
        dto.setIsFinished(false);
        List<TransactionBillVO> list = this.selectSmTransactionBillList(dto);

        if (!CollectionUtils.isEmptyElement(list)) {
            // 修改状态为蓝牙充值
            transactionBillMapper.updateDeviceRechargeStatusByIds(
                    list.stream().map(TransactionBillVO::getBillId).collect(Collectors.toList()),
                    TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus());
        }

        // 查询蓝牙充值列表
        dto.setDeviceRechargeStatus(TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus());
        return selectSmTransactionBillList(dto);
    }


    /**
     * 查询支付结果，直到过期
     *
     * @param bill     订单
     * @param delay    延迟时间
     * @param timeUnit 时间单位
     */
    @Override
    public void refreshPayResultBeforeExpire(TransactionBillVO bill, int delay, TimeUnit timeUnit) {

        // 刷新支付结果
        refreshPayResult(bill);

        // 获取刷新后的订单
        TransactionBillVO newBill = transactionBillMapper.selectSmTransactionBillByBillId(bill.getBillId());
        if (newBill == null) {
            log.error("获取订单{}为空", bill.getBillId());
            return;
        }

        // 未支付成功
        List<String> successList = TransactionBillStatus.asList(TransactionBillStatus.SUCCESS, TransactionBillStatus.SUCCESS_DEPOSIT);
        if (!successList.contains(newBill.getStatus())) {
            if (newBill.getExpireTime() != null && new Date().compareTo(newBill.getExpireTime()) < 0) {
                // 未过期，继续延迟查询
                log.info("{}支付失败，未过期，继续延迟刷新", newBill);
                scheduledExecutorService.schedule(() -> {
                    this.refreshPayResultBeforeExpire(newBill, delay , timeUnit);
                }, delay, timeUnit);
            } else {
                log.info("{}支付失败，过期取消订单", newBill);
                this.cancelRecharge(newBill.getBillNo(), TransactionBillStatus.SYS_CANCELED);
            }
        }
    }

    /**
     * 查询数量并映射
     *
     * @param query     查询条件
     * @param keyMapper 映射方法
     */
    @Override
    public <K> Map<K, BillCountVo> selectCountMap(TransactionBillQuery query, Function<? super BillCountVo, ? extends K> keyMapper) {
        List<BillCountVo> list = this.selectCount(query);
        if (CollectionUtils.isEmptyElement(list)) {
            return Collections.emptyMap();
        }
        return list.stream().collect(Collectors.toMap(keyMapper, item -> item));
    }

    /**
     * 蓝牙充值成功
     *
     * @param billNo
     * @return
     */
    @Override
    public boolean bluetoothRechargeSuccess(String billNo) {
        TransactionBillVO bill = selectSmTransactionBillByBillNo(billNo);
        ServiceUtil.assertion(bill == null, "订单不存在", 10001);
        ServiceUtil.assertion(!TransactionBillType.RECHARGE.getType().equals(bill.getType()), "该订单不是充值订单", 10002);
        ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已充值成功，请勿重复充值", 10003);
        ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单尚未充值成功", 10004);

        // 拉取设备信息
        deviceService.pullDeviceInfo(bill.getDeviceId());

        Integer execute = transactionTemplate.execute(status -> {
            // 更新设备充值状态
            TransactionBill data = new TransactionBill();
            data.setDeviceRechargeStatus(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus());
            data.setOpenMsg("蓝牙充值成功");
            TransactionBillQuery query = new TransactionBillQuery();
            query.setBillId(bill.getBillId());
            query.setType(TransactionBillType.RECHARGE.getType());
            query.setDeviceRechargeStatus(TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus());
            query.setStatus(TransactionBillStatus.SUCCESS.getStatus());
            int result = this.updateByQuery(data, query);
            ServiceUtil.assertion( result != 1, "蓝牙充值回调失败，设备状态已发生改变");

            // 更新套餐使用信息
            DeviceVO device = deviceService.selectById(bill.getDeviceId());
            int updateInfo = this.updateSuitInfoBeforeDevice(bill, device);
            ServiceUtil.assertion(updateInfo != 1, "更新套餐使用信息失败");

            // 修改剩余时间
            boolean addTime = deviceService.addTime(bill.getDeviceId(), transactionBillConverter.toRechargeSeconds(bill), false, "蓝牙充值订单：" + bill.getBillNo());
            ServiceUtil.assertion(!addTime, "修改剩余时间失败");

            return result;
        });

        // 是否成功
        boolean success = execute != null && execute == 1;
        // 成功后操作
        if (success) {
            // 时长变化记录
            this.recordTime(bill, "蓝牙手动充值订单：" + bill.getBillNo());
        }

        return success;
    }

    // 记录时长变化记录
    private void recordTime(TransactionBillVO bill, String reason) {
        scheduledExecutorService.schedule(() -> {
            DeviceVO device = deviceService.selectById(bill.getDeviceId());
            if (device == null) {
                return;
            }
            SmUserVO user = userService.selectSmUserByUserId(bill.getUserId());
            // 根据订单套餐类型记录
            if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) {
                recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, transactionBillConverter.toRechargeSeconds(bill), reason, user, RecordTimeType.TIME.getType()));
            } else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) {
                recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, transactionBillConverter.toRechargeEle(bill).longValue(), reason, user, RecordTimeType.ELE.getType()));
            }
        }, 1L, TimeUnit.SECONDS);
    }

    @Override
    public int refund(BillRefundDTO dto) {
        // 校验订单
        TransactionBillVO bill = this.selectSmTransactionBillByBillId(dto.getBillId());
        ServiceUtil.assertion(bill == null, "待退款的订单不存在");
        ServiceUtil.assertion(dto.getRefundAmount().compareTo(bill.getMoney()) > 0, "退款金额不允许大于订单金额");
        ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "当前订单状态不允许退款");
        ServiceUtil.assertion(bill.getIsFinished() == null || !bill.getIsFinished(), "当前订单未完成，不允许退款");

        // 拼接分成列表
        transactionAssembler.assembleBonusList(bill);
        List<BonusVO> bonusList = bill.getBonusList();
        ServiceUtil.assertion(CollectionUtils.isEmptyElement(bonusList), "当前订单没有收益信息，无法退款");

        Integer result = transactionTemplate.execute(status -> {
            // 修改订单状态
            TransactionBill data = new TransactionBill();
            data.setStatus(TransactionBillStatus.REFUNDING.getStatus());
            TransactionBillQuery billQuery = new TransactionBillQuery();
            billQuery.setBillId(dto.getBillId());
            billQuery.setStatus(TransactionBillStatus.SUCCESS.getStatus());
            billQuery.setIsFinished(true);
            int updateBill = this.updateByQuery(data, billQuery);
            ServiceUtil.assertion(updateBill != 1, "退款时修改订单状态失败，订单状态已发生改变，请刷新后重试");

            // 分成方余额按照比例扣减
            // 按比例计算退款金额
            BigDecimal refundAmount = dto.getRefundAmount();    // 总退款金额
            int updateRefundBonus = this.updateRefundBonus(bonusList, refundAmount, bill.getMoney());
            ServiceUtil.assertion(updateRefundBonus != bonusList.size(), "商户余额更新失败");

            // 修改原订单的退款金额
            int updateRefundAmount = this.addRefundAmount(bill.getBillId(), refundAmount, BigDecimal.ZERO, BigDecimal.ZERO);
            ServiceUtil.assertion(updateRefundAmount != 1, "修改原订单的退款金额失败");

            // 发起退款
            PayBillRefundDTO refundDto = new PayBillRefundDTO();
            refundDto.setPayId(bill.getPayId());
            refundDto.setRefundAmount(dto.getRefundAmount());
            refundDto.setRefundReason(dto.getRefundReason());
            int refund = payBillService.refund(refundDto);
            ServiceUtil.assertion(refund != 1, "退款失败");

            return updateBill;
        });

        return result == null ? 0 : result;
    }

    /**
     * 更新分成的退款金额
     * @param bonusList 分成列表
     * @param refundAmount 本次退款金额
     * @param billAmount 订单总金额
     */
    private int updateRefundBonus(List<BonusVO> bonusList, BigDecimal refundAmount, BigDecimal billAmount) {
        if (CollectionUtils.isEmptyElement(bonusList)) {
            return 0;
        }

        // 构建退款列表
        List<Bonus> refundList = new ArrayList<>(); // 退款的列表
        BigDecimal dividedAmount = BigDecimal.ZERO; // 已分配金额
        for (BonusVO bonus : bonusList) {
            if (bonus == null) {
                continue;
            }
            // 计算扣减金额 = 退款金额 * 分配金额 / 总金额
            BigDecimal bonusRefundAmount = refundAmount.multiply(bonus.getAmount()).divide(billAmount, 2, RoundingMode.HALF_UP);

            Bonus refundBonus = new Bonus();
            refundBonus.setId(bonus.getId());
            refundBonus.setStatus(bonus.getStatus());
            refundBonus.setRefundAmount(bonusRefundAmount);
            refundBonus.setArrivalId(bonus.getArrivalId());
            refundBonus.setArrivalType(bonus.getArrivalType());
            refundBonus.setBillNo(bonus.getBillNo());
            refundBonus.setBillId(bonus.getBillId());
            refundList.add(refundBonus);

            dividedAmount = dividedAmount.add(bonusRefundAmount);
        }
        // 平台吃掉误差
        Bonus platform = refundList.stream().filter(bonus -> bonus.getArrivalType().equals(BonusArrivalType.PLATFORM.getType())).findFirst().orElse(null);
        ServiceUtil.assertion(platform == null, "平台不存在");
        if (dividedAmount.compareTo(refundAmount) != 0) {
            BigDecimal subtract = refundAmount.subtract(dividedAmount);    // 误差值
            platform.setRefundAmount(platform.getRefundAmount().add(subtract));
        }

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

        Integer result = transactionTemplate.execute(status -> {
            int update = 0;

            for (Bonus bonus : refundList) {
                // 根据分成状态来决定是否扣减用户余额（给用户加余额前需要判断分成状态是否为待分成）
                if (BonusStatus.WAIT_DIVIDE.getStatus().equals(bonus.getStatus())) {
                    // 待分成，扣减待分成金额，加上退款金额
                    int refund = bonusService.refundWhenWaitDivide(bonus.getId(), bonus.getRefundAmount());
                    ServiceUtil.assertion(refund != 1, "可退款金额不足");
                } else if (BonusStatus.DIVIDEND.getStatus().equals(bonus.getStatus())) {
                    // 已分成，扣减已分成金额，加上退款金额
                    int refund = bonusService.refundWhenDividend(bonus.getId(), bonus.getRefundAmount());
                    ServiceUtil.assertion(refund != 1, "可退款金额不足");

                    // 扣减分成方余额
                    if (BonusArrivalType.userList().contains(bonus.getArrivalType())) {
                        userService.subtractBalance(bonus.getArrivalId(), bonus.getRefundAmount(), String.format("订单退款:%s", bonus.getBillNo()), RecordBalanceBstType.RECHARGE, bonus.getBillId());
                    } else if (BonusArrivalType.deptList().contains(bonus.getArrivalType())) {
//                    deptService.subtractBalance(bonus.getArrivalId(), bonus.getRefundAmount(), String.format("订单退款:%s", bonus.getBillNo()), RecordBalanceBstType.RECHARGE, bonus.getBillId());
                    }
                }

                update++;
            }

            return update;
        });

        return result == null ? 0 : result;
    }



    @Override
    public int updateByQuery(TransactionBill data, TransactionBillQuery query) {
        if (query == null) {
            return 0;
        }
        return transactionBillMapper.updateByQuery(data, query);
    }

    @Override
    public int refreshPayResult(String billNo) {
        return this.refreshPayResult(this.selectSmTransactionBillByBillNo(billNo));
    }

    @Override
    public int refreshPayResult(List<TransactionBillVO> billList) {
        if (CollectionUtils.isEmptyElement(billList)) {
            return 0;
        }

        // 获取支付订单列表
        PayBillQuery query = new PayBillQuery();
        query.setBstIds(CollectionUtils.map(billList, TransactionBillVO::getBillId));
        query.setBstTypes(PayBillBstType.asList(PayBillBstType.RECHARGE_ORDER, PayBillBstType.RECHARGE_ORDER_DEPOSIT));
        List<PayBillVO> payList = payBillService.selectPayBillList(query);

        // 刷新支付订单结果
        return payBillService.refreshPayResult(payList);
    }

    @Override
    public int addRefundAmount(Long billId, BigDecimal refundAmount, BigDecimal refundMchAmount, BigDecimal refundServiceAmount) {
        if (billId == null) {
            return 0;
        }
        return transactionBillMapper.addRefundAmount(billId, refundAmount, refundMchAmount, refundServiceAmount);
    }

    @Override
    public List<TransactionBillVO> selectUsingBill(Long userId) {
        TransactionBillQuery query = new TransactionBillQuery();
        query.setUserId(userId);
        return this.selectUsingBill(query);
    }

    @Override
    public List<TransactionBillVO> selectUsingBill(TransactionBillQuery query) {
        query.setIsUsing(true);
        query.setType(TransactionBillType.RECHARGE.getType());
        return this.selectSmTransactionBillList(query);
    }

    @Override
    public int batchCloseBillByDevice(List<TransactionBillVO> billList, boolean withDevice, DeviceVO device, BigDecimal totalEle) {
        if (CollectionUtils.isEmptyElement(billList)) {
            return 0;
        }
        EndUseBO bo = new EndUseBO();
        bo.setDevice(device);
        bo.setTotalEle(totalEle);

        if (CollectionUtils.isNotEmptyElement(billList)) {
            Integer result = transactionTemplate.execute(status -> {
                int sum = 0;

                // 关闭订单，智能订单需要退款
                for (TransactionBillVO bill : billList) {
                    bo.setOrder(bill);
                    EndUseVO end = this.endUse(bo, withDevice);
                    ServiceUtil.assertion(end.getResult() != 1, "关闭订单失败:" + bill.getBillNo());
                    sum += end.getResult();
                }

                return sum;
            });

            return result == null ? 0 : result;
        }

        return 0;
    }

    // 结束使用订单
    @Override
    public EndUseVO endUse(EndUseBO bo, boolean withDevice) {
        if (bo == null) {
            return EndUseVO.fail();
        }
        TransactionBillVO bill = bo.getOrder();

        // 智能订单
        if (SuitFeeMode.SMART.getMode().equals(bill.getSuitFeeMode())) {
            // 单次智能
            if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) {
                return this.endSmartUse(bo, withDevice);
            }
            // 分时段智能
            else if (SuitFeeType.timingList().contains(bill.getSuitFeeType())){
                return this.endTimingUse(bo, withDevice);
            }
        }
        // 单次订单
        else if (SuitFeeMode.SINGLE.getMode().equals(bill.getSuitFeeMode())) {
            return this.endUseSingle(bo, withDevice);
        }

        return EndUseVO.fail();
    }

    @Override
    public EndUseVO endUse(EndUseDTO dto, boolean withDevice) {
        EndUseBO bo = transactionBillConverter.toEndUseBO(dto);
        return this.endUse(bo, withDevice);
    }

    @Override
    public <T> List<TransactionAmountVO<T>> selectCommonSumOfMoney(TransactionBillQuery query, String groupBy) {
        return transactionBillMapper.selectCommonSumOfMoney(query, groupBy);
    }

    @Override
    public TransactionBillVO selectOne(TransactionBillQuery query) {
        PageHelper.startPage(1, 1);
        List<TransactionBillVO> list = this.selectSmTransactionBillList(query);
        if (CollectionUtils.isEmptyElement(list)) {
            return null;
        }
        return list.get(0);
    }

    @Override
    public int switchDevice(TransactionBillVO bill, boolean open) {
        ServiceUtil.assertion(bill == null, "订单不存在");
        ServiceUtil.assertion(bill.getIsUsing() == null || !bill.getIsUsing(), "该订单不是正在使用中的订单，无法操作");

        DeviceVO device = deviceService.selectById(bill.getDeviceId());
        ServiceUtil.assertion(device == null, "设备不存在");
        ServiceUtil.assertion(StringUtils.isBlank(device.getMac()), "设备MAC为空，请联系管理员处理");

        if (open) {
            if (SuitFeeType.timingList().contains(bill.getSuitFeeType())) {
                return iotService.open(device, "订单" + bill.getBillNo() + "临时开启") ? 1 : 0;
            } else {
                // 计算设备剩余时长
                LocalDateTime expireTime = device.getExpireTime();
                if (expireTime == null) {
                    return 0;
                }
                Duration between = Duration.between(LocalDateTime.now(), expireTime);
                if (between.getSeconds() > 0) {
                    CommandResponse res = iotService.setTime(device, between.getSeconds(), "订单" + bill.getBillNo() + "临时开启");
                    return res.isSuccess() ? 1 : 0;
                }
            }
        } else {
            return iotService.close(device, "订单" + bill.getBillNo() + "临时关闭") ? 1 : 0;
        }

        return 0;
    }

    @Override
    public TransactionBillVO selectLastOne(TransactionBillQuery query) {
        PageHelper.orderBy("stb.bill_id desc");
        return selectOne(query);
    }

    @Override
    public UserWithdrawServiceVO getUserWithdrawService(Long userId, Long channelId) {
        SmUserVO user = userService.selectSmUserByUserId(userId);
        ChannelWithdrawVO channel = channelWithdrawService.selectChannelWithdrawByChannelId(channelId);
        return this.getUserWithdrawService(user, channel);
    }

    @Override
    public DoPayVO pay(RechargePayBO bo) {
        // 校验
        ServiceUtil.assertion(transactionBillValidator.prePay(bo));

        TransactionBillVO order = bo.getOrder();

        Long lockKey = order.getBillId();
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.PREPAY, lockKey), "当前订单正在支付，请稍后重试");
        try {

            return transactionTemplate.execute(status -> {
                // 修改订单信息
                TransactionBill data = new TransactionBill();
                data.setServiceCharge(order.getServiceCharge());
                data.setArrivalAmount(order.getArrivalAmount());
                TransactionBillQuery query = new TransactionBillQuery();
                query.setStatus(TransactionBillStatus.UNPAID.getStatus());
                query.setBillId(order.getBillId());
                int paying = this.updateByQuery(data, query);
                ServiceUtil.assertion(paying != 1, "订单状态已发生变化，更新失败");

                // 创建支付订单并发起支付
                return payBillService.createPayBill(payBillConverter.toPoByRechargeMoney(bo));
            });
        } finally {
            redisLock.unlock(RedisLockKey.PREPAY, lockKey);
        }
    }

    @Override
    public List<TransactionBillVO> selectWithdrawList(TransactionBillQuery query) {
        query.setType(TransactionBillType.WITHDRAW.getType());
        return transactionBillMapper.selectWithdrawList(query);
    }

    @Override
    public TransactionBillVO selectWithdrawById(Long billId) {
        return transactionBillMapper.selectWithdrawById(billId);
    }

    @Override
    public int selectSimpleCount(TransactionBillQuery query) {
        return transactionBillMapper.selectSimpleCount(query);
    }

    /**
     * 转账成功后
     */
    @Override
    public int onTransferSuccess(TransferVO transfer) {
        if (transfer == null || transfer.getBstId() == null) {
            return 0;
        }

        Integer result = transactionTemplate.execute(status -> {
            // 修改提现状态
            TransactionBill data = new TransactionBill();
            data.setStatus(TransactionBillStatus.WITHDRAW_SUCCESS.getStatus());
            data.setPayTime(transfer.getUpdateTime());
            TransactionBillQuery query = new TransactionBillQuery();
            query.setStatus(TransactionBillStatus.WITHDRAW_PAYING.getStatus());
            query.setBillId(transfer.getBstId());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改提现状态失败，提现状态已发生改变");
            return update;
        });

        return result == null ? 0 : result;
    }

    /**
     * 转账失败后
     */
    @Override
    public int onTransferFail(TransferVO transfer) {
        if (transfer == null || transfer.getBstId() == null) {
            return 0;
        }

        TransactionBillVO withdraw = this.selectWithdrawById(transfer.getBstId());
        if (withdraw == null) {
            return 0;
        }

        Integer result = transactionTemplate.execute(status -> {
            // 修改提现状态
            TransactionBill data = new TransactionBill();
            data.setStatus(TransactionBillStatus.WITHDRAW_FAIL.getStatus());
            TransactionBillQuery query = new TransactionBillQuery();
            query.setStatus(TransactionBillStatus.WITHDRAW_PAYING.getStatus());
            query.setBillId(transfer.getBstId());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "修改提现状态失败，提现状态已发生改变");

            // 将提现金额退回用户
            userService.addBalance(withdraw.getUserId(), withdraw.getMoney(), String.format("提现%s打款失败", withdraw.getBillNo()), RecordBalanceBstType.WITHDRAW, transfer.getBstId());

            return update;
        });

        return result == null ? 0 : result;
    }
}
