package com.red.circle.other.infra.database.rds.service.user.user.impl;


import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.infra.database.rds.dao.user.user.CpApplyDAO;
import com.red.circle.other.infra.database.rds.entity.user.user.CpApply;
import com.red.circle.other.infra.database.rds.service.user.user.CpApplyService;
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.model.cmd.user.CpApplyQryCmd;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.springframework.stereotype.Service;

/**
 * <p>
 * CP申请记录 服务实现类.
 * </p>
 *
 * @author pengshigang
 * @since 2021-07-13
 */
@Service
public class CpApplyServiceImpl extends BaseServiceImpl<CpApplyDAO, CpApply> implements
    CpApplyService {

  private static final long APPLY_WAIT_EXPIRE_HOURS = 24L;

  @Override
  public void dismiss(Long requestUserId) {
    dismiss(requestUserId, CpRelationshipType.CP.name());
  }

  @Override
  public void dismiss(Long requestUserId, String relationType) {
    update()
        .set(CpApply::getStatus, CpApplyStatus.DISMISS.name())
        .set(CpApply::getUpdateTime, LocalDateTime.now())
        .set(CpApply::getUpdateUser, requestUserId)
        .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType))
        .eq(CpApply::getStatus, CpApplyStatus.AGREE.name())
        .and(where -> where.eq(CpApply::getSendApplyUserId, requestUserId)
            .or().eq(CpApply::getAcceptApplyUserId, requestUserId))
        .last(PageConstant.LIMIT_ONE)
        .execute();
  }

  @Override
  public Long getId(Long acceptApplyUserId, Long sendApplyUserId) {
    return getId(acceptApplyUserId, sendApplyUserId, CpRelationshipType.CP.name());
  }

  @Override
  public Long getId(Long acceptApplyUserId, Long sendApplyUserId, String relationType) {
    return Optional.ofNullable(
        query()
            .select(CpApply::getId)
            .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
            .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType))
            .eq(CpApply::getAcceptApplyUserId, acceptApplyUserId)
            .eq(CpApply::getSendApplyUserId, sendApplyUserId)
            .gt(CpApply::getCreateTime, waitExpireBefore())
            .orderByDesc(CpApply::getCreateTime)
            .last(PageConstant.LIMIT_ONE)
            .getOne()
    ).map(CpApply::getId).orElse(null);
  }

  @Override
  public boolean exists(Long acceptApplyUserId, Long sendApplyUserId) {
    return exists(acceptApplyUserId, sendApplyUserId, CpRelationshipType.CP.name());
  }

  @Override
  public boolean exists(Long acceptApplyUserId, Long sendApplyUserId, String relationType) {
    return Optional.ofNullable(getId(acceptApplyUserId, sendApplyUserId, relationType)).isPresent();
  }

  @Override
  public boolean existsNotProcessSendApply(Long sendApplyUserId) {
    return existsNotProcessSendApply(sendApplyUserId, CpRelationshipType.CP.name());
  }

  @Override
  public boolean existsNotProcessSendApply(Long sendApplyUserId, String relationType) {
    return Optional.ofNullable(query()
            .select(CpApply::getId)
            .eq(CpApply::getSendApplyUserId, sendApplyUserId)
            .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType))
            .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
            .gt(CpApply::getCreateTime, waitExpireBefore())
            .getOne())
        .map(cpApply -> Objects.nonNull(cpApply.getId()))
        .orElse(Boolean.FALSE);
  }

  @Override
  public boolean changeStatusById(Long id, CpApplyStatus status) {
    return update()
        .set(CpApply::getStatus, status.name())
        .set(CpApply::getUpdateTime, LocalDateTime.now())
        .eq(CpApply::getId, id)
        .execute();
  }

  @Override
  public boolean expireWaitById(Long id) {
    return Optional.ofNullable(query()
        .select(CpApply::getId)
        .eq(CpApply::getId, id)
        .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
        .lt(CpApply::getCreateTime, waitExpireBefore())
        .last(PageConstant.LIMIT_ONE)
        .getOne())
        .map(cpApply -> Objects.nonNull(cpApply.getId()))
        .orElse(Boolean.FALSE);
  }

  @Override
  public void expireOverdueWaitApplies(Long userId) {
    // The production status column does not accept a persisted EXPIRE value.
    // Expiration is derived from create_time so old WAIT rows do not block new applies.
  }

  private Timestamp waitExpireBefore() {
    return Timestamp.valueOf(LocalDateTime.now().minusHours(APPLY_WAIT_EXPIRE_HOURS));
  }

  @Override
  public void refuseStatusWait(Long acceptApplyUserId) {
    refuseStatusWait(acceptApplyUserId, CpRelationshipType.CP.name());
  }

  @Override
  public void refuseStatusWait(Long acceptApplyUserId, String relationType) {
    update()
        .set(CpApply::getStatus, CpApplyStatus.REFUSE.name())
        .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
        .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType))
        .eq(CpApply::getAcceptApplyUserId, acceptApplyUserId)
        .execute();
  }

  @Override
  public List<Long> refuseOtherWaitBetweenUsers(Long excludeApplyId, Long userId, Long cpUserId) {
    return refuseOtherWaitAppliesBetweenUsers(excludeApplyId, userId, cpUserId)
        .stream()
        .map(CpApply::getId)
        .filter(Objects::nonNull)
        .toList();
  }

  @Override
  public List<CpApply> refuseOtherWaitAppliesBetweenUsers(Long excludeApplyId, Long userId,
      Long cpUserId) {
    List<CpApply> applies = query()
        .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
        .ne(Objects.nonNull(excludeApplyId), CpApply::getId, excludeApplyId)
        .and(where -> where
            .nested(w -> w.eq(CpApply::getSendApplyUserId, userId)
                .eq(CpApply::getAcceptApplyUserId, cpUserId))
            .or()
            .nested(w -> w.eq(CpApply::getSendApplyUserId, cpUserId)
                .eq(CpApply::getAcceptApplyUserId, userId)))
        .list();
    List<Long> applyIds = applies.stream()
        .map(CpApply::getId)
        .filter(Objects::nonNull)
        .toList();
    if (applyIds.isEmpty()) {
      return applies;
    }
    update()
        .set(CpApply::getStatus, CpApplyStatus.REFUSE.name())
        .set(CpApply::getUpdateTime, LocalDateTime.now())
        .in(CpApply::getId, applyIds)
        .execute();
    applies.forEach(apply -> apply.setStatus(CpApplyStatus.REFUSE.name()));
    return applies;
  }

  @Override
  public List<CpApply> refuseWaitAppliesByUserAndRelation(Long excludeApplyId, Long userId,
      String relationType) {
    List<CpApply> applies = query()
        .eq(CpApply::getStatus, CpApplyStatus.WAIT.name())
        .eq(CpApply::getRelationType, CpRelationshipType.normalize(relationType))
        .ne(Objects.nonNull(excludeApplyId), CpApply::getId, excludeApplyId)
        .and(where -> where.eq(CpApply::getSendApplyUserId, userId)
            .or()
            .eq(CpApply::getAcceptApplyUserId, userId))
        .list();
    List<Long> applyIds = applies.stream()
        .map(CpApply::getId)
        .filter(Objects::nonNull)
        .toList();
    if (applyIds.isEmpty()) {
      return applies;
    }
    update()
        .set(CpApply::getStatus, CpApplyStatus.REFUSE.name())
        .set(CpApply::getUpdateTime, LocalDateTime.now())
        .in(CpApply::getId, applyIds)
        .execute();
    applies.forEach(apply -> apply.setStatus(CpApplyStatus.REFUSE.name()));
    return applies;
  }

  @Override
  public List<CpApply> listRefuse(Long userId) {
    return query()
        .eq(CpApply::getStatus, CpApplyStatus.REFUSE.name())
        .eq(CpApply::getAcceptApplyUserId, userId)
        .last(PageConstant.MAX_LIMIT)
        .list();
  }

  @Override
  public List<CpApply> listRefuseBetweenUsers(Long userId, Long cpUserId) {
    return query()
        .eq(CpApply::getStatus, CpApplyStatus.REFUSE.name())
        .and(where -> where
            .nested(w -> w.eq(CpApply::getSendApplyUserId, userId)
                .eq(CpApply::getAcceptApplyUserId, cpUserId))
            .or()
            .nested(w -> w.eq(CpApply::getSendApplyUserId, cpUserId)
                .eq(CpApply::getAcceptApplyUserId, userId)))
        .last(PageConstant.MAX_LIMIT)
        .list();
  }

  @Override
  public PageResult<CpApply> pageCpApply(CpApplyQryCmd query) {
    return query()
        .eq(Objects.nonNull(query.getSendApplyUserId()), CpApply::getSendApplyUserId,
            query.getSendApplyUserId())
        .eq(Objects.nonNull(query.getAcceptApplyUserId()), CpApply::getAcceptApplyUserId,
            query.getAcceptApplyUserId())
        .eq(StringUtils.isNotBlank(query.getStatus()), CpApply::getStatus,
            query.getStatus())
        .orderByDesc(CpApply::getCreateTime)
        .page(query.getPageQuery());
  }

}
