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

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.LotteryPrizeCO;
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
import com.red.circle.other.app.service.activity.LotteryPrizeGrantService;
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.utils.ZonedDateTimeUtils;
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.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

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-18
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LotteryDrawExe {

  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;

  @Transactional(rollbackFor = Exception.class)
  public LotteryDrawResultCO execute(LotteryDrawCmd cmd) {
    Long userId = cmd.requiredReqUserId();
    Long activityId = cmd.getActivityId();
    Timestamp now = TimestampUtils.now();

    // 1. 校验活动
    LotteryActivity activity = validateActivity(activityId, now);

    // 2. 校验抽奖次数限制
    validateDrawCount(userId, activityId, activity);

    // 3. 校验并扣除抽奖券（如果需要）
    deductTicket(userId, activity.getTicketCost());

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

    // 5. 执行抽奖算法（动态概率）
    LotteryPrize prize = drawPrizeWithDynamicProbability(activityId, activity, progress);

    // 6. 扣减奖品库存（使用悲观锁防止超卖）
    if (prize != null) {
      boolean deductSuccess = deductPrizeStock(prize.getId());
      if (!deductSuccess) {
        log.warn("Prize stock deduction failed, prizeId: {}", prize.getId());
        prize = null;
      }
    }

    // 7. 保存抽奖记录
    LotteryRecord record = saveDrawRecord(userId, activityId, prize, now, null, 1);

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

    // 9. 更新用户抽奖次数
    updateUserDrawCount(userId, activityId);

    // 10. 更新用户进度（累加金额和次数）
    updateUserProgress(progress, prize);

    // 11. 组装返回结果
    return convertToResultCO(record, prize);
  }

  /**
   * 校验活动状态和时间.
   */
  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) {
    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 drawCount = userCount != null ? userCount.getDrawCount() : 0;
    ResponseAssert.isTrue(LotteryErrorCode.DRAW_COUNT_EXHAUSTED,
        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) {
    // 判断是否启用动态概率
    if (activity.getEnableDynamicProbability() == null || activity.getEnableDynamicProbability() != 1) {
      // 未启用动态概率，使用普通抽奖
      return drawPrizeNormal(activityId, progress.getUserId());
    }

    // 1. 判断用户所处阶段
    String stage = determinePrizePool(progress, activity);
    log.info("User stage: {}, userId: {}, drawCount: {}, totalAmount: {}", 
        stage, progress.getUserId(), progress.getTotalDrawCount(), progress.getTotalAmount());

    // 2. 查询对应奖品池的奖品
    List<LotteryPrize> prizes = getPrizesByPool(activityId, stage);

    // 3. 过滤用户已中过的BADGE类型奖品
    prizes = filterAlreadyWonBadgePrizes(prizes, progress.getUserId());

    // 4. 过滤会超过提现门槛的大奖
    prizes = filterOverThresholdPrizes(prizes, progress, activity.getWithdrawThreshold());

    if (prizes.isEmpty()) {
      return null;
    }

    // 5. 动态调整概率
    adjustProbability(prizes, progress, activity.getWithdrawThreshold());

    // 6. 执行抽奖
    LotteryPrize selectedPrize = randomSelectByProbability(prizes);
    
    // 7. 如果抽中了BADGE类型奖品，记录到Redis
    if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) {
      markBadgePrizeAsWon(progress.getUserId(), selectedPrize.getId());
    }
    
    return selectedPrize;
  }

  /**
   * 普通抽奖（不使用动态概率）.
   */
  private LotteryPrize drawPrizeNormal(Long activityId, Long userId) {
    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);

    if (prizes.isEmpty()) {
      return null;
    }

    LotteryPrize selectedPrize = randomSelectByProbability(prizes);
    
    // 如果抽中了BADGE类型奖品，记录到Redis
    if (selectedPrize != null && "BADGE".equals(selectedPrize.getPrizeType())) {
      markBadgePrizeAsWon(userId, selectedPrize.getId());
    }
    
    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");

    // 前期池：1-15次
    if (drawCount < earlyMax) {
      return "EARLY";
    }

    // 后期池：次数>=50 或 距离门槛<=6美元
    BigDecimal gap = threshold.subtract(totalAmount);
    if (drawCount >= middleMax || gap.compareTo(lateTrigger) <= 0) {
      return "LATE";
    }

    // 中期池：16-49次
    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) {
      log.info("User reached threshold, no more prizes this week. userId: {}, amount: {}", 
          progress.getUserId(), currentAmount);
      // 只允许发虚拟物品
      return prizes.stream()
          .filter(prize -> prize.getPrizeValue().compareTo(BigDecimal.ZERO) == 0)
          .collect(Collectors.toList());
    }

    // 特殊情况：前期（<50次）已经拿到>=5美元，进入"慢速模式"
    if (currentAmount.compareTo(new BigDecimal("5.0")) >= 0 && drawCount < 50) {
      log.info("User got big prize early, entering slow mode. userId: {}, amount: {}, count: {}", 
          progress.getUserId(), currentAmount, drawCount);
      
      // 只允许0.1美元和虚拟物品（prize_value<=0.15）
      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) {
        // 差距在0.5美元内，概率翻倍
        prize.setProbability(originalProbability.multiply(new BigDecimal("2")));
        log.debug("Boost prize probability x2, prizeName: {}, gap: {}", prize.getPrizeName(), gap);
      }

      // 如果用户已经很接近门槛（>9美元），大幅提升能凑整的小奖概率
      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")));
          log.debug("Boost prize probability x3 (near threshold), prizeName: {}", prize.getPrizeName());
        }
      }
    }
  }

  /**
   * 按概率随机选择奖品.
   */
  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 void deductTicket(Long userId, Integer ticketCost) {
    List<LotteryTicket> tickets = lotteryTicketService.query()
        .eq(LotteryTicket::getUserId, userId)
        .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 (!records.isEmpty()) {
      lotteryTicketRecordService.saveBatch(records);
    }
  }

  /**
   * 扣减奖品库存.
   */
  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) {
    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 + 1")
        .execute();

    if (!updated) {
      try {
        LotteryUserCount userCount = new LotteryUserCount();
        userCount.setUserId(userId);
        userCount.setActivityId(activityId);
        userCount.setDrawDate(today);
        userCount.setDrawCount(1);
        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 + 1")
            .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 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) {
    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 + ":" + prize.getId();
          String won = redisService.getString(redisKey);
          
          if (won != null) {
            log.info("User already won this BADGE prize, filtering it out. userId: {}, prizeId: {}, prizeName: {}", 
                userId, prize.getId(), prize.getPrizeName());
            return false;
          }
          
          return true;
        })
        .collect(Collectors.toList());
  }

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

  /**
   * 转换为结果CO.
   */
  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;
  }

}
