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

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.framework.web.props.EnvProperties;
import com.red.circle.other.app.convertor.game.GameLudoAppConvertor;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.game.GameLudoRunCO;
import com.red.circle.other.infra.database.rds.entity.sys.GameListConfig;
import com.red.circle.other.app.dto.cmd.game.GameLudoCreateCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.infra.database.cache.service.other.GameCacheService;
import com.red.circle.other.infra.database.mongo.entity.game.ludo.CheckerboardColor;
import com.red.circle.other.infra.database.mongo.entity.game.ludo.GameLudoPlayer;
import com.red.circle.other.infra.database.mongo.entity.game.ludo.GameLudoRun;
import com.red.circle.other.infra.database.mongo.entity.game.ludo.GameStatus;
import com.red.circle.other.infra.database.mongo.entity.game.ludo.GameStatusEvent;
import com.red.circle.other.infra.database.mongo.service.game.GameLudoRunService;
import com.red.circle.other.infra.database.rds.service.live.RoomMemberService;
import com.red.circle.other.infra.database.rds.service.sys.GameListConfigService;
import com.red.circle.other.inner.asserts.GameErrorCode;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.enums.live.RoomUserRolesEnum;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
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.Collections;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 创建ludo.
 *
 * @author pengliang on 2021/7/27
 */
@Component
@RequiredArgsConstructor
public class GameLudoCreateCmdExe {

  private final RedisService redisService;
  private final EnvProperties envProperties;
  private final WalletGoldClient walletGoldClient;
  private final GameCacheService gameCacheService;
  private final RoomMemberService roomMemberService;
  private final UserProfileGateway userProfileGateway;
  private final GameLudoRunService gameLudoRunService;
  private final GameLudoAppConvertor gameLudoAppConvertor;
  private final UserProfileAppConvertor userProfileAppConvertor;
  private final GameListConfigService gameListConfigService;

  public GameLudoRunCO execute(GameLudoCreateCmd cmd) {

    if (envProperties.isProd()) {
      RoomUserRolesEnum roles = roomMemberService.getRoomUserRoles(cmd.getHomeownerUserId(),
          cmd.getRoomId());

      ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION,
          Objects.equals(roles, RoomUserRolesEnum.ADMIN)
              || Objects.equals(roles, RoomUserRolesEnum.HOMEOWNER));
    }

    GameLudoRun gameLudoRun;
    try {
      // 8027 游戏创建存在抢占
      ResponseAssert.isTrue(GameErrorCode.GAME_CREATE_SEIZE,
          redisService.lock(lockKey(cmd), 60));
      // 8008 游戏已创建
      ResponseAssert.isFalse(GameErrorCode.GAME_CREATED,
          gameLudoRunService.exists(cmd.getRoomId()));

      GameListConfig gameListConfig = gameListConfigService.getById(cmd.getGameId());
      ResponseAssert.notNull(GameErrorCode.GAME_NOT_EXIST, gameListConfig);

      gameLudoRun = createGameLudoRun(cmd, gameListConfig);
      ResponseAssert.isTrue(GameErrorCode.GAME_CREATED, gameLudoRunService.create(gameLudoRun));
      gameCacheService.inrJoinNumberPeopleLudo(gameLudoRun.getId());
      gameCacheService.setJoinGameLudo(gameLudoRun.getId(), gameLudoRun.getUserId());

    } finally {
      redisService.unlock(lockKey(cmd));
    }
    return gameLudoAppConvertor.entityToGameLudoRunCO(gameLudoRun);
  }

  private String lockKey(GameLudoCreateCmd cmd) {
    return "CREATE_LUDO".concat(cmd.getRoomId().toString());
  }

  private GameLudoRun createGameLudoRun(GameLudoCreateCmd cmd, GameListConfig config) {
    BigDecimal consumeAmount = consumeAmount(cmd);
    UserProfileDTO userProfile = userProfileAppConvertor.toUserProfileDTO(
        userProfileGateway.getByUserId(cmd.getHomeownerUserId()));
    ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, userProfile);
    GameLudoRun.GameInfo gameInfo = new GameLudoRun.GameInfo();
    gameInfo.setId(config.getId());
    gameInfo.setGameId(config.getGameId());
    gameInfo.setName(config.getName());
    gameInfo.setGameCode(config.getGameCode());
    gameInfo.setCover(config.getCover());
    return new GameLudoRun()
        .setId(cmd.getRoomId())
        .setSysOrigin(cmd.getReqSysOrigin().getOrigin())
        .setRoomId(cmd.getRoomId())
        .setUserId(cmd.getHomeownerUserId())
        .setGameId(cmd.getGameId())
        .setGameInfo(gameInfo)
        .setGameStatus(GameStatus.WAIT)
        .setGameStatusEvent(GameStatusEvent.NONE)
        .setJoinAmount(cmd.getAmount())
        .setLockSeat(cmd.getLocalSeat())
        .setPlayers(Collections.singletonList(new GameLudoPlayer()
                .setConsumeJoinAmount(consumeAmount)
                .setCheckerboardColor(CheckerboardColor.RED)
                .setInitiator(Boolean.TRUE)
                .setPlayerProfile(gameLudoAppConvertor.userProfileToPlayerProfile(userProfile))
                .setCreateTime(TimestampUtils.now())
            )
        )
        .setCreateTime(TimestampUtils.now());
  }

  private BigDecimal consumeAmount(GameLudoCreateCmd cmd) {
    if (ArithmeticUtils.lteZero(cmd.getAmount())) {
      return BigDecimal.ZERO;
    }
    WalletReceiptResDTO walletReceiptDTO = ResponseAssert.requiredSuccess(
        walletGoldClient.changeBalance(GoldReceiptCmd.builder()
            .appExpenditure()
            .eventId(cmd.getRoomId())
            .userId(cmd.getHomeownerUserId())
            .sysOrigin(cmd.getReqSysOrigin().getOrigin())
            .origin(GoldOrigin.LUDO_GAME)
            .amount(cmd.getAmount())
            .build()));
    return Objects.isNull(walletReceiptDTO) ? ArithmeticUtils.toBigDecimal(0)
        : walletReceiptDTO.getBalance().getDollarAmount();
  }
}
