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

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
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.app.dto.clientobject.user.invite.InviteRedPacketUserInfoCO;
import com.red.circle.other.app.dto.cmd.user.user.InviteUserRedPacketCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserProfile;
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.dto.user.InviteUserCheckAuthDTO;
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.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.enums.user.user.InviteRedPacketDrawTypeEnum;
import com.red.circle.other.infra.enums.user.user.InviteRedPacketStatusEnum;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.DateUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 开红包.
 * </p>
 *
 * @author pengshigang
 * @since 2023-12-20
 */
@Log4j2
@Service
@AllArgsConstructor
public class InviteNewOpenRedPacketExe {

  private static final Random random = new Random();
  private final RedisService redisService;
  private final UserProfileGateway userProfileGateway;
  private final RedPacketInviteUserCommon redPacketInviteUserCommon;
  private final LatestMobileDeviceService latestMobileDeviceService;
  private final InviteRedPacketRecordService inviteRedPacketRecordService;
  private final InviteRedPacketDrawLogService inviteRedPacketDrawLogService;
  private final RedPacketInviteUserCacheService redPacketInviteUserCacheService;

  public InviteRedPacketUserInfoCO execute(InviteUserRedPacketCmd cmd) {

    InviteUserCheckAuthDTO checkAuth = getUserCheckAuth(cmd);
    ResponseAssert.isFalse(CommonErrorCode.SYSTEM_IS_DOWN, checkAuth.getClosed());
    ResponseAssert.isTrue(CommonErrorCode.NOT_REPEATABLE, checkAuth.getRemovableRedPacket());
    ResponseAssert.isTrue(CommonErrorCode.CAN_NOT_RECEIVE, checkAuth.getShowInviteRedPacket());

    try {

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

      // 加载配置
      InviteUserRedPacketConfigCache configCache = getInviteConfig(cmd);
      List<InviteUserRedPacketCarouselAwardsCache> awardsCaches = getCarouselAwardsCaches(cmd);

      // 解析配置的转盘奖项都有什么内容并基于这个组装本次红包奖项
      boolean existCashAwards = Boolean.FALSE;
      Set<InviteUserRedPacketCarouselAwardsCache> otherAwards = Sets.newHashSet();
      for (InviteUserRedPacketCarouselAwardsCache awards : awardsCaches) {
        if (Objects.equals(awards.getType(), InviteRedPacketDrawTypeEnum.USD.name())) {
          existCashAwards = Boolean.TRUE;
        }
        if (Objects.equals(awards.getType(), InviteRedPacketDrawTypeEnum.DIAMOND.name()) ||
            Objects.equals(awards.getType(), InviteRedPacketDrawTypeEnum.TARGET.name())) {
          otherAwards.add(awards);
        }
      }

      // 组装红包数据
      InviteRedPacketRecord redPacket = saveRedPacket(cmd, checkAuth, configCache);

      // 生成现金奖项并缓存
      List<InviteUserRedPacketCarouselAwardsCache> awards = generateCashAwardsAndCache(configCache,
          existCashAwards, redPacket);

      // 生成其他奖项并缓存
      awards.addAll(generateOtherAwardsAndCache(configCache, otherAwards));
      Collections.shuffle(awards);

      // 保存抽奖记录
      saveDrawLog(checkAuth, redPacket);

      redPacketInviteUserCacheService.cacheRedPacketAwards(redPacket.getId(), awards);

      return buildGameInviteUserRedPacket(redPacket);

    } finally {
      redisService.unlock(lockKey(cmd));
    }
  }

  private InviteUserCheckAuthDTO getUserCheckAuth(InviteUserRedPacketCmd cmd) {
    return redPacketInviteUserCommon.getUserCheckAuth(cmd.getReqUserId(),
        cmd.getInviteCode());
  }

  /**
   * 保存抽奖记录.
   */
  private void saveDrawLog(InviteUserCheckAuthDTO checkAuth, InviteRedPacketRecord redPacket) {
    InviteRedPacketDrawLog drawLog = new InviteRedPacketDrawLog();
    drawLog.setId(IdWorkerUtils.getId());
    drawLog.setSysOrigin(redPacket.getSysOrigin());
    drawLog.setRedPacketId(redPacket.getId());
    drawLog.setUserId(redPacket.getUserId());
    drawLog.setAwardType(InviteRedPacketDrawTypeEnum.OPEN_RED_PACKET.name());
    drawLog.setAwardNumber(redPacket.getCurrentAmount());
    drawLog.setTotalAmount(redPacket.getTotalAmount());
    drawLog.setCurrentAmount(redPacket.getCurrentAmount());
    drawLog.setRemainFrequency(0L);
    drawLog.setImei(checkAuth.getImei());
    drawLog.setIp(checkAuth.getIp());
    inviteRedPacketDrawLogService.save(drawLog);
  }

  /**
   * 组装返回数据.
   */
  private InviteRedPacketUserInfoCO buildGameInviteUserRedPacket(
      InviteRedPacketRecord redPacket) {

    UserProfile userProfile = userProfileGateway.getByUserId(redPacket.getUserId());
    InviteRedPacketUserInfoCO redPacketInfoCO = new InviteRedPacketUserInfoCO();
    redPacketInfoCO.setCountdown(
            TimestampUtils.between(TimestampUtils.now(), redPacket.getExpiresTime()).getSeconds());
    redPacketInfoCO.setTotalAmount(redPacket.getTotalAmount());
    redPacketInfoCO.setCurrentAmount(redPacket.getCurrentAmount());
    redPacketInfoCO.setRemainAmount(BigDecimal.valueOf(redPacket.getTotalAmount())
        .subtract(redPacket.getCurrentAmount()));
    redPacketInfoCO.setThisInviteCode(userProfile.getAccount());
    redPacketInfoCO.setResidueNumber(0L);
    return redPacketInfoCO;
  }

  /**
   * 分配不均匀现金奖励.
   */
  public static List<BigDecimal> generateCashShare(BigDecimal residueAmount, int numberOfPackets) {

    if (numberOfPackets <= 1) {
      return Lists.newArrayList(residueAmount);
    }


    // 根据红包份额扣除每份的0.01
    BigDecimal detainBase = BigDecimal.valueOf(numberOfPackets * 0.01);
    if (residueAmount.compareTo(detainBase) > 0) {
      residueAmount = residueAmount.subtract(detainBase);
    } else {
      ResponseAssert.failure(CommonErrorCode.DATA_ERROR);
    }

    List<BigDecimal> redPackets = new ArrayList<>();

    for (int i = 0; i < numberOfPackets - 1; i++) {
      // 使用两倍均值法，确保每个红包的金额都是不同的
      double max = residueAmount.doubleValue() / (numberOfPackets - i);
      double amount = random.nextDouble() * max;
      BigDecimal packet = BigDecimal.valueOf(amount).setScale(2, RoundingMode.DOWN);

      // 将红包金额添加到列表(加上被扣除的每份0.01,保证红包只少有0.01)
      redPackets.add(packet.add(BigDecimal.valueOf(0.01)));

      // 更新总金额
      residueAmount = residueAmount.subtract(packet);
    }

    // 加上被扣除的每份0.01,保证红包只少有0.01
    redPackets.add(residueAmount.add(BigDecimal.valueOf(0.01)));
    return redPackets;
  }

  /**
   * 生成其他奖项.
   */
  private List<InviteUserRedPacketCarouselAwardsCache> generateOtherAwardsAndCache(
      InviteUserRedPacketConfigCache configCache,
      Set<InviteUserRedPacketCarouselAwardsCache> otherAwards) {

    if (CollectionUtils.isEmpty(otherAwards)) {
      return Lists.newArrayList();
    }

    int otherNumber = getRandomInt(configCache.getOtherNumberMin().intValue(),
        configCache.getOtherNumberMax().intValue());

    List<InviteUserRedPacketCarouselAwardsCache> typeList = new ArrayList<>(otherAwards);

    List<InviteUserRedPacketCarouselAwardsCache> listOtherShare = Lists.newArrayList();
    for (int i = 0; i < otherNumber; i++) {
      listOtherShare.add(typeList.get(random.nextInt(typeList.size())));
    }
    return listOtherShare;
  }

  /**
   * 生成现金奖项.
   */
  private List<InviteUserRedPacketCarouselAwardsCache> generateCashAwardsAndCache(
      InviteUserRedPacketConfigCache configCache, boolean existCashAwards,
      InviteRedPacketRecord redPacket) {

    if (!existCashAwards) {
      return Lists.newArrayList();
    }
    // 生成现金抽奖次数
    Long cashNumber = getCashNumber(configCache);

    // 待收集金额 = 总额 - 红包当前额度
    BigDecimal residueAmount = new BigDecimal(Objects.toString(redPacket.getTotalAmount()))
        .subtract(redPacket.getCurrentAmount());

    if (cashNumber <= 0 || BigDecimal.ZERO.compareTo(residueAmount) >= 0) {
      return Lists.newArrayList();
    }

    List<BigDecimal> cashShares = generateCashShare(residueAmount, cashNumber.intValue());
    if (CollectionUtils.isEmpty(cashShares)) {
      return Lists.newArrayList();
    }

    return cashShares.stream().map(cash -> {
      InviteUserRedPacketCarouselAwardsCache awardsCache = new InviteUserRedPacketCarouselAwardsCache();
      awardsCache.setQuantity(cash);
      awardsCache.setType(InviteRedPacketDrawTypeEnum.USD.name());
      awardsCache.setTypeDesc(InviteRedPacketDrawTypeEnum.USD.getDesc());
      return awardsCache;
    }).collect(Collectors.toList());
  }

  private Long getCashNumber(InviteUserRedPacketConfigCache configCache) {
    return getRandomLong(configCache.getCashNumberMin(), configCache.getCashNumberMax());
  }

  private List<InviteUserRedPacketCarouselAwardsCache> getCarouselAwardsCaches(
      InviteUserRedPacketCmd cmd) {
    List<InviteUserRedPacketCarouselAwardsCache> awardsCaches =
        redPacketInviteUserCommon.listCarouselAwardsConfig(cmd.getReqSysOrigin().getOrigin());
    ResponseAssert.notEmpty(CommonErrorCode.CONFIGURATION_ERROR, awardsCaches);
    return awardsCaches;
  }

  /**
   * 保存红包数据.
   */
  private InviteRedPacketRecord saveRedPacket(InviteUserRedPacketCmd cmd,
      InviteUserCheckAuthDTO checkAuth, InviteUserRedPacketConfigCache configCache) {

    // 生成红包初始金额
    BigDecimal currentAmount = getCurrentAmount(configCache);

    InviteRedPacketRecord redPacket = new InviteRedPacketRecord();

    redPacket.setId(IdWorkerUtils.getId());
    redPacket.setSysOrigin(cmd.getReqSysOrigin().getOrigin());
    redPacket.setUserId(cmd.getReqUserId());
    redPacket.setStatus(InviteRedPacketStatusEnum.UNDONE.name());
    redPacket.setRedPacketSource(cmd.getSource());
    redPacket.setTotalAmount(configCache.getTotalAmount());
    redPacket.setCurrentAmount(currentAmount);
    redPacket.setDateNumber(ZonedDateTimeAsiaRiyadhUtils.nowDateToInt());
    redPacket.setIp(checkAuth.getIp());
    redPacket.setImei(checkAuth.getImei());
    if (Objects.nonNull(checkAuth.getInviter())) {
      redPacket.setInviteUserId(checkAuth.getInviter().getId());
    }
    redPacket.setViolationOrNot(getViolationOrNot(redPacket.getUserId(),redPacket.getInviteUserId(),cmd.getReqSysOrigin().getOrigin()));
    redPacket.setExpiresTime(TimestampUtils.nowPlusDays(1));
    redPacket.setCreateTime(TimestampUtils.now());
    redPacket.setCreateUser(cmd.getReqUserId());
    redPacket.setInviteMemberCount(0L);
    inviteRedPacketRecordService.save(redPacket);
    return redPacket;
  }

  private Boolean getViolationOrNot(Long userId, Long inviterUserId,String sysOrigin) {
    Set<Long> userIdList= latestMobileDeviceService.getIpOrImeiUserId(userId);
    for(Long id:userIdList){
      if (Objects.nonNull(inviteRedPacketRecordService.getViolationOrNotStatus(id,Boolean.TRUE,sysOrigin))) {
        return Boolean.FALSE;
      }
    }
    if (Objects.nonNull(inviterUserId)) {
      InviteRedPacketRecord invitePeopleRedPacket = inviteRedPacketRecordService.getUserRedPacketByUndone(inviterUserId);
      if (Objects.isNull(invitePeopleRedPacket)) {
        return Boolean.FALSE;
      } else {
        return invitePeopleRedPacket.getViolationOrNot();
      }

    }
    return Boolean.TRUE;

  }

  private static BigDecimal getCurrentAmount(InviteUserRedPacketConfigCache configCache) {
    return new BigDecimal(
        Objects.toString(ThreadLocalRandom.current()
            .nextDouble(configCache.getOpenRedPacketMin().doubleValue(),
                configCache.getOpenRedPacketMax().doubleValue()))).setScale(2,
        RoundingMode.DOWN);
  }

  public static Long getRandomLong(Long start, Long end) {
    return ThreadLocalRandom.current().nextLong(start, end + 1);
  }

  public static Integer getRandomInt(Integer start, Integer end) {
    return ThreadLocalRandom.current().nextInt(start, end + 1);
  }

  private InviteUserRedPacketConfigCache getInviteConfig(InviteUserRedPacketCmd cmd) {
    InviteUserRedPacketConfigCache configCache = redPacketInviteUserCommon.getInviteConfig(
        cmd.getReqSysOrigin().getOrigin());
    ResponseAssert.notNull(CommonErrorCode.CONFIGURATION_ERROR, configCache);
    return configCache;
  }

  private String lockKey(InviteUserRedPacketCmd cmd) {
    return "GAME_INVITE_NEW_OPEN_RED_PACKET:" + cmd.getReqUserId();
  }

}
