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

import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
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.live.inner.endpoint.LiveMicClient;
import com.red.circle.mq.rocket.business.producer.UserMqMessageService;
import com.red.circle.other.app.dto.clientobject.room.ChangeRoleSuccessCO;
import com.red.circle.other.app.dto.cmd.room.RoomMemberIdentityCmd;
import com.red.circle.other.app.dto.cmd.room.RoomMemberIdentityCmd.RoomChangeRolesReason;
import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskProgressUpdateCmd;
import com.red.circle.other.app.service.task.RoomDailyTaskProgressService;
import com.red.circle.other.app.util.DateTimeAsiaRiyadhUtils;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.infra.database.cache.service.other.RoomManagerCacheService;
import com.red.circle.other.infra.database.mongo.entity.live.RoomProfile;
import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager;
import com.red.circle.other.infra.database.mongo.entity.sys.ApiOperationLog;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.database.mongo.service.sys.ApiOperationLogService;
import com.red.circle.other.infra.database.rds.entity.live.RoomMember;
import com.red.circle.other.infra.database.rds.service.live.RoomMemberService;
import com.red.circle.other.inner.asserts.RoomErrorCode;
import com.red.circle.other.inner.enums.live.RoomUserRolesEnum;
import com.red.circle.other.inner.enums.material.BadgeKeyEnum;
import com.red.circle.other.inner.enums.task.RoomDailyTaskCode;
import com.red.circle.tool.core.date.DateUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.json.JacksonUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 房间成员变更身份.
 *
 * @author pengliang on 2022/3/22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RoomMemberChangeIdentityCmdExe {

  private final RedisService redisService;
  private final LiveMicClient liveMicClient;
  private final WalletGoldClient walletGoldClient;
  private final RoomMemberService roomMemberService;
  private final UserProfileGateway userProfileGateway;
  private final UserMqMessageService userMqMessageService;
  private final ApiOperationLogService apiOperationLogService;
  private final RoomManagerCacheService roomManagerCacheService;
  private final RoomProfileManagerService roomProfileManagerService;
  private final RoomDailyTaskProgressService roomDailyTaskProgressService;
  private final RedisTemplate redisTemplate;

  public ChangeRoleSuccessCO execute(RoomMemberIdentityCmd cmd) {
    apiOperationLogService.add(
        new ApiOperationLog()
            .setId(IdWorkerUtils.getIdStr())
            .setRequestUserId(cmd.requiredReqUserId())
            .setApiDesc("身份变更V3")
            .setBusinessCode("ROOM_ROLES_CHANGE_V3")
            .setBusinessContent(Objects.toString(cmd.getRoomId()))
            .setCmd(JacksonUtils.toJson(cmd))
            .setExpiredTime(DateUtils.nowPlusDays(7))
            .setCreateTime(DateUtils.now())
    );

    try {
      roomManagerCacheService.removeRoomUserRoles(cmd.getChangeUserId(), cmd.getRoomId());
      if (redisService.lock(cmd.lockKey(), 60)) {
        executeChangeRoles(cmd);
        liveMicClient.refreshMicUserRoles(cmd.getRoomId(), cmd.getChangeUserId());
      }
    } finally {
      redisService.unlock(cmd.lockKey());
    }
    return new ChangeRoleSuccessCO()
        .setRoomId(cmd.getRoomId())
        .setUserType(cmd.getUserType())
        .setChangeUserId(cmd.getChangeUserId())
        .setOperatorUserId(cmd.getOperatorUserId())
        .setReason(Objects.toString(cmd.getReason()));
  }

  private void executeChangeRoles(RoomMemberIdentityCmd cmd) {
    ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION, cmd.requestUserExists());

    // 无法操作，错误归属
    checkSysOrigin(cmd);

    // 自己操作自己
    if (cmd.isOperationSelf()) {
      ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION, cmd.allowOperationSelf());
    }
    // 强制矫正
    if (Objects.equals(cmd.getReason(), RoomChangeRolesReason.ONESELF_JOIN)) {
      cmd.setUserType(RoomUserRolesEnum.MEMBER);
    }
    if (Objects.equals(cmd.getReason(), RoomChangeRolesReason.ONESELF_EXIT)) {
      cmd.setUserType(RoomUserRolesEnum.TOURIST);
    }

    // 本次操作角色"房主"-权限不足
    ResponseAssert.isFalse(CommonErrorCode.INSUFFICIENT_PERMISSION, cmd.checkHomeowner());

    RoomMember beRoomMember = roomMemberService
        .getRoomMember(cmd.getRoomId(), cmd.getChangeUserId());
    // 被操作人是否是"房主"-权限不足
    ResponseAssert.isFalse(CommonErrorCode.INSUFFICIENT_PERMISSION, isRoomHomeowner(beRoomMember));

    // 不是操作自己的时候
    if (!cmd.isOperationSelf() || !cmd.allowOperationSelf()) {
      RoomMember operationRoomMember = roomMemberService.getRoomMember(cmd.getRoomId(),
          cmd.getOperatorUserId());
      // 操作人角色是否存在-权限不足
      ResponseAssert.notNull(CommonErrorCode.INSUFFICIENT_PERMISSION, operationRoomMember);
      // 操作人必须是管理员/房主角色
      ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION,
          operationRoomMember.checkHomeownerOrAdmin());

      // 本次操作角色/被操作人是："管理员"； 必须： 操作人=房主
      if (cmd.checkAdmin() || isAdmin(beRoomMember)) {
        ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION,
            operationRoomMember.checkHomeowner());
      }
    }

    if (isNotChangeRoles(cmd, beRoomMember)) {
      return;
    }

    // [变更游客] 移除房间，计数-1
    if (cmd.checkTourist()) {
      executeChangeTourist(beRoomMember);
      return;
    }

    // [游客变更-> 管理员/成员]
    if (Objects.isNull(beRoomMember)) {
      firstJoinRoomMember(cmd);
      return;
    }

    // [管理员-> 非管理员] 管理员计数-1，角色变更
    if (beRoomMember.checkAdmin() && !cmd.checkAdmin()) {
      roomProfileManagerService.decrCounterAdmin(cmd.getRoomId());
    }

    // [其他 ->管理员] 检查管理员是否上线 ,管理员计数+1, 角色变更
    if (!beRoomMember.checkAdmin() && cmd.checkAdmin()) {
      checkJoinMaxMember(cmd.getRoomId(), RoomUserRolesEnum.ADMIN);
      roomProfileManagerService.incrCounterAdmin(cmd.getRoomId());
    }

    roomMemberService.update()
        .set(RoomMember::getRoles, cmd.getRoles())
        .set(RoomMember::getUpdateUser, cmd.requiredReqUserId())
        .eq(RoomMember::getId, beRoomMember.getId())
        .execute();
  }

  private void checkSysOrigin(RoomMemberIdentityCmd cmd) {
    ResponseAssert.isTrue(CommonErrorCode.INOPERABLE_WRONG_ATTRIBUTION,
        StringUtils
            .startsWith(userProfileGateway.getSysOrigin(cmd.getChangeUserId()),
                cmd.requireReqSysOrigin()));
  }

  private boolean isNotChangeRoles(RoomMemberIdentityCmd cmd, RoomMember roomMember) {
    return Objects.nonNull(roomMember) && Objects.equals(roomMember.getRoles(),
        cmd.getRoles().name());
  }

  private boolean isRoomHomeowner(RoomMember roomMember) {
    return Objects.nonNull(roomMember) && Objects
        .equals(RoomUserRolesEnum.HOMEOWNER.name(), roomMember.getRoles());
  }

  private boolean isAdmin(RoomMember roomMember) {
    return Objects.nonNull(roomMember) && Objects
        .equals(RoomUserRolesEnum.ADMIN.name(), roomMember.getRoles());
  }

  private void firstJoinRoomMember(RoomMemberIdentityCmd cmd) {
    if (!Objects.equals(cmd.requireReqSysOrigin(), SysOriginPlatformEnum.ASWAT.name())) {
      ResponseAssert.isFalse(WalletErrorCode.INSUFFICIENT_BALANCE, checkJoinRoomConsumeGolds(cmd));
    }
    // 检测成员+admin
    checkJoinMaxMember(cmd.getRoomId(), cmd.getRoles());
    RoomMember roomMember = new RoomMember()
        .setRoles(Objects.toString(cmd.getRoles()))
        .setUserId(cmd.getChangeUserId())
        .setRoomId(cmd.getRoomId())
        .setSysOrigin(cmd.requireReqSysOrigin());
    roomMember.setCreateUser(cmd.requiredReqUserId());
    roomMember.setUpdateUser(cmd.requiredReqUserId());
    try {
      if (!roomMemberService.save(roomMember)) {
        return;
      }
    } catch (DuplicateKeyException ignore) {
      log.error("加入房间唯一键冲突：{}", JacksonUtils.toJson(cmd));
      return;
    }

    incrQuantity(cmd.getRoomId(), cmd.getRoles());

    // 房间每日任务
    RoomProfileManager manager = roomProfileManagerService.getById(cmd.getRoomId());
    updateRoomDailyTask(cmd.getChangeUserId(), manager.getUserId(), manager.getId());

  }

  private void updateRoomDailyTask(Long enterUserId, Long roomOwnerId, Long roomId) {
    // 只处理用户进入非自己房间的情况
    if (Objects.equals(enterUserId, roomOwnerId)) {
      return;
    }
    RoomMember roomMember = roomMemberService.getRoomMember(roomId, enterUserId);
    if (Objects.isNull(roomMember)) {
      return;
    }

    // 防止反复 游客<->成员 刷任务：30天内同一房主+成员组合只计一次
    String memberHistoryKey = "room:daily:task:member_history:" + roomOwnerId + ":" + enterUserId;
    boolean isFirstJoin = redisService.setIfAbsent(memberHistoryKey, "1", 30, TimeUnit.DAYS);
    if (!isFirstJoin) {
      return;
    }

    LocalDate createDate = roomMember.getCreateTime().toLocalDateTime()
            .atZone(java.time.ZoneOffset.UTC)
            .withZoneSameInstant(ZoneId.of("Asia/Riyadh"))
            .toLocalDate();
    LocalDate todayDate = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
    if (!createDate.equals(todayDate)) {
      return;
    }

    try {
      // Redis 去重：同一用户当天进入同一房间只计一次
      String redisKey = "room:daily:task:new_member:" + roomOwnerId + ":" + ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
      Long added = redisTemplate.opsForSet().add(redisKey, enterUserId);
      redisService.expire(redisKey, DateTimeAsiaRiyadhUtils.getSecondsUntilMidnight(), TimeUnit.SECONDS);

      if (!Long.valueOf(1L).equals(added)) {
        return;
      }

      long uniqueCount = redisService.setSize(redisKey);
      roomDailyTaskProgressService.updateTaskProgress(
              new RoomDailyTaskProgressUpdateCmd()
                      .setUserId(roomOwnerId)
                      .setTaskCode(RoomDailyTaskCode.ROOM_NEW_MEMBER.name())
                      .setProgressValue((int) uniqueCount)
      );
    } catch (Exception e) {
      log.error("更新房间每日任务失败, roomOwnerId={}, enterUserId={}", roomOwnerId, enterUserId, e);
    }
  }

  private void executeChangeTourist(RoomMember roomMember) {
    if (Objects.isNull(roomMember)) {
      return;
    }

    if (!roomMemberService.removeMemberById(roomMember.getId())) {
      return;
    }

    // 计数器变更
    RoomUserRolesEnum roles = RoomUserRolesEnum.valueOf(roomMember.getRoles());
    if (Objects.equals(RoomUserRolesEnum.ADMIN, roles)) {
      roomProfileManagerService.decrCounterAdminAndMember(roomMember.getRoomId());
      return;
    }
    roomProfileManagerService.decrCounterMember(roomMember.getRoomId());
  }

  private void incrQuantity(Long roomId, RoomUserRolesEnum roles) {
    if (Objects.equals(RoomUserRolesEnum.ADMIN, roles)) {
      roomProfileManagerService.incrCounterAdminAndMember(roomId);
      return;
    }
    roomProfileManagerService.incrCounterMember(roomId);
  }

  private void checkJoinMaxMember(Long roomId, RoomUserRolesEnum roles) {
    RoomProfileManager manager = roomProfileManagerService.getById(roomId);
    // 判断成员上限
    ResponseAssert.isFalse(RoomErrorCode.ROOM_MEMBER_IS_MAX,
        manager.checkMemberOverflow());

    // 判断管理员上限
    if (Objects.equals(roles, RoomUserRolesEnum.ADMIN)) {
      ResponseAssert.isFalse(RoomErrorCode.ROOM_ADMIN_OVERFLOW,
          manager.checkAdminOverflow());
    }
  }


  private Boolean checkJoinRoomConsumeGolds(RoomMemberIdentityCmd cmd) {
    if (!Objects.equals(cmd.getReason().name(), RoomChangeRolesReason.ONESELF_JOIN.name())) {
      return false;
    }

    RoomProfileManager manager = roomProfileManagerService.getById(cmd.getRoomId());
    Integer joinGolds = manager.getSetting().getJoinGolds();
    if (joinGolds < 1) {
      return false;
    }
    BigDecimal balance = ResponseAssert.requiredSuccess(
            walletGoldClient.getBalance(cmd.requiredReqUserId()))
        .getDollarAmount();

    if (ArithmeticUtils.lt(balance, new BigDecimal(joinGolds))) {
      return true;
    }

    ResponseAssert.requiredSuccess(
        walletGoldClient.changeBalance(GoldReceiptCmd.builder()
            .appExpenditure()
            .eventId(cmd.getRoomId())
            .userId(cmd.requiredReqUserId())
            .sysOrigin(cmd.requireReqSysOriginChildEnum())
            .origin(GoldOrigin.JOIN_ROOM)
            .amount(BigDecimal.valueOf(joinGolds)).build()));

    BigDecimal gold = ArithmeticUtils.multiplyRatioRemain(BigDecimal.valueOf(joinGolds), 0.5)
        .setScale(0, RoundingMode.DOWN);
    if (!ArithmeticUtils.gte(gold, BigDecimal.ONE)) {
      return false;
    }

    walletGoldClient.changeBalance(GoldReceiptCmd.builder()
        .appIncome()
        .eventId(cmd.getRoomId())
        .userId(manager.getUserId())
        .sysOrigin(cmd.requireReqSysOriginChildEnum())
        .origin(GoldOrigin.JOIN_ROOM)
        .amount(gold).build());
    return false;

  }


}
