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

import com.red.circle.common.business.core.enums.ReceiptType;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.common.business.enums.DiamondOrigin;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.infra.database.rds.enums.CacheKeysEnum;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.app.dto.clientobject.user.invite.InviteRedPacketCarouselAwardsCO;
import com.red.circle.other.infra.common.user.RedPacketInviteUserCommon;
import com.red.circle.other.infra.database.cache.entity.user.InviteUserRedPacketCarouselAwardsCache;
import com.red.circle.other.infra.database.cache.entity.user.InviteUserRedPacketConfigCache;
import com.red.circle.other.infra.database.cache.service.user.RedPacketInviteUserCacheService;
import com.red.circle.other.infra.database.rds.entity.user.user.InviteRedPacketDrawLog;
import com.red.circle.other.infra.database.rds.entity.user.user.InviteRedPacketRecord;
import com.red.circle.other.infra.database.rds.entity.user.user.LatestMobileDevice;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.infra.database.rds.service.user.user.InviteRedPacketDrawLogService;
import com.red.circle.other.infra.database.rds.service.user.user.InviteRedPacketRecordService;
import com.red.circle.other.infra.database.rds.service.user.user.InviteTargetTransitBalanceService;
import com.red.circle.other.infra.database.rds.service.user.user.InviteWithdrawTransitBalanceService;
import com.red.circle.other.infra.enums.user.user.InviteRedPacketDrawTypeEnum;
import com.red.circle.other.infra.enums.user.user.InviteRedPacketStatusEnum;
import com.red.circle.wallet.inner.endpoint.wallet.WalletDiamondClient;
import com.red.circle.wallet.inner.model.cmd.DiamondReceiptCmd;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 抽奖.
 * </p>
 *
 * @author pengshigang
 * @since 2023-12-20
 */
@Component
@RequiredArgsConstructor
public class InviteNewLotteryExe {

  private static final Random random = new Random();
  private final RedisService redisService;
  private final WalletDiamondClient walletDiamondClient;
  private final LatestMobileDeviceService latestMobileDeviceService;
  private final RedPacketInviteUserCommon redPacketInviteUserCommon;
  private final InviteRedPacketRecordService inviteRedPacketRecordService;
  private final InviteRedPacketDrawLogService inviteRedPacketDrawLogService;
  private final RedPacketInviteUserCacheService redPacketInviteUserCacheService;
  private final InviteTargetTransitBalanceService inviteTargetTransitBalanceService;
  private final InviteWithdrawTransitBalanceService inviteWithdrawTransitBalanceService;

  public InviteRedPacketCarouselAwardsCO execute(AppExtCommand cmd) {

    try {

      InviteRedPacketRecord redPacket = getUserRedPacketByUndone(cmd);
      ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, redPacket);

      LatestMobileDevice device = latestMobileDeviceService.getByUserId(cmd.getReqUserId());
      ResponseAssert.notNull(CommonErrorCode.DEVICE_UNAVAILABLE, device);
      ResponseAssert.isFalse(CommonErrorCode.DEVICE_UNAVAILABLE,
          StringUtils.isBlank(device.getIp()) || StringUtils.isBlank(device.getImei()));

      ResponseAssert.isTrue(CommonErrorCode.NOT_REPEATABLE, redisService.lock(lockKey(cmd), 30));

      // 获得剩余抽奖次数
      Long lotteryCount = getRedPacketLotteryCount(redPacket);
      ResponseAssert.isTrue(CommonErrorCode.LACK_OF_RESOURCES, lotteryCount > 0);

      List<InviteUserRedPacketCarouselAwardsCache> awardsList = getRedPacketAwards(redPacket);
      ResponseAssert.notEmpty(CommonErrorCode.DATA_ERROR, awardsList);

      // 先抽取"立即提现"奖项看看是否能中
      InviteUserRedPacketCarouselAwardsCache award = new InviteUserRedPacketCarouselAwardsCache();
      if (Boolean.TRUE.equals(isWinnWithdraw(redPacket))) {

        award.setQuantity(redPacket.getCurrentAmount());
        award.setType(InviteRedPacketDrawTypeEnum.WITHDRAWAL.name());
        award.setTypeDesc(InviteRedPacketDrawTypeEnum.WITHDRAWAL.getDesc());

        // 修改为提前结束
        redPacketBeforeComplete(redPacket);

        // 添加抽奖记录
        addDrawLog(cmd, redPacket, award, device);
        return buildGameRedPacketInviteCarouselAwards(award);
      }

      // 随机抽取一个奖励
      int awardIndex = random.nextInt(awardsList.size());
      award = extractAwardsCache(awardsList, awardIndex, redPacket.getId());
      usdProcess(award, redPacket);
      targetProcess(award, cmd);
      diamondProcess(award, cmd);

      // 判断是否为最后一个奖项是则将红包修改为正常结束
      if (CollectionUtils.isEmpty(awardsList)) {
        redPacketComplete(redPacket);
      }

      // 添加抽奖记录
      addDrawLog(cmd, redPacket, award, device);
      return buildGameRedPacketInviteCarouselAwards(award);
    } finally {
      redisService.unlock(lockKey(cmd));
    }

  }

  /**
   * 是否中“立即提现奖项”.
   */
  private Boolean isWinnWithdraw(InviteRedPacketRecord redPacket) {

    String sysOrigin = redPacket.getSysOrigin();

    InviteUserRedPacketConfigCache configCache = getInviteConfig(sysOrigin);
    if (Objects.isNull(configCache)) {
      return Boolean.FALSE;
    }

    // 不论全局抽奖次数+1
    Long allLotteryCount = redPacketInviteUserCacheService.incrLotteryCount(sysOrigin);

    // 本轮红包邀请人数不达标
    if (configCache.getWithdrawThresholdCount() > redPacket.getInviteMemberCount()) {
      return Boolean.FALSE;
    }

    // 每withdrawTriggerProportion 人次中会出现一次“立即提现”奖项
    int withdrawTriggerProportion = configCache.getWithdrawTriggerProportion();

    // “立即提现”已经被抽走
    if (Boolean.FALSE.equals(redPacketInviteUserCacheService.existWithdrawPrizes(sysOrigin))) {

      // 没有“立即提现奖项”,且已经达到全局抽象极限则重置“立即提现奖项”与全局抽奖次数
      if (allLotteryCount >= withdrawTriggerProportion) {

        redPacketInviteUserCacheService.resetWithdrawPrizes(sysOrigin);
        redPacketInviteUserCacheService.resetLotteryCount(sysOrigin);
      }
      return Boolean.FALSE;
    }

    // 最后一次且还存在"立即提现"奖项则中奖,且全部重置.
    if (allLotteryCount >= withdrawTriggerProportion) {

      redPacketInviteUserCacheService.resetWithdrawPrizes(sysOrigin);
      redPacketInviteUserCacheService.resetLotteryCount(sysOrigin);
      return Boolean.TRUE;
    }

    // 获得随机数, 1或者withdrawTriggerProportion 都是中奖数字
    int randomCount = ThreadLocalRandom.current().nextInt(1, withdrawTriggerProportion + 1);
    if (randomCount == 1 || randomCount == withdrawTriggerProportion) {
      redPacketInviteUserCacheService.receiveWithdrawPrizes(sysOrigin);
      return Boolean.TRUE;
    }

    return Boolean.FALSE;
  }

  private InviteUserRedPacketConfigCache getInviteConfig(String sysOrigin) {
    return redPacketInviteUserCommon.getInviteConfig(sysOrigin);
  }

  /**
   * 减少奖项.
   */
  private void reduceAward(Long redPacketId,
      List<InviteUserRedPacketCarouselAwardsCache> awardsList, int index) {
    awardsList.remove(index);
    redPacketInviteUserCacheService.cacheRedPacketAwards(redPacketId, awardsList);
  }

  private static InviteRedPacketCarouselAwardsCO buildGameRedPacketInviteCarouselAwards(
      InviteUserRedPacketCarouselAwardsCache award) {
    InviteRedPacketCarouselAwardsCO awardsCO = new InviteRedPacketCarouselAwardsCO();
    awardsCO.setQuantity(award.getQuantity());
    awardsCO.setType(award.getType());
    awardsCO.setTypeDesc(award.getTypeDesc());
    return awardsCO;
  }

  /**
   * 正常完成.
   */
  private void redPacketComplete(InviteRedPacketRecord redPacket) {

    inviteRedPacketRecordService.updateStatusById(redPacket.getId(),
        InviteRedPacketStatusEnum.COMPLETE.name());

    // 完成则将当前获得的金额放入临时提现账户
    inviteWithdrawTransitBalanceService.incr(redPacket.getUserId(), redPacket.getSysOrigin(),
        redPacket.getCurrentAmount());
    setRedisUserCount(redPacket.getUserId());
  }

  /**
   * 提前完成.
   */
  private void redPacketBeforeComplete(InviteRedPacketRecord redPacket) {

    inviteRedPacketRecordService.updateStatusById(redPacket.getId(),
        InviteRedPacketStatusEnum.BEFORE_COMPLETE.name());

    // 提前完成表示抽中了立即提现,所以将当前获得的金额放入临时提现账户
    inviteWithdrawTransitBalanceService.incr(redPacket.getUserId(), redPacket.getSysOrigin(),
        redPacket.getCurrentAmount());
    setRedisUserCount(redPacket.getUserId());
  }

  private void setRedisUserCount(Long UserID){
    LatestMobileDevice latestMobileDevice = latestMobileDeviceService.getByUserId(UserID);
    //修改缓存让满额加一
    redisService.increment(CacheKeysEnum.COLLECT_THE_AMOUNT_OF_RED_PACKETS.getAppendKey(UserID));
    //设备满额加一
    redisService.increment(CacheKeysEnum.COLLECT_THE_AMOUNT_OF_RED_PACKETS.getAppendKey(latestMobileDevice.getImei()));
    //ip
    redisService.increment(CacheKeysEnum.COLLECT_THE_AMOUNT_OF_RED_PACKETS.getAppendKey(latestMobileDevice.getIp()));
    //当天统计数
    redisService.increment(CacheKeysEnum.THE_NUMBER_OF_RED_ENVELOPES_COLLECTED_TODAY.getAppendKey(UserID), 1, TimeUnit.DAYS);
  }


  /**
   * 添加抽奖记录.
   */
  private void addDrawLog(AppExtCommand cmd, InviteRedPacketRecord redPacket,
      InviteUserRedPacketCarouselAwardsCache award, LatestMobileDevice device) {

    // 抽奖次数-1
    Long remainFrequency = redPacketInviteUserCacheService.decrRedPacketLotteryCount(
        redPacket.getId());

    // 保存抽奖记录
    InviteRedPacketDrawLog drawLog = new InviteRedPacketDrawLog();
    drawLog.setId(IdWorkerUtils.getId());
    drawLog.setSysOrigin(cmd.getReqSysOrigin().getOrigin());
    drawLog.setRedPacketId(redPacket.getId());
    drawLog.setUserId(redPacket.getUserId());
    drawLog.setAwardType(award.getType());
    drawLog.setAwardNumber(award.getQuantity());
    drawLog.setTotalAmount(redPacket.getTotalAmount());
    drawLog.setCurrentAmount(redPacket.getCurrentAmount());
    drawLog.setRemainFrequency(Optional.ofNullable(remainFrequency).orElse(0L));
    drawLog.setIp(device.getIp());
    drawLog.setImei(device.getImei());
    inviteRedPacketDrawLogService.save(drawLog);
  }

  /**
   * 中USD奖励.
   */
  private void usdProcess(InviteUserRedPacketCarouselAwardsCache award,
      InviteRedPacketRecord redPacket) {

    if (!Objects.equals(award.getType(), InviteRedPacketDrawTypeEnum.USD.name())) {
      return;
    }

    redPacket.setCurrentAmount(redPacket.getCurrentAmount().add(award.getQuantity()));
    inviteRedPacketRecordService.incrCurrentAmountById(redPacket.getId(), award.getQuantity());

  }

  /**
   * 中目标奖励.
   */
  private void targetProcess(InviteUserRedPacketCarouselAwardsCache award, AppExtCommand cmd) {

    if (!Objects.equals(award.getType(), InviteRedPacketDrawTypeEnum.TARGET.name())) {
      return;
    }

    inviteTargetTransitBalanceService.incr(cmd.getReqUserId(), cmd.getReqSysOrigin().getOrigin(),
        award.getQuantity());
  }

  /**
   * 中钻石奖励.
   */
  private void diamondProcess(InviteUserRedPacketCarouselAwardsCache award, AppExtCommand cmd) {

    if (!Objects.equals(award.getType(), InviteRedPacketDrawTypeEnum.DIAMOND.name())) {
      return;
    }

    walletDiamondClient.changeBalanceAsync(new DiamondReceiptCmd()
        .setConsumeId(Objects.toString(cmd.getReqUserId()))
        .setTrackId(cmd.getReqUserId())
        .setUserId(cmd.getReqUserId())
        .setSysOrigin(SysOriginPlatformEnum.valueOf(cmd.getReqSysOrigin().getOrigin()))
        .setOriginUserId(cmd.getReqUserId())
        .setOrigin(DiamondOrigin.INVITE_USER_RED_PACKET_LOTTERY)
        .setAmount(award.getQuantity())
        .setCreateTime(TimestampUtils.now())
        .setType(ReceiptType.INCOME));
  }


  /**
   * 获得奖励.
   */
  private InviteUserRedPacketCarouselAwardsCache extractAwardsCache(
      List<InviteUserRedPacketCarouselAwardsCache> awardsList, int prizeIndex, Long redPacketId) {

    InviteUserRedPacketCarouselAwardsCache result;

    // 只有一个奖项了则直接返回
    if (awardsList.size() == 1) {
      result = awardsList.get(0);
      reduceAward(redPacketId, awardsList, 0);
      return result;
    }

    // 获得剩余现金奖项个数
    int usdCount = 0;
    for (InviteUserRedPacketCarouselAwardsCache inviteUserRedPacketCarouselAwardsCache : awardsList) {
      if (Objects.equals(inviteUserRedPacketCarouselAwardsCache.getType(),
          InviteRedPacketDrawTypeEnum.USD.name())) {
        usdCount += 1;
      }
    }
    // 不止一个现金奖项或者没有现金奖项则随机抽取
    result = awardsList.get(prizeIndex);
    if (usdCount != 1 || !Objects.equals(result.getType(),
        InviteRedPacketDrawTypeEnum.USD.name())) {
      reduceAward(redPacketId, awardsList, prizeIndex);
      return result;
    }

    // 只有一个现金奖项但有其他奖项时, 先抽取其他奖项
    if (prizeIndex == 0) {
      result = awardsList.get(1);
      reduceAward(redPacketId, awardsList, 1);
      return result;
    }
    result = awardsList.get(prizeIndex - 1);
    reduceAward(redPacketId, awardsList, prizeIndex - 1);
    return result;

  }

  private List<InviteUserRedPacketCarouselAwardsCache> getRedPacketAwards(
      InviteRedPacketRecord redPacket) {
    return redPacketInviteUserCacheService.getRedPacketAwards(redPacket.getId());
  }

  private Long getRedPacketLotteryCount(InviteRedPacketRecord redPacket) {
    return redPacketInviteUserCacheService.getRedPacketLotteryCount(redPacket.getId());
  }

  private InviteRedPacketRecord getUserRedPacketByUndone(AppExtCommand cmd) {
    return inviteRedPacketRecordService.getUserRedPacketByUndone(cmd.getReqUserId());
  }

  private String lockKey(AppExtCommand cmd) {
    return "GAME_INVITE_NEW_LOTTERY:" + cmd.getReqUserId();
  }

}
