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

import com.red.circle.external.inner.endpoint.message.ImMessageClient;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.common.cp.CpRelationC2cNoticeUtils;
import com.red.circle.other.app.common.cp.CpRelationBroadcastMqClient;
import com.red.circle.other.app.dto.cmd.user.relation.cp.ProcessApplyCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.cache.service.cp.CpRelationshipCacheService;
import com.red.circle.other.infra.database.rds.entity.user.user.CpApply;
import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship;
import com.red.circle.other.infra.database.rds.entity.user.user.CpValue;
import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
import com.red.circle.other.infra.database.rds.service.user.user.CpValueService;
import com.red.circle.other.infra.enums.user.user.CpApplyStatus;
import com.red.circle.other.infra.enums.user.user.CpRelationshipType;
import com.red.circle.other.inner.asserts.user.UserRelationErrorCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 处理cp.
 *
 * @author pengliang on 2021/7/13
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ProcessCpApplyCmd {

  private static final int CP_MAX_COUNT = 1;
  private static final int FRIEND_MAX_COUNT = 3;
  private static final long APPLY_WAIT_EXPIRE_HOURS = 24L;

  private final CpApplyService cpApplyService;
  private final CpValueService cpValueService;
  private final ImMessageClient imMessageClient;
  private final CpRelationshipService cpRelationshipService;
  private final UserProfileGateway userProfileGateway;
  private final CpRelationshipCacheService cpRelationshipCacheService;
  private final CpRelationBroadcastMqClient cpRelationBroadcastMqClient;

  public void execute(ProcessApplyCmd cmd) {
    process(cmd);
  }

  private void process(ProcessApplyCmd cmd) {
    CpApply cpApply = cpApplyService.getById(cmd.getApplyId());
    // 没有找到映射信息
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, cpApply);
    String relationType = CpRelationshipType.normalize(cpApply.getRelationType());

    // 状态错误
    ResponseAssert.isTrue(CommonErrorCode.STATE_ERROR,
        Objects.equals(cpApply.getStatus(), CpApplyStatus.WAIT.name()));
    ResponseAssert.isFalse(CommonErrorCode.STATE_ERROR, isOverdueWait(cpApply));

    boolean agree = isAgree(cmd);
    boolean isReconcile = false;

    if (agree) {
      // 检查是否是复合申请
      CpRelationship dismissingCp = cpRelationshipService.getDismissingCp(
          cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), relationType);
      isReconcile = Objects.nonNull(dismissingCp);
    }

    if (agree && !isReconcile) {
      // 非复合申请：两人之间只能存在一种正常关系
      ResponseAssert.isFalse(UserRelationErrorCode.ME_OR_YOU_CP_EXISTED,
          cpRelationshipService.existsRelationship(cpApply.getSendApplyUserId(),
              cpApply.getAcceptApplyUserId()));

      int maxCount = relationMaxCount(relationType);
      ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
          cpRelationshipService.countCp(cpApply.getSendApplyUserId(), relationType) >= maxCount);
      ResponseAssert.isFalse(UserRelationErrorCode.CP_LIMIT_EXCEEDED,
          cpRelationshipService.countCp(cpApply.getAcceptApplyUserId(), relationType) >= maxCount);
    }

    // 操作失败
    ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE,
        cpApplyService.changeStatusById(cmd.getApplyId(), getStatus(cmd)));

    if (!agree) {
      sendProcessNotice(cpApply, false);
      return;
    }

    if (isReconcile) {
      // 复合：更新状态为NORMAL
      cpRelationshipService.reconcile(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(),
          relationType);
    } else {
      // 新建关系
      cpRelationshipService
          .addCp(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), createCpValue(),
              cmd.requireReqSysOrigin(), relationType);
    }

    autoRefuseWaitAppliesAfterAgree(cpApply, relationType);

    cpRelationshipCacheService.remove(Arrays.asList(cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId()));
    cpRelationBroadcastMqClient.publish(cmd.requireReqSysOrigin(), cpApply.getId(),
        cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(), relationType);
    sendProcessNotice(cpApply, true);
  }

  private boolean isOverdueWait(CpApply cpApply) {
    return Objects.equals(cpApply.getStatus(), CpApplyStatus.WAIT.name())
        && Objects.nonNull(cpApply.getCreateTime())
        && cpApply.getCreateTime().toLocalDateTime()
            .isBefore(LocalDateTime.now().minusHours(APPLY_WAIT_EXPIRE_HOURS));
  }

  private void sendProcessNotice(CpApply cpApply, boolean agree) {
    try {
      UserProfile senderProfile = userProfileGateway.getByUserId(cpApply.getSendApplyUserId());
      UserProfile acceptProfile = userProfileGateway.getByUserId(cpApply.getAcceptApplyUserId());
      if (Objects.isNull(senderProfile) || Objects.isNull(acceptProfile)) {
        log.info("[CP] skip process c2c notice, applyId={}, senderProfile={}, acceptProfile={}",
            cpApply.getId(), Objects.nonNull(senderProfile), Objects.nonNull(acceptProfile));
        return;
      }
      imMessageClient.sendCustomMessage(agree
          ? CpRelationC2cNoticeUtils.buildAccepted(cpApply, senderProfile, acceptProfile)
          : CpRelationC2cNoticeUtils.buildRejected(cpApply, senderProfile, acceptProfile));
    } catch (Exception ex) {
      log.warn("[CP] send process c2c notice failed, applyId={}, agree={}, reason={}",
          cpApply.getId(), agree, ex.getMessage(), ex);
    }
  }

  private List<CpApply> autoRefuseWaitAppliesAfterAgree(CpApply cpApply, String relationType) {
    Map<Long, CpApply> refusedApplyMap = new LinkedHashMap<>();
    List<CpApply> samePairRefusedApplies = cpApplyService.refuseOtherWaitAppliesBetweenUsers(
        cpApply.getId(), cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId());
    addRefusedApplies(refusedApplyMap, samePairRefusedApplies);
    if (!samePairRefusedApplies.isEmpty()) {
      log.info("[CP] auto refused other wait applies between same users after agree, "
              + "agreeApplyId={}, sendUserId={}, acceptUserId={}, applyIds={}",
          cpApply.getId(), cpApply.getSendApplyUserId(), cpApply.getAcceptApplyUserId(),
          samePairRefusedApplies.stream().map(CpApply::getId).toList());
    }
    addRefusedApplies(refusedApplyMap,
        refuseWaitAppliesIfLimitReached(cpApply, cpApply.getSendApplyUserId(), relationType));
    addRefusedApplies(refusedApplyMap,
        refuseWaitAppliesIfLimitReached(cpApply, cpApply.getAcceptApplyUserId(), relationType));
    return List.copyOf(refusedApplyMap.values());
  }

  private List<CpApply> refuseWaitAppliesIfLimitReached(CpApply cpApply, Long userId,
      String relationType) {
    if (cpRelationshipService.countCp(userId, relationType) < relationMaxCount(relationType)) {
      return List.of();
    }
    List<CpApply> refusedApplies = cpApplyService.refuseWaitAppliesByUserAndRelation(
        cpApply.getId(), userId, relationType);
    log.info("[CP] auto refused wait applies after relation limit reached, userId={}, relationType={}, applyIds={}",
        userId, relationType, refusedApplies.stream().map(CpApply::getId).toList());
    return refusedApplies;
  }

  private void addRefusedApplies(Map<Long, CpApply> applyMap, List<CpApply> applies) {
    if (Objects.isNull(applies) || applies.isEmpty()) {
      return;
    }
    applies.stream()
        .filter(Objects::nonNull)
        .filter(apply -> Objects.nonNull(apply.getId()))
        .forEach(apply -> applyMap.putIfAbsent(apply.getId(), apply));
  }

  private int relationMaxCount(String relationType) {
    return Objects.equals(CpRelationshipType.CP, CpRelationshipType.of(relationType))
        ? CP_MAX_COUNT
        : FRIEND_MAX_COUNT;
  }

  private Long createCpValue() {
    CpValue cpValue = new CpValue()
        .setCpVal(BigDecimal.ZERO);
    cpValueService.save(cpValue);
    return cpValue.getId();
  }

  private boolean isAgree(ProcessApplyCmd cmd) {
    return Objects.equals(cmd.getAgree(), Boolean.TRUE);
  }

  private CpApplyStatus getStatus(ProcessApplyCmd cmd) {
    return isAgree(cmd)
        ? CpApplyStatus.AGREE : CpApplyStatus.REFUSE;
  }

}
