package com.red.circle.other.app.command.activity;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO;
import com.red.circle.other.app.dto.clientobject.activity.LotteryPrizeCO;
import com.red.circle.other.app.dto.cmd.activity.LotteryMultiDrawCmd;
import com.red.circle.other.app.service.activity.LotteryPrizeGrantService;
import com.red.circle.other.domain.gateway.user.ability.RegisterDeviceGateway;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryActivity;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryPrize;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryRecord;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicket;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicketRecord;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserCount;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserProgress;
import com.red.circle.other.infra.database.rds.service.activity.LotteryActivityService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryPrizeService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryRecordService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketRecordService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserCountService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserProgressService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryDeviceRecordService;
import com.red.circle.other.infra.database.rds.service.activity.UserActivityRechargeService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.infra.gateway.lottery.LotteryBackpackGateway;
import com.red.circle.other.infra.utils.ZonedDateTimeUtils;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.asserts.lottery.LotteryErrorCode;
import com.red.circle.tool.core.date.TimestampUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 执行连抽（支持动态概率池）.
 *
 * @author system
 * @since 2025-10-20
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LotteryMultiDrawExe {

  private final LotteryActivityService lotteryActivityService;
  private final LotteryPrizeService lotteryPrizeService;
  private final LotteryRecordService lotteryRecordService;
  private final LotteryTicketService lotteryTicketService;
  private final LotteryTicketRecordService lotteryTicketRecordService;
  private final LotteryUserCountService lotteryUserCountService;
  private final LotteryUserProgressService lotteryUserProgressService;
  private final LotteryPrizeGrantService lotteryPrizeGrantService;
  private final RedisService redisService;
  private final RegisterDeviceGateway registerDeviceGateway;
  private final LotteryDeviceRecordService lotteryDeviceRecordService;
  private final UserActivityRechargeService userActivityRechargeService;
  private final WalletGoldClient walletGoldClient;
  private final LotteryBackpackGateway lotteryBackpackGateway;
  private static final Long FIXED_ACTIVITY_ID = 2004471533988167125L;

  @Transactional(rollbackFor = Exception.class)
  public LotteryMultiDrawResultCO execute(LotteryMultiDrawCmd cmd) {
    Long userId = cmd.requiredReqUserId();
    Long activityId = cmd.getActivityId();
    Integer drawCount = cmd.getDrawCount();
    Timestamp now = TimestampUtils.now();

    // 1. 校验活动
    LotteryActivity activity = validateActivity(activityId, now);
    
    // 2. 校验设备唯一性（防止小号薅羊毛）
    checkDeviceUnique(activityId, userId);
    
    // 3. 校验是否启用连抽
    ResponseAssert.isTrue(LotteryErrorCode.MULTI_DRAW_NOT_ENABLED,
        activity.getEnableMultiDraw() != null && activity.getEnableMultiDraw() == 1);
    
    // 4. 查询用户充值金额（用于过滤美元奖励）
    BigDecimal userRechargeAmount = getUserRechargeAmount(userId, activityId);
    
    // 5. 校验抽奖次数限制（需要剩余足够次数）
    validateDrawCount(userId, activityId, activity, drawCount);

    // 6. 校验并扣除抽奖券（连抽消耗更少）
    Integer ticketCost = activity.getMultiDrawTicketCost() != null 
        ? activity.getMultiDrawTicketCost() 
        : drawCount * activity.getTicketCost();

    if (Boolean.TRUE.equals(cmd.getNeedCoins()) &&
            Boolean.TRUE.equals(activity.getNeedCoins())) {
      // 扣除用户金币
      Integer amount = activity.getCoinCost() * (drawCount - 1);
      deductUserGold(userId, activityId, amount);
    } else {
      deductTicket(userId, activityId, ticketCost);
    }

    // 7. 获取或创建用户进度（用于动态概率）
    LotteryUserProgress progress = getOrCreateUserProgress(userId, activityId);

    // 8. 生成批次号
    String batchNo = generateBatchNo();

    // 9. 执行连抽
    List<LotteryDrawResultCO> results = new ArrayList<>();
    boolean guaranteeTriggered = false;
    BigDecimal batchTotalAmount = BigDecimal.ZERO;

    // 每次抽奖都使用动态概率
    for (int i = 0; i < drawCount; i++) {
      // 实时获取用户进度（因为每次抽奖后都会更新）
      LotteryUserProgress currentProgress = lotteryUserProgressService.getById(progress.getId());
      
      // 使用动态概率抽奖
      LotteryPrize prize = drawPrizeWithDynamicProbability(activityId, activity, currentProgress, userRechargeAmount);
      
      // 扣减库存
      if (prize != null) {
        boolean deductSuccess = deductPrizeStock(prize.getId());
        if (!deductSuccess) {
          log.warn("Prize stock deduction failed in multi-draw, prizeId: {}", prize.getId());
          prize = null;
        }
      }
      
      // 保存记录
      LotteryRecord record = saveDrawRecord(userId, activityId, prize, now, batchNo, 2);
      results.add(convertToResultCO(record, prize));

      // 发放奖励
      if (prize != null) {
        lotteryPrizeGrantService.grantPrize(userId, prize, record.getRecordNo());

        if (Objects.equals(prize.getPrizeType(), "FRAGMENTS")) {
          String addBizNo = "lottery_add:" + IdWorker.getTimeId();
          lotteryBackpackGateway.recordFragmentAdd(userId, prize.getPrizeValue().intValue(), "lottery", addBizNo);
        }
      }

      // 临时更新用户进度（用于下次抽奖判断阶段）
      if (prize != null && prize.getPrizeValue() != null) {
        BigDecimal prizeValue = prize.getPrizeValue();
        batchTotalAmount = batchTotalAmount.add(prizeValue);
        
        // 立即更新数据库
        updateUserProgress(progress, prize);
      } else {
        // 没中奖也要更新次数
        lotteryUserProgressService.update()
            .eq(LotteryUserProgress::getId, progress.getId())
            .setSql("total_draw_count = total_draw_count + 1")
            .set(LotteryUserProgress::getLastDrawDate, LocalDate.now())
            .execute();
      }
    }

    // 10. 检查连抽保底
    BigDecimal guaranteeMinAmount = activity.getMultiDrawGuaranteeMinAmount() != null 
        ? activity.getMultiDrawGuaranteeMinAmount() 
        : BigDecimal.ZERO;
    
    if (batchTotalAmount.compareTo(guaranteeMinAmount) < 0 && guaranteeMinAmount.compareTo(BigDecimal.ZERO) > 0) {
      // 触发保底：替换最后一个未中奖的记录
      guaranteeTriggered = true;
      replaceWithGuaranteePrize(results, activity, userId, activityId, now, batchNo, progress);
      log.info("Multi-draw guarantee triggered, userId: {}, activityId: {}, batchAmount: {}", 
          userId, activityId, batchTotalAmount);
    }

    // 11. 更新用户抽奖次数（+drawCount次）
    updateUserDrawCount(userId, activityId, drawCount);

    // 12. 记录设备信息（首次抽奖时）
    recordDeviceAfterDraw(activityId, userId);
    
    // 13. 组装返回结果
    return buildMultiDrawResult(batchNo, results, guaranteeTriggered);
  }

  /**
   * 校验设备唯一性（防止小号薅羊毛）
   */
  private void checkDeviceUnique(Long activityId, Long userId) {
    String fingerprint = registerDeviceGateway.getEnhancedDeviceFingerprintByUserId(userId);

    if (com.red.circle.tool.core.text.StringUtils.isBlank(fingerprint)) {
      log.warn("Failed to get device fingerprint in multi-draw, userId: {}", userId);
      return;
    }

    com.red.circle.other.infra.database.rds.entity.activity.LotteryDeviceRecord existingRecord = lotteryDeviceRecordService
        .getByActivityAndFingerprint(activityId, fingerprint);

    if (existingRecord != null && !existingRecord.getUserId().equals(userId)) {
      log.warn("Duplicate device detected in multi-draw, activityId: {}, currentUser: {}, boundUser: {}, fingerprint: {}",
          activityId, userId, existingRecord.getUserId(), fingerprint);
      ResponseAssert.isTrue(OtherErrorCode.DEVICE_ALREADY_USED, false);
    }
  }

  /**
   * 记录设备信息（抽奖成功后）
   */
  private void recordDeviceAfterDraw(Long activityId, Long userId) {
    try {
      String fingerprint = registerDeviceGateway.getEnhancedDeviceFingerprintByUserId(userId);
      if (com.red.circle.tool.core.text.StringUtils.isNotBlank(fingerprint)) {
        lotteryDeviceRecordService.recordDevice(activityId, userId, fingerprint);
      }
    } catch (Exception e) {
      log.error("Failed to record device in multi-draw, activityId: {}, userId: {}", activityId, userId, e);
    }
  }

  /**
   * 查询用户充值金额
   */
  private BigDecimal getUserRechargeAmount(Long userId, Long activityId) {
    try {
      BigDecimal amount = userActivityRechargeService.getUserTotalAmount(userId, activityId);
      return amount != null ? amount : BigDecimal.ZERO;
    } catch (Exception e) {
      log.error("Failed to get user recharge amount in multi-draw, userId: {}, activityId: {}", userId, activityId, e);
      return BigDecimal.ZERO;
    }
  }

  private LotteryActivity validateActivity(Long activityId, Timestamp now) {
    LotteryActivity activity = lotteryActivityService.getById(activityId);
    ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_NOT_FOUND, activity != null);
    ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_CLOSED, activity.getStatus() == 1);
    ResponseAssert.isTrue(LotteryErrorCode.NOT_IN_ACTIVITY_TIME,
        now.after(activity.getStartTime()) && now.before(activity.getEndTime()));
    return activity;
  }

  private void validateDrawCount(Long userId, Long activityId, LotteryActivity activity, Integer drawCount) {
    if (activity.getDrawCountLimit() == null || activity.getDrawCountLimit() <= 0) {
      return;
    }
    LotteryUserCount userCount = lotteryUserCountService.query()
        .eq(LotteryUserCount::getUserId, userId)
        .eq(LotteryUserCount::getActivityId, activityId)
        .eq(LotteryUserCount::getDrawDate, LocalDate.now())
        .getOne();
    int currentDrawCount = userCount != null ? userCount.getDrawCount() : 0;
    ResponseAssert.isTrue(LotteryErrorCode.DRAW_COUNT_EXHAUSTED,
        currentDrawCount + drawCount <= activity.getDrawCountLimit());
  }

  private LotteryUserProgress getOrCreateUserProgress(Long userId, Long activityId) {
//    String weekKey = ZonedDateTimeUtils.getWeekKey();
    LotteryUserProgress progress = lotteryUserProgressService.query()
        .eq(LotteryUserProgress::getUserId, userId)
        .eq(LotteryUserProgress::getActivityId, activityId)
//        .eq(LotteryUserProgress::getWeekKey, weekKey)
        .getOne();
    if (progress == null) {
      progress = new LotteryUserProgress();
      progress.setUserId(userId);
      progress.setActivityId(activityId);
//      progress.setWeekKey(weekKey);
      progress.setTotalAmount(BigDecimal.ZERO);
      progress.setTotalDrawCount(0);
      progress.setBigPrizeCount(0);
      progress.setCurrentStage("EARLY");
      progress.setLastDrawDate(LocalDate.now());
      lotteryUserProgressService.save(progress);
    }
    return progress;
  }

  private LotteryPrize drawPrizeWithDynamicProbability(Long activityId, LotteryActivity activity, 
                                                       LotteryUserProgress progress, BigDecimal userRechargeAmount) {
    if (activity.getEnableDynamicProbability() == null || activity.getEnableDynamicProbability() != 1) {
      return drawPrizeWithMultiProbability(activityId, progress.getUserId(), userRechargeAmount);
    }
    String stage = determinePrizePool(progress, activity);
    log.debug("Multi-draw stage: {}, userId: {}, drawCount: {}, totalAmount: {}", 
        stage, progress.getUserId(), progress.getTotalDrawCount(), progress.getTotalAmount());
    List<LotteryPrize> prizes = getPrizesByPool(activityId, stage);
    
    // 过滤用户已中过的BADGE类型奖品
    prizes = filterAlreadyWonBadgePrizes(prizes, progress.getUserId(), activityId);
    
    // 过滤美元奖励（充值金额为0的用户不能抽到）
    prizes = filterMoneyPrizes(activityId, prizes, userRechargeAmount);
    
    prizes = filterOverThresholdPrizes(prizes, progress, activity.getWithdrawThreshold());
    if (prizes.isEmpty()) {
      return null;
    }
    for (LotteryPrize prize : prizes) {
      if (prize.getMultiDrawProbability() != null) {
        prize.setProbability(prize.getMultiDrawProbability());
      }
    }
    adjustProbability(prizes, progress, activity.getWithdrawThreshold());
    LotteryPrize selectedPrize = randomSelectByProbability(prizes);
    
    // 如果抽中了BADGE类型奖品，记录到Redis
    if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) {
      markBadgePrizeAsWon(progress.getUserId(), activityId);
    }
    
    return selectedPrize;
  }

  private String determinePrizePool(LotteryUserProgress progress, LotteryActivity activity) {
    int drawCount = progress.getTotalDrawCount();
    BigDecimal totalAmount = progress.getTotalAmount();
    BigDecimal threshold = activity.getWithdrawThreshold() != null ? activity.getWithdrawThreshold() : new BigDecimal("10");
    int earlyMax = activity.getEarlyStageMaxCount() != null ? activity.getEarlyStageMaxCount() : 15;
    int middleMax = activity.getMiddleStageMaxCount() != null ? activity.getMiddleStageMaxCount() : 50;
    BigDecimal lateTrigger = activity.getLateStageTriggerAmount() != null ? activity.getLateStageTriggerAmount() : new BigDecimal("6");
    if (drawCount < earlyMax) {
      return "EARLY";
    }
    BigDecimal gap = threshold.subtract(totalAmount);
    if (drawCount >= middleMax || gap.compareTo(lateTrigger) <= 0) {
      return "LATE";
    }
    return "MIDDLE";
  }

  private List<LotteryPrize> getPrizesByPool(Long activityId, String pool) {
    return lotteryPrizeService.query()
        .eq(LotteryPrize::getActivityId, activityId)
        .gt(LotteryPrize::getRemainingStock, 0)
        .and(wrapper -> wrapper.eq(LotteryPrize::getPrizePool, pool).or().eq(LotteryPrize::getPrizePool, "ALL"))
        .orderByAsc(LotteryPrize::getSortOrder)
        .list();
  }

  private List<LotteryPrize> filterOverThresholdPrizes(List<LotteryPrize> prizes, LotteryUserProgress progress, BigDecimal threshold) {
    if (threshold == null) {
      return prizes;
    }
    BigDecimal currentAmount = progress.getTotalAmount();
    int drawCount = progress.getTotalDrawCount();
    if (currentAmount.compareTo(threshold) >= 0) {
      return prizes.stream().filter(prize -> prize.getPrizeValue().compareTo(BigDecimal.ZERO) == 0).collect(Collectors.toList());
    }
//    if (currentAmount.compareTo(new BigDecimal("5.0")) >= 0 && drawCount < 50) {
//      return prizes.stream().filter(prize -> prize.getPrizeValue().compareTo(new BigDecimal("0.15")) <= 0).collect(Collectors.toList());
//    }
    return prizes.stream().filter(prize -> {
      BigDecimal afterAmount = currentAmount.add(prize.getPrizeValue());
      return afterAmount.compareTo(threshold) <= 0;
    }).collect(Collectors.toList());
  }

  private void adjustProbability(List<LotteryPrize> prizes, LotteryUserProgress progress, BigDecimal threshold) {
    if (threshold == null) {
      return;
    }
    BigDecimal totalAmount = progress.getTotalAmount();
    BigDecimal gap = threshold.subtract(totalAmount);
    for (LotteryPrize prize : prizes) {
      BigDecimal prizeValue = prize.getPrizeValue();
      BigDecimal originalProbability = prize.getProbability();
      BigDecimal diff = gap.subtract(prizeValue).abs();
      if (diff.compareTo(new BigDecimal("0.5")) <= 0) {
        prize.setProbability(originalProbability.multiply(new BigDecimal("2")));
      }
      if (totalAmount.compareTo(new BigDecimal("9")) > 0) {
        if (prizeValue.compareTo(gap) <= 0 && prizeValue.compareTo(new BigDecimal("0.5")) >= 0) {
          prize.setProbability(originalProbability.multiply(new BigDecimal("3")));
        }
      }
    }
  }

  private LotteryPrize randomSelectByProbability(List<LotteryPrize> prizes) {
    BigDecimal totalProbability = prizes.stream().map(LotteryPrize::getProbability).reduce(BigDecimal.ZERO, BigDecimal::add);
    if (totalProbability.compareTo(BigDecimal.ZERO) == 0) {
      return null;
    }
    double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
    double cumulative = 0.0;
    for (LotteryPrize prize : prizes) {
      cumulative += prize.getProbability().doubleValue();
      if (randomValue <= cumulative) {
        return prize;
      }
    }
    return prizes.get(prizes.size() - 1);
  }

  private LotteryPrize drawPrizeWithMultiProbability(Long activityId, Long userId, BigDecimal userRechargeAmount) {
    List<LotteryPrize> prizes = lotteryPrizeService.query()
        .eq(LotteryPrize::getActivityId, activityId)
        .gt(LotteryPrize::getRemainingStock, 0)
        .orderByAsc(LotteryPrize::getSortOrder)
        .list();
    if (prizes.isEmpty()) {
      return null;
    }
    
    // 过滤用户已中过的BADGE类型奖品
    prizes = filterAlreadyWonBadgePrizes(prizes, userId, activityId);
    
    // 过滤美元奖励（充值金额为0的用户不能抽到）
    prizes = filterMoneyPrizes(activityId, prizes, userRechargeAmount);
    
    if (prizes.isEmpty()) {
      return null;
    }
    
    BigDecimal totalProbability = prizes.stream()
        .map(prize -> prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability())
        .reduce(BigDecimal.ZERO, BigDecimal::add);
    double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
    double cumulative = 0.0;
    for (LotteryPrize prize : prizes) {
      BigDecimal probability = prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability();
      cumulative += probability.doubleValue();
      if (randomValue <= cumulative) {
        // 如果抽中了BADGE类型奖品，记录到Redis
        if ("BADGE".equals(prize.getPrizeType())) {
          markBadgePrizeAsWon(userId, activityId);
        }
        return prize;
      }
    }
    
    LotteryPrize lastPrize = prizes.get(prizes.size() - 1);
    // 如果抽中了BADGE类型奖品，记录到Redis
    if (lastPrize != null && "BADGE".equals(lastPrize.getPrizeType())) {
      markBadgePrizeAsWon(userId, activityId);
    }
    return lastPrize;
  }

  private void replaceWithGuaranteePrize(List<LotteryDrawResultCO> results, LotteryActivity activity, Long userId, Long activityId, Timestamp now, String batchNo, LotteryUserProgress progress) {
    int replaceIndex = results.size() - 1;
    for (int i = results.size() - 1; i >= 0; i--) {
      if (!results.get(i).getIsWin()) {
        replaceIndex = i;
        break;
      }
    }
    LotteryPrize guaranteePrize = getGuaranteePrize(activityId, activity.getMultiDrawGuaranteePrizeId());
    if (guaranteePrize != null) {
      deductPrizeStock(guaranteePrize.getId());
      LotteryDrawResultCO oldResult = results.get(replaceIndex);
      LotteryRecord record = lotteryRecordService.query().eq(LotteryRecord::getRecordNo, oldResult.getRecordNo()).getOne();
      if (record != null) {
        record.setPrizeId(guaranteePrize.getId());
        record.setPrizeName(guaranteePrize.getPrizeName());
        record.setPrizeType(guaranteePrize.getPrizeType());
        record.setIsWin(1);
        lotteryRecordService.updateSelectiveById(record);
        results.set(replaceIndex, convertToResultCO(record, guaranteePrize));

        // 发放保底奖励
        lotteryPrizeGrantService.grantPrize(userId, guaranteePrize, record.getRecordNo());
      }

      updateUserProgress(progress, guaranteePrize);
    }

  }

  private LotteryPrize getGuaranteePrize(Long activityId, Long guaranteePrizeId) {
    if (guaranteePrizeId != null) {
      LotteryPrize prize = lotteryPrizeService.getById(guaranteePrizeId);
      if (prize != null && prize.getRemainingStock() > 0) {
        return prize;
      }
    }
    return lotteryPrizeService.query().eq(LotteryPrize::getActivityId, activityId).eq(LotteryPrize::getIsGuaranteePrize, 1).gt(LotteryPrize::getRemainingStock, 0).orderByDesc(LotteryPrize::getPrizeValue).last("LIMIT 1").getOne();
  }

  private void deductTicket(Long userId, Long activityId, Integer ticketCost) {
    List<LotteryTicket> tickets = lotteryTicketService.query()
            .eq(LotteryTicket::getUserId, userId)
            .eq(LotteryTicket::getActivityId, activityId)
            .eq(LotteryTicket::getStatus, 1)
            .gt(LotteryTicket::getRemainingCount, 0)
            .orderByAsc(LotteryTicket::getExpireTime).list();
    int totalRemaining = tickets.stream().mapToInt(LotteryTicket::getRemainingCount).sum();
    ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, totalRemaining >= ticketCost);
    List<LotteryTicketRecord> records = new ArrayList<>();
    int remaining = ticketCost;
    for (LotteryTicket ticket : tickets) {
      if (remaining <= 0) break;
      int deduct = Math.min(remaining, ticket.getRemainingCount());
      boolean updateSuccess = lotteryTicketService.update()
              .eq(LotteryTicket::getId, ticket.getId())
              .eq(LotteryTicket::getRemainingCount, ticket.getRemainingCount())
              .setSql("used_count = used_count + " + deduct)
              .setSql("remaining_count = remaining_count - " + deduct)
              .execute();
      ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, updateSuccess);
      LotteryTicketRecord ticketRecord = new LotteryTicketRecord();
      ticketRecord.setUserId(userId);
      ticketRecord.setTicketId(ticket.getId());
      ticketRecord.setChangeCount(-deduct);
      ticketRecord.setChangeType(2);
      ticketRecord.setRemark("连抽消耗");
      records.add(ticketRecord);
      remaining -= deduct;


      if (Objects.equals(activityId, FIXED_ACTIVITY_ID)) {
        String deductBizNo = "lottery_deduct:" + IdWorker.getTimeId();
        lotteryBackpackGateway.recordTicketDeduct(userId, deduct, "lottery", deductBizNo);
      }
    }
    if (!records.isEmpty()) {
      lotteryTicketRecordService.saveBatch(records);
    }
  }

  private void deductUserGold(Long userId, Long activityId, Integer amount) {
    GoldReceiptCmd build = GoldReceiptCmd.builder()
            .appExpenditure()
            .userId(userId)
            .amount(PennyAmount.ofDollar(amount.longValue()))
            .eventId(activityId)
            .sysOrigin(SysOriginPlatformEnum.LIKEI)
            .origin(GoldOrigin.CP_LOTTERY)
            .build();
    ResponseAssert.requiredSuccess(walletGoldClient.changeBalance(build));
  }

  private boolean deductPrizeStock(Long prizeId) {
    return lotteryPrizeService.update()
        .eq(LotteryPrize::getId, prizeId)
        .gt(LotteryPrize::getRemainingStock, 0)
        .setSql("remaining_stock = remaining_stock - 1")
        .execute();
  }

  private LotteryRecord saveDrawRecord(Long userId, Long activityId, LotteryPrize prize, Timestamp now, String batchNo, Integer drawType) {
    String recordNo = generateRecordNo();
    LotteryRecord record = new LotteryRecord();
    record.setRecordNo(recordNo);
    record.setUserId(userId);
    record.setActivityId(activityId);
    record.setDrawTime(now);
    record.setDrawBatchNo(batchNo);
    record.setDrawType(drawType);
    if (prize != null) {
      record.setPrizeId(prize.getId());
      record.setPrizeName(prize.getPrizeName());
      record.setPrizeType(prize.getPrizeType());
      record.setIsWin(1);
      record.setPrizeStatus(0);
    } else {
      record.setIsWin(0);
    }
    lotteryRecordService.save(record);
    return record;
  }

  private void updateUserDrawCount(Long userId, Long activityId, Integer drawCount) {
    LocalDate today = LocalDate.now();
    boolean updated = lotteryUserCountService.update().eq(LotteryUserCount::getUserId, userId).eq(LotteryUserCount::getActivityId, activityId).eq(LotteryUserCount::getDrawDate, today).setSql("draw_count = draw_count + " + drawCount).execute();
    if (!updated) {
      try {
        LotteryUserCount userCount = new LotteryUserCount();
        userCount.setUserId(userId);
        userCount.setActivityId(activityId);
        userCount.setDrawDate(today);
        userCount.setDrawCount(drawCount);
        lotteryUserCountService.save(userCount);
      } catch (Exception e) {
        lotteryUserCountService.update().eq(LotteryUserCount::getUserId, userId).eq(LotteryUserCount::getActivityId, activityId).eq(LotteryUserCount::getDrawDate, today).setSql("draw_count = draw_count + " + drawCount).execute();
      }
    }
  }

  private void updateUserProgress(LotteryUserProgress progress, LotteryPrize prize) {
    BigDecimal addAmount = prize != null ? prize.getPrizeValue() : BigDecimal.ZERO;
    int addBigPrizeCount = (prize != null && prize.getPrizeValue().compareTo(new BigDecimal("0.5")) >= 0) ? 1 : 0;
    lotteryUserProgressService.update()
        .eq(LotteryUserProgress::getId, progress.getId())
        .setSql("total_amount = total_amount + " + addAmount)
        .setSql("total_draw_count = total_draw_count + 1")
        .setSql("big_prize_count = big_prize_count + " + addBigPrizeCount)
        .set(LotteryUserProgress::getLastDrawDate, LocalDate.now())
        .execute();
  }

  private LotteryDrawResultCO convertToResultCO(LotteryRecord record, LotteryPrize prize) {
    LotteryDrawResultCO result = new LotteryDrawResultCO();
    result.setRecordNo(record.getRecordNo());
    result.setIsWin(record.getIsWin() == 1);
    result.setDrawTime(record.getDrawTime());
    if (prize != null) {
      LotteryPrizeCO prizeCO = new LotteryPrizeCO();
      BeanUtils.copyProperties(prize, prizeCO);
      result.setPrize(prizeCO);
    }
    return result;
  }

  private LotteryMultiDrawResultCO buildMultiDrawResult(String batchNo, List<LotteryDrawResultCO> results, boolean guaranteeTriggered) {
    LotteryMultiDrawResultCO multiResult = new LotteryMultiDrawResultCO();
    multiResult.setBatchNo(batchNo);
    multiResult.setDrawType(2);
    multiResult.setResults(results);
    LotteryMultiDrawResultCO.MultiDrawSummary summary = new LotteryMultiDrawResultCO.MultiDrawSummary();
    BigDecimal totalAmount = results.stream().filter(LotteryDrawResultCO::getIsWin).map(r -> r.getPrize() != null ? r.getPrize().getPrizeValue() : BigDecimal.ZERO).reduce(BigDecimal.ZERO, BigDecimal::add);
    summary.setTotalAmount(totalAmount);
    summary.setGuaranteeTriggered(guaranteeTriggered);
    long winCount = results.stream().filter(LotteryDrawResultCO::getIsWin).count();
    summary.setWinCount((int) winCount);
    long bigPrizeCount = results.stream().filter(LotteryDrawResultCO::getIsWin).filter(r -> r.getPrize() != null && r.getPrize().getPrizeValue().compareTo(new BigDecimal("0.5")) >= 0).count();
    summary.setBigPrizeCount((int) bigPrizeCount);
    multiResult.setSummary(summary);
    return multiResult;
  }

  private String generateBatchNo() {
    return "BATCH" + System.currentTimeMillis() + String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
  }

  private String generateRecordNo() {
    return "LT" + System.currentTimeMillis() + String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
  }

  /**
   * 过滤用户已中过的BADGE类型奖品.
   * 
   * @param prizes 奖品列表
   * @param userId 用户ID
   * @return 过滤后的奖品列表
   */
  private List<LotteryPrize> filterAlreadyWonBadgePrizes(List<LotteryPrize> prizes, Long userId, Long activityId) {
    if (prizes == null || prizes.isEmpty()) {
      return prizes;
    }

    return prizes.stream()
        .filter(prize -> {
          // 如果不是BADGE类型，直接通过
          if (!"BADGE".equals(prize.getPrizeType())) {
            return true;
          }
          
          // 检查用户是否已经中过这个BADGE
          String redisKey = "lottery:badge:won:" + userId + ":" + activityId;
          String won = redisService.getString(redisKey);
          
          if (won != null) {
            log.info("User already won this BADGE prize in multi-draw, filtering it out. userId: {}, prizeId: {}, prizeName: {}", 
                userId, prize.getId(), prize.getPrizeName());
            return false;
          }
          
          return true;
        })
        .collect(Collectors.toList());
  }

  /**
   * 过滤美元奖励（根据充值金额过滤不同档位的奖品）
   */
  private List<LotteryPrize> filterMoneyPrizes(Long activityId, List<LotteryPrize> prizes, BigDecimal userRechargeAmount) {
    if (prizes == null || prizes.isEmpty()) {
      return prizes;
    }

    // 从Redis获取活动充值门槛，默认500
    String thresholdKey = "lottery:recharge:threshold:" + activityId;
    String thresholdStr = redisService.getString(thresholdKey);
    BigDecimal rechargeThreshold = thresholdStr != null ? new BigDecimal(thresholdStr) : new BigDecimal("500");

    // 如果用户充值金额 >= 门槛，不需要过滤
    if (userRechargeAmount.compareTo(rechargeThreshold) >= 0) {
      return prizes;
    }

    // 如果用户充值金额 <= 0，过滤掉所有MONEY类型奖品
    if (userRechargeAmount.compareTo(BigDecimal.ZERO) <= 0) {
        return prizes.stream()
                .filter(prize -> !"MONEY".equals(prize.getPrizeType()))
                .collect(Collectors.toList());
    }

    // 如果用户充值金额 > 0 且 < 门槛，过滤掉 prizeValue >= 10 的MONEY奖品
      return prizes.stream()
              .filter(prize -> !"MONEY".equals(prize.getPrizeType()) ||
                      prize.getPrizeValue().compareTo(new BigDecimal("10")) < 0)
              .collect(Collectors.toList());
  }

  /**
   * 标记用户已中BADGE类型奖品（永久记录）.
   * 
   * @param userId 用户ID
   */
  private void markBadgePrizeAsWon(Long userId, Long activityId) {
    String redisKey = "lottery:badge:won:" + userId + ":" + activityId;
    // 永久存储，表示该用户已经中过这个BADGE
    redisService.setString(redisKey, "1");
  }

}
