package com.red.circle.other.app.command.game.redpacket;

import com.google.common.collect.Lists;
import com.red.circle.common.business.dto.cmd.app.AppIdLongCmd;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.game.GameReceiveRedPacketCO;
import com.red.circle.other.app.dto.clientobject.game.GameReceiveRedPacketUserCO;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRelationsCalculatorGateway;
import com.red.circle.other.domain.model.user.ability.UserRelationsCalculator;
import com.red.circle.other.infra.database.cache.service.other.GameRedPacketCacheService;
import com.red.circle.other.infra.database.mongo.entity.game.redpacket.GameRedPacket;
import com.red.circle.other.infra.database.mongo.entity.game.redpacket.GameRedPacketReceiveUsers;
import com.red.circle.other.infra.database.mongo.service.game.GameRedPacketSendRecordService;
import com.red.circle.other.infra.database.rds.service.user.user.UserSubscriptionService;
import com.red.circle.other.inner.asserts.GameErrorCode;
import com.red.circle.other.inner.enums.game.GameRedPacketErrorCode;
import com.red.circle.other.inner.enums.game.GameRedPacketReceiveStatus;
import com.red.circle.other.inner.enums.game.RedPacketEnum;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.text.StringUtils;
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 java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 领取红包.
 *
 * @author pengliang on 2021/5/17
 */
@Component
@RequiredArgsConstructor
public class GameRedPacketReceiveCmdExe {

  private final RedisService redisService;
  private final WalletGoldClient walletGoldClient;
  private final UserProfileGateway userProfileGateway;
  private final UserSubscriptionService userSubscriptionService;
  private final UserProfileAppConvertor userProfileAppConvertor;
  private final GameRedPacketCacheService gameRedPacketCacheService;
  private final UserRelationsCalculatorGateway userRelationsCalculatorGateway;
  private final GameRedPacketSendRecordService gameRedPacketSendRecordService;

  public GameReceiveRedPacketCO execute(AppIdLongCmd cmd) {

    GameRedPacket redPacket = getGameRedPacket(cmd.getId());

    // 已过期
    if (redPacket.checkExpired()) {
      return getGameReceiveRedPacketResult(cmd, GameRedPacketReceiveStatus.EXPIRED);
    }

    // 个人红包则不允许发送人领取
    if (Objects.equals(redPacket.getType(), RedPacketEnum.USER.name()) &&
        Objects.equals(redPacket.getUserId(), cmd.requiredReqUserId())) {

      ResponseAssert.failure(GameRedPacketErrorCode.UNCLAIMABLE);
    }

    GameRedPacketReceiveStatus receiveStatus = Optional.ofNullable(gameRedPacketCacheService
            .getReceiveStatus(cmd.getId(), cmd.requiredReqUserId()))
        .map(GameRedPacketReceiveStatus::valueOf).orElse(GameRedPacketReceiveStatus.NONE);

    // 是否已抢完
    if (redPacket.checkStatusEqNotAvailable()
        || Objects.equals(receiveStatus, GameRedPacketReceiveStatus.NOT_ACQUIRED)) {
      return getGameReceiveRedPacketResult(cmd, GameRedPacketReceiveStatus.NOT_ACQUIRED);
    }

    // 是否已领取
    boolean received = Objects.equals(receiveStatus, GameRedPacketReceiveStatus.RECEIVED);
    if (received) {
      GameReceiveRedPacketCO packetCO = getGameReceiveRedPacketResult(cmd,
          GameRedPacketReceiveStatus.RECEIVED);
      packetCO.setLastReceived(Boolean.TRUE);
      return packetCO;
    }

    boolean existsReceived = existsReceivedUser(redPacket, cmd.requiredReqUserId());
    if (existsReceived) {
      changeCacheReceiveStatus(cmd.getId(), cmd.requiredReqUserId(),
          GameRedPacketReceiveStatus.RECEIVED);
      GameReceiveRedPacketCO packetCO = getGameReceiveRedPacketResult(cmd,
          GameRedPacketReceiveStatus.RECEIVED);
      packetCO.setLastReceived(Boolean.TRUE);
      return packetCO;
    }

    return sendAmount(cmd);
  }

  private Boolean existsReceivedUser(GameRedPacket redPacket, Long userId) {
    return Optional.ofNullable(redPacket.getReceiveUsers())
        .map(receiveUsers -> receiveUsers.stream().filter(
                receiveUser -> Objects.equals(receiveUser.getUserId(), userId))
            .findFirst()
            .map(receiveUser -> Boolean.TRUE)
            .orElse(Boolean.FALSE))
        .orElse(Boolean.FALSE);
  }

  private GameReceiveRedPacketUserCO getMeReceived(Long userId,
      List<GameReceiveRedPacketUserCO> recipients) {
    return recipients.stream()
        .filter(receiveUser -> Objects.equals(receiveUser.getUserId(), userId))
        .findFirst()
        .orElse(null);
  }

  private List<GameReceiveRedPacketUserCO> getRecipients(
      List<GameRedPacketReceiveUsers> receiveUsers) {
    if (CollectionUtils.isEmpty(receiveUsers)) {
      return Lists.newArrayList();
    }

    Map<Long, UserProfileDTO> userProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
        userProfileGateway.mapByUserIds(
            receiveUsers.stream().map(GameRedPacketReceiveUsers::getUserId)
                .collect(Collectors.toSet())
        ));

    return receiveUsers.stream().map(receiveUser -> {
      UserProfileDTO userProfile = userProfileMap.get(receiveUser.getUserId());
      return new GameReceiveRedPacketUserCO()
          .setUserId(receiveUser.getUserId())
          .setUserNickname(userProfile.getUserNickname())
          .setUserAvatar(userProfile.getUserAvatar())
          .setQuantity(receiveUser.getAmount())
          .setCreateTime(receiveUser.getCreateTime());
    }).toList();
  }


  private GameRedPacket getGameRedPacket(Long id) {
    GameRedPacket gameRedPacket = gameRedPacketSendRecordService.getById(id);
    // 8000 没有找到红包信息
    ResponseAssert.notNull(GameErrorCode.NOT_FOUND_RED_PACKET, gameRedPacket);
    return gameRedPacket;
  }

  private UserProfileDTO senderUserProfile(Long userId) {
    return userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(userId));
  }

  private GameReceiveRedPacketCO sendAmount(AppIdLongCmd cmd) {
    String key = "RD:" + cmd.getId() + "_" + cmd.requiredReqUserId();
    try {
      if (!redisService.lock(key, 60)) {
        return getGameReceiveRedPacketResult(cmd, GameRedPacketReceiveStatus.NONE);
      }
      GameRedPacket redPacket = getGameRedPacket(cmd.getId());
      String popAmount = gameRedPacketCacheService.pop(redPacket.getId());

      int amount = StringUtils.isBlank(popAmount) ? 0 : DataTypeUtils.toInteger(popAmount);

      // 已抢完，没有啦！
      if (amount <= 0) {
        changeCacheReceiveStatus(redPacket.getId(),
            cmd.requiredReqUserId(),
            GameRedPacketReceiveStatus.NOT_ACQUIRED);

        gameRedPacketSendRecordService.updateStatus(redPacket.getId(),
            GameRedPacket.GameRedPacketStatus.NOT_RECEIVE);
        return getGameReceiveRedPacketResult(cmd, GameRedPacketReceiveStatus.NOT_ACQUIRED);
      }

      GoldOrigin goldOrigin = GoldOrigin.RECEIVE_FAMILY_GROUP_RED_PACKET;
      if (Objects.equals(redPacket.getType(), RedPacketEnum.USER.name())) {
        goldOrigin = GoldOrigin.RECEIVE_USER_RED_PACKET;
      }
      if (Objects.equals(redPacket.getType(), RedPacketEnum.ROOM.name())) {
        goldOrigin = GoldOrigin.RECEIVE_RED_PACKET;
      }

      walletGoldClient.changeBalance(GoldReceiptCmd.builder()
          .appIncome()
          .eventId(cmd.getId())
          .userId(cmd.requiredReqUserId())
          .sysOrigin(cmd.requireReqSysOrigin())
          .origin(goldOrigin)
          .amount(BigDecimal.valueOf(amount))
          .build());

      gameRedPacketSendRecordService.addReceiveUsers(redPacket.getId()
          , cmd.requiredReqUserId()
          , BigDecimal.valueOf(amount));

      changeCacheReceiveStatus(redPacket.getId(), cmd.requiredReqUserId(),
          GameRedPacketReceiveStatus.RECEIVED);

      // 个人红包领取则修改为已领取
      if (Objects.equals(redPacket.getType(), RedPacketEnum.USER.name())) {
        gameRedPacketSendRecordService.updateStatus(redPacket.getId(),
            GameRedPacket.GameRedPacketStatus.NOT_RECEIVE);
      }

      if (!Objects.equals(redPacket.getType(), RedPacketEnum.USER.name())
          && !Objects.equals(cmd.requiredReqUserId(), redPacket.getUserId())
          && isSatisfyMoney(redPacket.getAmount())
          && !checkSubscription(cmd, redPacket)) {
        subscription(cmd, redPacket.getUserId());
      }

      return getGameReceiveRedPacketResult(cmd, GameRedPacketReceiveStatus.RECEIVED);
    } finally {
      redisService.unlock(key);
    }
  }

  private boolean isSatisfyMoney(BigDecimal amount) {
    return amount.compareTo(new BigDecimal(1000)) >= 0;
  }

  private boolean checkSubscription(AppIdLongCmd cmd, GameRedPacket redPacket) {
    return userSubscriptionService
        .checkSubscription(cmd.requiredReqUserId(), redPacket.getUserId());
  }

  private GameReceiveRedPacketCO getGameReceiveRedPacketResult(AppIdLongCmd cmd,
      GameRedPacketReceiveStatus status) {
    GameRedPacket redPacket = getGameRedPacket(cmd.getId());
    if (Objects.equals(status, GameRedPacketReceiveStatus.EXPIRED)) {
      return new GameReceiveRedPacketCO()
          .setReceiveStatus(status)
          .setSenderUser(senderUserProfile(redPacket.getUserId()));
    }
    List<GameReceiveRedPacketUserCO> recipients = getRecipients(redPacket.getReceiveUsers());
    return new GameReceiveRedPacketCO()
        .setReceiveStatus(status)
        .setSenderUser(senderUserProfile(redPacket.getUserId()))
        .setMeReceive(getMeReceived(cmd.requiredReqUserId(), recipients))
        .setTotalAmount(redPacket.getAmount())
        .setTotalQuantity(redPacket.getQuantity())
        .setReceivedQuantity(recipients.size())
        .setRecipients(recipients);
  }

  private void subscription(AppIdLongCmd cmd, Long userId) {

    if (!Objects.equals(cmd.getReqSysOrigin().getOrigin(),
        userProfileGateway.getSysOrigin(userId))) {
      return;
    }

    if (!userSubscriptionService.notExistsAdd(cmd.requiredReqUserId(), userId)) {
      return;
    }

    userRelationsCalculatorGateway.process(new UserRelationsCalculator()
        .setUserId(cmd.requiredReqUserId())
        .setSubUserId(userId)
        .setType(UserRelationsCalculator.CounterEnum.SUBSCRIPTION)
        .setIncr(Boolean.TRUE));
  }

  private void changeCacheReceiveStatus(Long redPacketId, Long userId,
      GameRedPacketReceiveStatus status) {
    gameRedPacketCacheService
        .setReceiveStatus(redPacketId, userId, status.name());
  }

}
