package com.red.circle.game.fruit.application.command;

import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.game.fruit.application.dto.GameTimelineManage;
import com.red.circle.game.fruit.application.dto.cmd.BetPrizeCmd;
import com.red.circle.game.fruit.application.dto.cmd.UserBetCmd;
import com.red.circle.game.fruit.application.dto.enums.MessageTypeTopic;
import com.red.circle.game.fruit.core.domain.gateway.GameFruitUserBetGateway;
import com.red.circle.game.fruit.infrastructure.database.entity.GameFruitPrize;
import com.red.circle.game.fruit.infrastructure.database.entity.GameFruitPrizeBet;
import com.red.circle.game.fruit.infrastructure.database.entity.GameFruitRoundRecord;
import com.red.circle.game.fruit.infrastructure.database.entity.GameFruitUserBetRecord;
import com.red.circle.game.fruit.infrastructure.database.memory.service.GameFruitMemoryService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameCodeManageService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitDaysRecordService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitPrizeBetService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitPrizeService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitRoundRecordService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitUserDaysRankingService;
import com.red.circle.game.fruit.infrastructure.database.repository.GameFruitUserService;
import com.red.circle.game.fruit.infrastructure.websocket.message.MessageSender;
import com.red.circle.game.fruit.infrastructure.websocket.message.TopicMessage;
import com.red.circle.other.inner.asserts.GameErrorCode;
import com.red.circle.other.inner.endpoint.sys.SysGameListConfigClient;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
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.dto.WalletReceiptResDTO;
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.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author lisizhe 2023/12/1  17:23
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class UserBetExe {

  private final MessageSender messageSender;
  private final WalletGoldClient walletGoldClient;
  private final UserProfileClient userProfileClient;
  private final GameFruitUserService gameFruitUserService;
  private final GameFruitPrizeService gameFruitPrizeService;
  private final GameCodeManageService gameCodeManageService;
  private final SysGameListConfigClient gameListConfigClient;
  private final GameFruitMemoryService gameFruitMemoryService;
  private final GameFruitPrizeBetService gameFruitPrizeBetService;
  private final GameFruitUserBetGateway gameFruitUserBetGateway;
  private final GameFruitDaysRecordService gameFruitDaysRecordService;
  private final GameFruitRoundRecordService gameFruitRoundRecordService;
  private final GameFruitUserDaysRankingService gameFruitUserDaysRankingService;

  public void execute(UserBetCmd cmd) {
    // 检查游戏是否正常运行
    Set<Long> gameIds = gameCodeManageService.getGameIdsByCode("GAME_FRUIT");

    if (CollectionUtils.isEmpty(gameIds)) {
      messageSender.sendTopic(
          TopicMessage.of(
              MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()), ResultResponse.failure(
                  GameErrorCode.THE_GAME_HAS_BEEN_TAKEN_DOWN)));
    }
    // 校验是否有游戏下架
    Boolean isUnshelve = ResponseAssert.requiredSuccess(
        gameListConfigClient.checkGameUnshelve(gameIds));

    // 游戏是否下架
    if (isUnshelve) {
      messageSender.sendTopic(
          TopicMessage.of(
              MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()), ResultResponse.failure(
                  GameErrorCode.THE_GAME_HAS_BEEN_TAKEN_DOWN)));
    }

    // 判断是否是押注阶段
    if (gameFruitMemoryService.getRoundSec() <= GameTimelineManage.lotteryDrawSecond) {
      messageSender.sendTopic(
          TopicMessage.of(
              MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()), ResultResponse.failure(
                  GameErrorCode.BET_LOST)));
      return;
    }
    // 判断押注数量是否超出
    if (CollectionUtils.isEmpty(cmd.getBetPrizes()) || cmd.getBetPrizes().size() > 6) {
      messageSender.sendTopic(
          TopicMessage.of(
              MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()), ResultResponse.failure(
                  GameErrorCode.BET_LOST)));
      return;
    }

    GameFruitRoundRecord gameFruitRoundRecord = gameFruitRoundRecordService.getGameRound();

    List<GameFruitUserBetRecord> gameFruitLotteryRecords = gameFruitUserBetGateway.listLotteryUser(
        gameFruitRoundRecord.getId(), cmd.getUid());

    Map<Long, GameFruitUserBetRecord> gameFruitUserBetRecordMap = gameFruitLotteryRecords.stream()
        .collect(Collectors.toMap(GameFruitUserBetRecord::getPrizeId, v -> v));

    // 判断押注数量是否超出
    if (cmd.getBetPrizes().size() == 1 && CollectionUtils.isNotEmpty(gameFruitLotteryRecords)) {
      GameFruitUserBetRecord gameFruitUserBetRecord = gameFruitUserBetRecordMap.get(
          cmd.getBetPrizes().get(0).getPrizeId());
      if ((Objects.isNull(gameFruitUserBetRecord)
          && gameFruitLotteryRecords.size() > 5)) {
        messageSender.sendTopic(
            TopicMessage.of(
                MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()), ResultResponse.failure(
                    GameErrorCode.BET_LOST)));
        return;
      }
    }
    cmd.setGameId(gameFruitRoundRecord.getId());
    BigDecimal sumAmount = ArithmeticUtils.toBigDecimal(
        cmd.getBetPrizes().stream().mapToDouble(item -> item.getBetAmount().doubleValue()).sum());

    cmd.setSumAmount(sumAmount);
    changeBalance(cmd);
  }

  private void addBetAmount(UserBetCmd cmd) {
    // 更新用户缓存信息
    incrCachePrizeBet(cmd);
    // 新增用户本轮下注
    addUserTheRoundBet(cmd);
    // 累加每轮游戏下注
    addRoundBet(cmd);
    // 累加每轮奖品下注情况
    addRoundPrizeBet(cmd);
    // 累加每日游戏记录
    addDaysUserExpand(cmd.getUid(), cmd.getSumAmount(), cmd.getSysOrigin());
    // 新增用户每轮投注记录
    addUserBetAmount(cmd);
    // 累加用户盈利榜单
    addDaysRankingBet(cmd);
  }

  private void addDaysUserExpand(Long userId, BigDecimal sumAmount, String origin) {
    gameFruitDaysRecordService.addDaysBet(userId, sumAmount, origin);
  }

  private void addDaysRankingBet(UserBetCmd cmd) {
    gameFruitUserDaysRankingService.addDaysRankingBet(cmd);
  }

  private void addUserBetAmount(UserBetCmd cmd) {
    gameFruitUserService.updateBetAmount(cmd);
  }

  private void incrCachePrizeBet(UserBetCmd cmd) {
    cmd.getBetPrizes()
        .forEach(item -> gameFruitMemoryService.incrPrizeBet(item.getPrizeId(),
            item.getBetAmount().intValue()));
  }

  private void changeBalance(UserBetCmd cmd) {
    UserProfileDTO userProfile = userProfileClient.getByUserId(cmd.getUid()).getBody();
    if (Objects.isNull(userProfile)) {
      messageSender.sendTopic(
          TopicMessage.of(MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()),
              ResultResponse.failure(UserErrorCode.USER_INFO_NOT_FOUND)));
      return;
    }
    String origin = userProfile.getOriginSys();

    cmd.setSysOrigin(origin);
    // 修改用户金币余额
    ResultResponse<WalletReceiptResDTO> receiptRes = walletGoldClient.changeBalance(
        GoldReceiptCmd.builder()
            .appExpenditure()
            .userId(cmd.getUid())
            .eventId(cmd.getGameId())
            .sysOrigin(origin)
            .origin(GoldOrigin.GAME_FRUIT_BET)
            .amount(cmd.getSumAmount())
            .remark("摩天轮游戏下注：" + cmd.getSumAmount())
            .build());

    if (receiptRes.checkFail()) {
      messageSender.sendTopic(
          TopicMessage.of(MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()),
              ResultResponse.failure(receiptRes.getErrorCode(), receiptRes.getErrorMsg(),
                  receiptRes.getErrorCodeName())));
      return;
    }
    // 累加本轮&每日总下注金额
    addBetAmount(cmd);
    // 返回最新金豆信息
    messageSender.sendTopic(
        TopicMessage.of(MessageTypeTopic.USER_BET_MSG.getTopicId(cmd.getUid()),
            ResultResponse.success(
                Map.of("balance", receiptRes.getBody().getBalance().getDollarAmount(),
                    "prizeId", cmd.getBetPrizes().get(0).getPrizeId(),
                    "autoStart", cmd.getAutoStart()))));
  }

  private void addRoundPrizeBet(UserBetCmd cmd) {
    if (cmd.getAutoStart() == 0) {
      BetPrizeCmd betPrizeCmd = cmd.getBetPrizes().get(0);
      gameFruitPrizeBetService.updateOrAddBetAmount(
          new GameFruitPrizeBet().setGameId(cmd.getGameId())
              .setBetAmount(betPrizeCmd.getBetAmount())
              .setSysOrigin(cmd.getSysOrigin())
              .setPrizeId(betPrizeCmd.getPrizeId()));
      return;
    }
    List<GameFruitPrizeBet> gameFruitUserBetRecords = cmd.getBetPrizes().stream()
        .map(item -> new GameFruitPrizeBet().setGameId(cmd.getGameId())
            .setBetAmount(item.getBetAmount())
            .setSysOrigin(cmd.getSysOrigin())
            .setPrizeId(item.getPrizeId())).toList();
    gameFruitPrizeBetService.addBetAmount(gameFruitUserBetRecords);
  }

  private void addRoundBet(UserBetCmd cmd) {
    gameFruitRoundRecordService.addUserExpend(
        new GameFruitRoundRecord().setId(cmd.getGameId()).setUserExpend(cmd.getSumAmount()));
  }

  private void addUserTheRoundBet(UserBetCmd cmd) {
    Map<Long, GameFruitPrize> gameFruitPrizeMap = gameFruitPrizeService.mapPrize(cmd.getSysOrigin());
    if (cmd.getAutoStart() == 0) {
      BetPrizeCmd betPrizeCmd = cmd.getBetPrizes().get(0);
      gameFruitUserBetGateway.updateOrAddBetAmount(
          new GameFruitUserBetRecord()
              .setUserId(cmd.getUid())
              .setSysOrigin(cmd.getSysOrigin())
              .setGameId(cmd.getGameId())
              .setInvest(betPrizeCmd.getBetAmount())
              .setPrizeId(betPrizeCmd.getPrizeId())
              .setAward(gameFruitPrizeMap.get(betPrizeCmd.getPrizeId()).getAward())
              .setWinner(0)
      );
      return;
    }
    List<GameFruitUserBetRecord> gameFruitUserBetRecords = cmd.getBetPrizes().stream()
        .map(item -> new GameFruitUserBetRecord()
            .setSysOrigin(cmd.getSysOrigin())
            .setUserId(cmd.getUid())
            .setGameId(cmd.getGameId())
            .setInvest(item.getBetAmount())
            .setPrizeId(item.getPrizeId())
            .setAward(gameFruitPrizeMap.get(item.getPrizeId()).getAward())
            .setWinner(0)
        ).toList();
    gameFruitUserBetGateway.addBetAmount(gameFruitUserBetRecords);
  }
}
