package com.bizzan.bitrade.service;

import com.bizzan.bitrade.constant.TransactionType;
import com.bizzan.bitrade.dao.AirdropRecordDao;
import com.bizzan.bitrade.dao.StakingConfigDao;
import com.bizzan.bitrade.dao.StakingOrderDao;
import com.bizzan.bitrade.dao.StakingRecordDao;
import com.bizzan.bitrade.dto.StakingOrderVO;
import com.bizzan.bitrade.entity.*;
import com.bizzan.bitrade.entity.MemberWallet;
import com.bizzan.bitrade.service.Base.BaseService;
import com.bizzan.bitrade.util.MessageResult;
import com.bizzan.bitrade.util.RedisManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;

/**
 * 质押业务服务，处理用户质押、解冻释放等核心逻辑
 *
 * @author auto
 * @date 2026-05-17
 */
@Service
@Slf4j
public class StakingService extends BaseService {

    /** 质押中（锁定期内） */
    private static final int ORDER_STATUS_STAKING = 0;

    @Autowired
    private StakingConfigDao stakingConfigDao;

    @Autowired
    private StakingOrderDao stakingOrderDao;

    @Autowired
    private StakingRecordDao stakingRecordDao;

    @Autowired
    private AirdropRecordDao airdropRecordDao;

    @Autowired
    private MemberWalletService memberWalletService;

    @Autowired
    private MemberTransactionService memberTransactionService;

    @Lazy
    @Autowired
    private LocaleMessageSourceService msService;

    @Autowired
    private CoinService coinService;

    @Autowired
    private RedisManager redisManager;

    /**
     * IDO 预售：USDT 按现货价闪兑为质押币后走质押流程
     */
    @Transactional(rollbackFor = Exception.class)
    public MessageResult stakeWithUsdt(Long memberId, Long configId, BigDecimal usdtAmount) {
        StakingConfig config = stakingConfigDao.findOne(configId);
        if (config == null || config.getStatus() != 1) {
            return MessageResult.error(msService.getMessage("STAKING_CONFIG_NOT_FOUND"));
        }
        if (usdtAmount == null || usdtAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return MessageResult.error(msService.getMessage("STAKING_AMOUNT_INVALID"));
        }

        BigDecimal price = resolveSpotPriceUsdt(config.getCoinUnit());
        if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) {
            return MessageResult.error(msService.getMessage("STAKING_PRICE_UNAVAILABLE"));
        }

        Coin coin = resolveCoinByUnit(config.getCoinUnit());
        if (coin == null) {
            return MessageResult.error(msService.getMessage("COIN_ILLEGAL"));
        }
        int scale = coin.getWithdrawScale() > 0 ? coin.getWithdrawScale() : 8;
        BigDecimal coinAmount = usdtAmount.divide(price, scale, RoundingMode.DOWN);

        MessageResult validateResult = validateAmount(config, coinAmount);
        if (validateResult.getCode() != 0) {
            return validateResult;
        }

        MemberWallet usdtWallet = memberWalletService.findByCoinUnitAndMemberId("USDT", memberId);
        BigDecimal usdtBalance = usdtWallet != null ? usdtWallet.getBalance() : BigDecimal.ZERO;
        if (usdtBalance.compareTo(usdtAmount) < 0) {
            return MessageResult.error(msService.getMessage("STAKING_USDT_INSUFFICIENT"));
        }

        String stakeUnit = coin.getUnit();
        MessageResult swapResult = memberWalletService.flashSwapUsdtToCoin(
                memberId, stakeUnit, usdtAmount, price);
        if (swapResult.getCode() != 0) {
            return swapResult;
        }

        return executeStake(memberId, configId, config, stakeUnit, coinAmount);
    }

    /**
     * 从 Redis ExchangeNew2:latestPrice 解析币种 USDT 价（field 如 ibitusdt）
     */
    @Transactional(readOnly = true)
    public BigDecimal resolveSpotPriceUsdt(String coinUnit) {
        if (coinUnit == null || coinUnit.trim().isEmpty()) {
            return null;
        }
        String unit = coinUnit.trim();
        BigDecimal price = redisManager.getSpotPriceUsdt(unit);
        if (price != null && price.compareTo(BigDecimal.ZERO) > 0) {
            return price;
        }
        Coin coin = resolveCoinByUnit(unit);
        if (coin != null && coin.getUnit() != null) {
            price = redisManager.getSpotPriceUsdt(coin.getUnit());
            if (price != null && price.compareTo(BigDecimal.ZERO) > 0) {
                return price;
            }
        }
        return null;
    }

    /** 按 unit 查 coin，兼容大小写 */
    public Coin resolveCoinByUnit(String coinUnit) {
        if (coinUnit == null || coinUnit.trim().isEmpty()) {
            return null;
        }
        String unit = coinUnit.trim();
        Coin coin = coinService.findByUnit(unit);
        if (coin != null) {
            return coin;
        }
        coin = coinService.findByUnit(unit.toUpperCase());
        if (coin != null) {
            return coin;
        }
        return coinService.findByUnit(unit.toLowerCase());
    }

    /**
     * 用户质押：冻结余额，创建质押订单和流水
     *
     * @param memberId 用户ID
     * @param configId 质押配置ID
     * @param amount   质押金额
     * @return 操作结果
     */
    @Transactional(rollbackFor = Exception.class)
    public MessageResult stake(Long memberId, Long configId, BigDecimal amount) {
        StakingConfig config = stakingConfigDao.findOne(configId);
        if (config == null || config.getStatus() != 1) {
            return MessageResult.error(msService.getMessage("STAKING_CONFIG_NOT_FOUND"));
        }

        MessageResult validateResult = validateAmount(config, amount);
        if (validateResult.getCode() != 0) {
            return validateResult;
        }

        MemberWallet fundWallet = memberWalletService.findByCoinUnitAndMemberId(config.getCoinUnit(), memberId);
        BigDecimal currentBalance = fundWallet != null ? fundWallet.getBalance() : BigDecimal.ZERO;
        if (currentBalance.compareTo(amount) < 0) {
            return MessageResult.error(msService.getMessage("STAKING_INSUFFICIENT_BALANCE"));
        }

        Coin coin = resolveCoinByUnit(config.getCoinUnit());
        String stakeUnit = coin != null ? coin.getUnit() : config.getCoinUnit();
        return executeStake(memberId, configId, config, stakeUnit, amount);
    }

    /** 资金账户扣款并创建质押订单（调用前须已完成余额/金额校验） */
    private MessageResult executeStake(Long memberId, Long configId, StakingConfig config, String coinUnit,
                                       BigDecimal amount) {
        MessageResult freezeResult = memberWalletService.stakeFundToLocked(memberId, coinUnit, amount);
        if (freezeResult.getCode() != 0) {
            return freezeResult;
        }

        Date stakingTime = new Date();
        Date unlockTime = calcUnlockTime(stakingTime, config.getLockDays());

        StakingOrder order = buildStakingOrder(memberId, configId, coinUnit, amount, stakingTime, unlockTime);
        stakingOrderDao.save(order);

        StakingRecord record = buildStakingRecord(memberId, order.getId(), coinUnit, amount, 0, "用户质押");
        stakingRecordDao.save(record);

        return MessageResult.success("质押成功");
    }

    /**
     * 查询用户质押订单列表（分页）
     *
     * @param memberId 用户ID
     * @param pageNo   页码（从1开始）
     * @param pageSize 每页条数
     * @return 质押订单分页数据
     */
    @Transactional(readOnly = true)
    public Page<StakingOrder> getUserStakingOrders(Long memberId, int pageNo, int pageSize) {
        return getUserStakingOrders(memberId, pageNo, pageSize, null);
    }

    /**
     * 查询用户指定状态的质押订单列表（分页）
     *
     * @param memberId 用户ID
     * @param pageNo   页码（从1开始）
     * @param pageSize 每页条数
     * @param statuses 状态列表，为空时查询全部
     * @return 质押订单分页数据
     */
    @Transactional(readOnly = true)
    public Page<StakingOrder> getUserStakingOrders(Long memberId, int pageNo, int pageSize, List<Integer> statuses) {
        PageRequest pageable = new PageRequest(pageNo - 1, pageSize);
        if (statuses != null && !statuses.isEmpty()) {
            return stakingOrderDao.findByMemberIdAndStatusInOrderByCreateTimeDesc(memberId, statuses, pageable);
        }
        return stakingOrderDao.findByMemberIdOrderByCreateTimeDesc(memberId, pageable);
    }

    /**
     * 查询用户质押流水列表（分页）
     *
     * @param memberId 用户ID
     * @param pageNo   页码（从1开始）
     * @param pageSize 每页条数
     * @return 质押流水分页数据
     */
    @Transactional(readOnly = true)
    public Page<StakingRecord> getUserStakingRecords(Long memberId, int pageNo, int pageSize) {
        PageRequest pageable = new PageRequest(pageNo - 1, pageSize);
        return stakingRecordDao.findByMemberIdOrderByCreateTimeDesc(memberId, pageable);
    }

    /**
     * 查询所有质押订单（admin 分页）
     *
     * @param pageNo   页码
     * @param pageSize 每页条数
     * @return 所有质押订单分页
     */
    @Transactional(readOnly = true)
    public Page<StakingOrderVO> getAllStakingOrders(int pageNo, int pageSize,
                                                    Integer status, Long memberId, String coinUnit,
                                                    Boolean pendingAirdropToday) {
        PageRequest pageable = new PageRequest(pageNo - 1, pageSize, Sort.Direction.DESC, "createTime");
        Date[] todayRange = getTodayRangeGmt8();
        Specification<StakingOrder> spec = buildOrderSpec(status, memberId, coinUnit, pendingAirdropToday, todayRange);
        Page<StakingOrder> page = stakingOrderDao.findAll(spec, pageable);
        List<StakingOrderVO> content = page.getContent().stream()
                .map(order -> toStakingOrderVO(order, todayRange))
                .collect(Collectors.toList());
        return new PageImpl<>(content, pageable, page.getTotalElements());
    }

    private Specification<StakingOrder> buildOrderSpec(Integer status, Long memberId, String coinUnit,
                                                       Boolean pendingAirdropToday, Date[] todayRange) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (status != null) {
                predicates.add(cb.equal(root.get("status"), status));
            }
            if (memberId != null) {
                predicates.add(cb.equal(root.get("memberId"), memberId));
            }
            if (StringUtils.hasText(coinUnit)) {
                predicates.add(cb.equal(root.get("coinUnit"), coinUnit.trim()));
            }
            if (Boolean.TRUE.equals(pendingAirdropToday) && todayRange != null) {
                predicates.add(cb.equal(root.get("status"), ORDER_STATUS_STAKING));
                Subquery<Long> distributedToday = query.subquery(Long.class);
                Root<AirdropRecord> ar = distributedToday.from(AirdropRecord.class);
                distributedToday.select(ar.get("id"));
                distributedToday.where(
                        cb.equal(ar.get("stakingOrderId"), root.get("id")),
                        cb.between(ar.get("createTime"), todayRange[0], todayRange[1])
                );
                predicates.add(cb.not(cb.exists(distributedToday)));
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }

    private StakingOrderVO toStakingOrderVO(StakingOrder order, Date[] todayRange) {
        boolean isStaking = order.getStatus() != null && order.getStatus() == ORDER_STATUS_STAKING;
        if (!isStaking) {
            return StakingOrderVO.from(order, null, false);
        }
        boolean distributedToday = hasAirdropDistributedToday(order.getId(), todayRange);
        return StakingOrderVO.from(order, distributedToday, !distributedToday);
    }

    /** 指定质押订单今日是否已发放过空投（仅质押中订单调用） */
    private boolean hasAirdropDistributedToday(Long stakingOrderId, Date[] todayRange) {
        if (stakingOrderId == null) {
            return false;
        }
        return airdropRecordDao.existsByStakingOrderIdAndCreateTimeBetween(
                stakingOrderId, todayRange[0], todayRange[1]);
    }

    /** 今日起止时间 [00:00:00, 23:59:59.999]（GMT+8） */
    private Date[] getTodayRangeGmt8() {
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        Date start = cal.getTime();
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 999);
        Date end = cal.getTime();
        return new Date[] { start, end };
    }

    /**
     * 查询所有质押流水（admin 分页）
     *
     * @param pageNo   页码
     * @param pageSize 每页条数
     * @return 所有质押流水分页
     */
    @Transactional(readOnly = true)
    public Page<StakingRecord> getAllStakingRecords(int pageNo, int pageSize) {
        PageRequest pageable = new PageRequest(pageNo - 1, pageSize, Sort.Direction.DESC, "createTime");
        return stakingRecordDao.findAll(pageable);
    }

    /**
     * 查询所有质押配置
     *
     * @return 配置列表
     */
    @Transactional(readOnly = true)
    public List<StakingConfig> getAllConfigs() {
        return stakingConfigDao.findAll();
    }

    /**
     * 查询启用中的质押配置
     *
     * @return 启用配置列表
     */
    @Transactional(readOnly = true)
    public List<StakingConfig> getEnabledConfigs() {
        return stakingConfigDao.findByStatus(1);
    }

    /**
     * 获取 ID 最小且启用中的质押配置（IDO 预售页默认展示）
     *
     * @return 配置，无启用配置时返回 null
     */
    @Transactional(readOnly = true)
    public StakingConfig getMinIdEnabledConfig() {
        return stakingConfigDao.findFirstByStatusOrderByIdAsc(1);
    }

    /**
     * 保存质押配置（新增或修改）
     *
     * @param config 质押配置
     * @return 保存后的配置
     */
    @Transactional(rollbackFor = Exception.class)
    public StakingConfig saveConfig(StakingConfig config) {
        if (!StringUtils.hasText(config.getProductName())) {
            throw new IllegalArgumentException("产品名称不能为空");
        }
        config.setProductName(config.getProductName().trim());
        if (config.getReleaseType() != null && config.getReleaseType() == 1) {
            if (config.getReleasePeriod() == null || config.getReleasePeriod() < 1) {
                throw new IllegalArgumentException("分批释放时分批周期必填且须大于0");
            }
            config.setReleaseRate(calcReleaseRateByPeriod(config.getReleasePeriod()));
        } else {
            config.setReleaseRate(BigDecimal.ZERO);
        }
        validateAirdropConfig(config);
        return stakingConfigDao.save(config);
    }

    /** 校验自动空投相关配置 */
    private void validateAirdropConfig(StakingConfig config) {
        int auto = config.getAutoAirdropEnabled() != null ? config.getAutoAirdropEnabled() : 0;
        if (auto != 1) {
            return;
        }
        Integer distType = config.getAirdropDistributeType();
        if (distType == null || (distType != 0 && distType != 1)) {
            throw new IllegalArgumentException("启用自动空投时请选择发放方式：固定金额或按质押比例");
        }
        if (distType == 0) {
            if (config.getAirdropFixedAmount() == null
                    || config.getAirdropFixedAmount().compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("固定发放时空投金额须大于0");
            }
            config.setAirdropRatio(BigDecimal.ZERO);
        } else {
            if (config.getAirdropRatio() == null
                    || config.getAirdropRatio().compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("按比例发放时空投比例须大于0");
            }
            config.setAirdropFixedAmount(BigDecimal.ZERO);
        }
    }

    /**
     * 根据质押配置与订单金额计算应发放的空投数量
     */
    public static BigDecimal calcAirdropAmount(StakingOrder order, StakingConfig config) {
        if (order == null || config == null || order.getAmount() == null) {
            return BigDecimal.ZERO;
        }
        Integer distType = config.getAirdropDistributeType();
        if (distType != null && distType == 1) {
            BigDecimal ratio = config.getAirdropRatio() != null ? config.getAirdropRatio() : BigDecimal.ZERO;
            return order.getAmount().multiply(ratio).setScale(8, RoundingMode.DOWN);
        }
        return config.getAirdropFixedAmount() != null ? config.getAirdropFixedAmount() : BigDecimal.ZERO;
    }

    /**
     * 按分批周期计算每批释放比例：100 ÷ 周期（%），保留4位小数向上取整，再转为库内小数。
     * 例：周期 50 天 → 2% → 0.02
     */
    public static BigDecimal calcReleaseRateByPeriod(int releasePeriod) {
        BigDecimal percent = new BigDecimal("100")
                .divide(BigDecimal.valueOf(releasePeriod), 4, RoundingMode.CEILING);
        return percent.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
    }

    /**
     * 查询用户在某币种上的活跃质押剩余总量（质押中+释放中的未释放部分）
     *
     * @param memberId 用户ID
     * @param coinUnit 币种
     * @return 活跃质押剩余量
     */
    @Transactional(readOnly = true)
    public BigDecimal getUserActiveStakedAmount(Long memberId, String coinUnit) {
        return stakingOrderDao.sumActiveAmountByMemberIdAndCoinUnit(memberId, coinUnit);
    }

    /**
     * 查询用户在某币种上累计已释放总量（供资产页「释放总量」展示）
     */
    @Transactional(readOnly = true)
    public BigDecimal getUserReleasedAmount(Long memberId, String coinUnit) {
        if (memberId == null || coinUnit == null || coinUnit.trim().isEmpty()) {
            return BigDecimal.ZERO;
        }
        return stakingOrderDao.sumReleasedAmountByMemberIdAndCoinUnit(memberId, coinUnit.trim());
    }

    /**
     * 查询平台某币种当前总活跃质押量（供按比例空投计算）
     *
     * @param coinUnit 币种
     * @return 平台总活跃质押量
     */
    @Transactional(readOnly = true)
    public BigDecimal getTotalActiveStakedAmount(String coinUnit) {
        return stakingOrderDao.sumTotalActiveAmountByCoinUnit(coinUnit);
    }

    /**
     * 检查用户是否有活跃质押（供空投资格校验使用）
     *
     * @param memberId 用户ID
     * @return 是否有活跃质押
     */
    @Transactional(readOnly = true)
    public boolean hasActiveStaking(Long memberId) {
        return stakingOrderDao.existsActiveStakingByMemberId(memberId);
    }

    /**
     * 定时任务：将锁定期已到期的订单状态更新为解冻释放中
     */
    @Transactional(rollbackFor = Exception.class)
    public void processUnlockOrders() {
        // 查询所有到期的质押中订单
        List<StakingOrder> orders = stakingOrderDao.findOrdersToUnlock(new Date());
        for (StakingOrder order : orders) {
            stakingOrderDao.updateStatusToReleasing(order.getId());
            log.info("[质押解冻] 订单ID={} 用户ID={} 币种={} 进入释放阶段", order.getId(), order.getMemberId(), order.getCoinUnit());
        }
    }

    /**
     * 定时任务：对解冻释放中的订单执行本次周期的释放操作
     * 一次性释放：立即全额释放；分批释放：按周期判断是否到达释放时机
     * 返回本次实际发生释放的用户ID列表，供调用方在事务外发送 MQ 通知
     *
     * @return 本次实际释放成功的用户ID列表（去重）
     */
    public List<Long> processReleaseOrders() {
        // 查询所有解冻释放中的订单
        List<StakingOrder> orders = stakingOrderDao.findByStatus(1);
        List<Long> releasedMemberIds = new java.util.ArrayList<>();
        for (StakingOrder order : orders) {
            try {
                // 每笔订单独立事务，返回实际释放的用户ID（无释放则返回 null）
                Long memberId = doRelease(order);
                if (memberId != null && !releasedMemberIds.contains(memberId)) {
                    releasedMemberIds.add(memberId);
                }
            } catch (Exception e) {
                log.error("[质押释放] 订单ID={} 释放异常：{}", order.getId(), e.getMessage(), e);
            }
        }
        return releasedMemberIds;
    }

    /**
     * 对单个质押订单执行释放，使用独立事务防止单笔失败影响全局
     * 实际发生释放时返回用户ID，无释放（未到期/余额不足/跳过）返回 null
     *
     * @param order 质押订单
     * @return 实际释放成功的用户ID，未释放则返回 null
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public Long doRelease(StakingOrder order) {
        // 1. 查询质押配置
        StakingConfig config = stakingConfigDao.findOne(order.getConfigId());
        if (config == null) {
            log.warn("[质押释放] 订单ID={} 对应配置不存在，跳过", order.getId());
            return null;
        }

        // 2. 分批释放时检查是否到达本批次释放时机
        if (config.getReleaseType() == 1) {
            if (!isReleaseCycleReached(order, config.getReleasePeriod())) {
                return null;
            }
        }

        // 3. 计算本次释放金额
        BigDecimal remaining = order.getAmount().subtract(order.getReleasedAmount());
        BigDecimal releaseAmount = calcReleaseAmount(order, config, remaining);
        if (releaseAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }

        // 4. 查询用户钱包
        MemberWallet wallet = memberWalletService.findByCoinUnitAndMemberId(order.getCoinUnit(), order.getMemberId());
        if (wallet == null) {
            log.warn("[质押释放] 订单ID={} 用户钱包不存在", order.getId());
            return null;
        }

        // 5. 质押释放（减少 stakingBalance，增加可用 balance，与 frozenBalance 完全隔离）
        int releaseRet = memberWalletService.releaseFromStaking(wallet, releaseAmount);
        if (releaseRet <= 0) {
            log.warn("[质押释放] 订单ID={} 质押余额不足，跳过释放", order.getId());
            return null;
        }

        // 6. 更新质押订单已释放金额（全部释放后自动标记为已完成）
        stakingOrderDao.updateReleasedAmount(order.getId(), releaseAmount, new Date());

        // 7. 创建质押释放流水
        StakingRecord record = buildStakingRecord(order.getMemberId(), order.getId(),
                order.getCoinUnit(), releaseAmount, 1, "质押到期释放");
        stakingRecordDao.save(record);

        // 8. 创建资金流水
        MemberTransaction transaction = buildMemberTransaction(order.getMemberId(), releaseAmount,
                order.getCoinUnit(), TransactionType.STAKING_RELEASE);
        memberTransactionService.save(transaction);

        log.info("[质押释放] 订单ID={} 用户ID={} 释放金额={} 币种={}", order.getId(), order.getMemberId(), releaseAmount, order.getCoinUnit());
        // 返回用户ID，由调用方在事务提交后发送 MQ 通知
        return order.getMemberId();
    }

    // ========================= 私有辅助方法 =========================

    /** 校验质押金额合法性 */
    private MessageResult validateAmount(StakingConfig config, BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return MessageResult.error(msService.getMessage("STAKING_AMOUNT_INVALID"));
        }
        if (amount.compareTo(config.getMinAmount()) < 0) {
            return MessageResult.error(msService.getMessage("STAKING_AMOUNT_BELOW_MIN"));
        }
        if (config.getMaxAmount().compareTo(BigDecimal.ZERO) > 0
                && amount.compareTo(config.getMaxAmount()) > 0) {
            return MessageResult.error(msService.getMessage("STAKING_AMOUNT_ABOVE_MAX"));
        }
        return MessageResult.success();
    }

    /** 计算解冻时间（质押时间 + 锁定天数） */
    private Date calcUnlockTime(Date stakingTime, int lockDays) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(stakingTime);
        cal.add(Calendar.DAY_OF_MONTH, lockDays);
        return cal.getTime();
    }

    /** 判断分批释放是否到达本批释放周期 */
    private boolean isReleaseCycleReached(StakingOrder order, int releasePeriod) {
        // 以上次释放时间（若无则以解冻开始时间）为基准计算间隔
        Date lastRelease = order.getLastReleaseTime() != null
                ? order.getLastReleaseTime()
                : order.getUnlockTime();
        long diffMillis = System.currentTimeMillis() - lastRelease.getTime();
        long diffDays = diffMillis / (1000L * 60 * 60 * 24);
        return diffDays >= releasePeriod;
    }

    /** 计算本次释放金额：一次性全额或按比例分批（最后一批释放剩余） */
    private BigDecimal calcReleaseAmount(StakingOrder order, StakingConfig config, BigDecimal remaining) {
        if (config.getReleaseType() == 0) {
            // 一次性全额释放
            return remaining;
        }
        // 分批释放：按配置比例计算，不超过剩余量
        BigDecimal batchAmount = order.getAmount().multiply(config.getReleaseRate());
        return batchAmount.compareTo(remaining) >= 0 ? remaining : batchAmount;
    }

    /** 构建质押订单 */
    private StakingOrder buildStakingOrder(Long memberId, Long configId, String coinUnit,
                                           BigDecimal amount, Date stakingTime, Date unlockTime) {
        StakingOrder order = new StakingOrder();
        order.setMemberId(memberId);
        order.setConfigId(configId);
        order.setCoinUnit(coinUnit);
        order.setAmount(amount);
        order.setReleasedAmount(BigDecimal.ZERO);
        order.setStatus(0);
        order.setStakingTime(stakingTime);
        order.setUnlockTime(unlockTime);
        return order;
    }

    /** 构建质押流水 */
    private StakingRecord buildStakingRecord(Long memberId, Long orderId, String coinUnit,
                                             BigDecimal amount, int type, String remark) {
        StakingRecord record = new StakingRecord();
        record.setMemberId(memberId);
        record.setOrderId(orderId);
        record.setCoinUnit(coinUnit);
        record.setAmount(amount);
        record.setType(type);
        record.setRemark(remark);
        return record;
    }

    /** 构建资金流水（member_transaction） */
    private MemberTransaction buildMemberTransaction(Long memberId, BigDecimal amount,
                                                     String coinUnit, TransactionType type) {
        MemberTransaction transaction = new MemberTransaction();
        transaction.setMemberId(memberId);
        transaction.setAmount(amount);
        transaction.setSymbol(coinUnit);
        transaction.setType(type);
        transaction.setFee(BigDecimal.ZERO);
        transaction.setDiscountFee("0");
        transaction.setRealFee("0");
        transaction.setCreateTime(new Date());
        return transaction;
    }
}
