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

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.red.circle.other.infra.database.mongo.entity.user.profile.UserRunProfile;
import com.red.circle.other.infra.database.mongo.entity.user.profile.UserSpecialId;
import com.red.circle.other.infra.database.mongo.service.user.profile.UserRunProfileService;
import com.red.circle.other.inner.enums.material.BadgeConfigTypeEnum;
import com.red.circle.other.inner.model.dto.material.UseBadgeDTO;
import com.red.circle.other.inner.model.dto.material.UsePropsDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.tool.core.tuple.ImmutableKeyValuePair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;

/**
 * 用户运行资料 实现.
 *
 * @author pengliang on 2022/2/19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserRunProfileServiceImpl implements UserRunProfileService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void upsert(List<UserRunProfile> userRunProfiles) {
    mongoTemplate
        .bulkOps(BulkOperations.BulkMode.UNORDERED, UserRunProfile.class)
        .upsert(getPair(userRunProfiles))
        .execute();
  }

  private List<Pair<Query, Update>> getPair(List<UserRunProfile> userRunProfiles) {
    return userRunProfiles.stream().map(profile ->
        Pair.of(
            Query.query(Criteria.where("id").is(profile.getId())),
            getUpdateSelective(profile)
        )
    ).collect(Collectors.toList());
  }

  @Override
  public void updateSelectiveById(UserRunProfile userRunProfile) {
    if (Objects.isNull(userRunProfile) || Objects.isNull(userRunProfile.getId())) {
      return;
    }
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(userRunProfile.getId())),
        getUpdateSelective(userRunProfile),
        UserRunProfile.class);
  }

  @Override
  public void updateUserAutograph(Long id, String autograph) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("autograph", StringUtils.isBlankOrElse(autograph, "")),
        UserRunProfile.class);
  }

  @Override
  public void updateBatchUserAutographEmpty(Set<Long> ids) {
    if (CollectionUtils.isEmpty(ids)) {
      return;
    }
    mongoTemplate.updateMulti(Query.query(Criteria.where("id").in(ids)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("autograph", ""),
        UserRunProfile.class);
  }

  @Override
  public void updateUserNickname(Long id, String nickname) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("userNickname", nickname),
        UserRunProfile.class);
  }

  @Override
  public void updateBatchUserNickname(
      Collection<ImmutableKeyValuePair<Long, String>> userNicknames) {
    if (CollectionUtils.isEmpty(userNicknames)) {
      return;
    }
    List<Pair<Query, UpdateDefinition>> queryUpdatePair = userNicknames.stream()
        .filter(Objects::nonNull)
        .map(userNickname -> Pair.of(
            Query.query(Criteria.where("id").is(userNickname.getKey())),
            (UpdateDefinition) new Update()
                .set("userNickname", userNickname.getValue())
                .set("updateTime", TimestampUtils.now())
        )).toList();

    if (CollectionUtils.isEmpty(queryUpdatePair)) {
      return;
    }
    mongoTemplate
        .bulkOps(BulkOperations.BulkMode.UNORDERED, UserRunProfile.class)
        .updateOne(queryUpdatePair)
        .execute();
  }

  @Override
  public void updateUserAvatar(Long id, String avatar) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("userAvatar", avatar),
        UserRunProfile.class);
  }

  @Override
  public void updateUserAvatarBatch(Collection<Long> userIds, String avatar) {
    if (CollectionUtils.isEmpty(userIds)) {
      return;
    }
    mongoTemplate.updateMulti(Query.query(Criteria.where("id").in(userIds)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("userAvatar", avatar),
        UserRunProfile.class);
  }

  private Update getUpdateSelective(UserRunProfile userRunProfile) {
    Update update = new Update()
        .set("updateTime", TimestampUtils.now());

    if (Objects.nonNull(userRunProfile.getAccount())) {
      update.set("account", userRunProfile.getAccount());
    }

    if (StringUtils.isNotBlank(userRunProfile.getUserAvatar())) {
      update.set("userAvatar", userRunProfile.getUserAvatar());
    }

    if (StringUtils.isNotBlank(userRunProfile.getUserNickname())) {
      update.set("userNickname", userRunProfile.getUserNickname());
    }

    if (Objects.nonNull(userRunProfile.getUserSex())) {
      update.set("userSex", userRunProfile.getUserSex());
    }

    if (Objects.nonNull(userRunProfile.getUserType())) {
      update.set("userType", userRunProfile.getUserType());
    }

    if (Objects.nonNull(userRunProfile.getCountryId())) {
      update.set("countryId", userRunProfile.getCountryId());
    }

    if (Objects.nonNull(userRunProfile.getCountryName())) {
      update.set("countryName", userRunProfile.getCountryName());
    }

    if (Objects.nonNull(userRunProfile.getCountryCode())) {
      update.set("countryCode", userRunProfile.getCountryCode());
    }

    if (Objects.nonNull(userRunProfile.getAge())) {
      update.set("age", userRunProfile.getAge());
    }

    if (Objects.nonNull(userRunProfile.getBornYear())) {
      update.set("bornYear", userRunProfile.getBornYear());
    }

    if (Objects.nonNull(userRunProfile.getBornMonth())) {
      update.set("bornMonth", userRunProfile.getBornMonth());
    }

    if (Objects.nonNull(userRunProfile.getBornDay())) {
      update.set("bornDay", userRunProfile.getBornDay());
    }

    if (StringUtils.isNotBlank(userRunProfile.getAccountStatus())) {
      update.set("accountStatus", userRunProfile.getAccountStatus());
    }

    if (Objects.nonNull(userRunProfile.getFreezingTime())) {
      update.set("freezingTime", userRunProfile.getFreezingTime());
    }

    if (StringUtils.isNotBlank(userRunProfile.getOriginSys())) {
      update.set("originSys", userRunProfile.getOriginSys());
    }

    if (StringUtils.isNotBlank(userRunProfile.getSysOriginChild())) {
      update.set("sysOriginChild", userRunProfile.getSysOriginChild());
    }

    if (Objects.nonNull(userRunProfile.getDel())) {
      update.set("del", userRunProfile.getDel());
    }

    if (Objects.nonNull(userRunProfile.getCreateTime())) {
      update.set("createTime", userRunProfile.getCreateTime());
    }

    if (StringUtils.isNotBlank(userRunProfile.getAutograph())) {
      update.set("autograph", userRunProfile.getAutograph());
    }

    if (StringUtils.isNotBlank(userRunProfile.getHobby())) {
      update.set("hobby", userRunProfile.getHobby());
    }

    if (userRunProfile.getBackgroundPhotos() != null) {
      update.set("backgroundPhotos",CollectionUtils.isNotEmpty(userRunProfile.getBackgroundPhotos()) ?
              userRunProfile.getBackgroundPhotos() :
              CollectionUtils.newArrayList() );
    }

    if (userRunProfile.getPersonalPhotos() != null) {
      update.set("personalPhotos", CollectionUtils.isNotEmpty(userRunProfile.getPersonalPhotos()) ?
              userRunProfile.getPersonalPhotos() :
              CollectionUtils.newArrayList() );
    }

    if (CollectionUtils.isNotEmpty(userRunProfile.getCpList())) {
      update.set("cpList", userRunProfile.getCpList());
    }

    if (Objects.nonNull(userRunProfile.getOwnSpecialId())) {
      update.set("ownSpecialId", userRunProfile.getOwnSpecialId());
    }

    if (Objects.nonNull(userRunProfile.getUseProps())) {
      update.set("useProps", userRunProfile.getUseProps());
    }

    if (Objects.nonNull(userRunProfile.getWearBadge())) {
      update.set("wearBadge", userRunProfile.getWearBadge());
    }
      if (Objects.nonNull(userRunProfile.getWearHonor())) {
          update.set("wearHonor", userRunProfile.getWearHonor());
      }
      if (Objects.nonNull(userRunProfile.getFirstRecharge())) {
          update.set("firstRecharge", userRunProfile.getFirstRecharge());
      }
      if (Objects.nonNull(userRunProfile.getFirstRechargeEndTime())) {
          update.set("firstRechargeEndTime", userRunProfile.getFirstRechargeEndTime());
      }

    return update;
  }

  @Override
  public void updateUserOwnSpecialId(Long id, UserSpecialId ownSpecialId) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("ownSpecialId", ownSpecialId),
        UserRunProfile.class);
  }

  @Override
  public void updateUserUsePropsOverlay(Long id, List<UsePropsDTO> useProps) {
    if (CollectionUtils.isNotEmpty(useProps)) {
      useProps = useProps.stream()
          .filter(userUsePropsEntity -> Objects.nonNull(userUsePropsEntity.getPropsResources()))
          .collect(Collectors.toList());
    }

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("useProps", filterPropsNotAvailable(useProps)),
        UserRunProfile.class);
  }

  @Override
  public void updateUserUseProps(Long id, UsePropsDTO useProp) {
    if (Objects.isNull(id) || Objects.isNull(useProp) || Objects.isNull(
        useProp.getPropsResources())) {
      return;
    }

    if (useProp.getExpireTime() <= TimestampUtils.now().getTime()) {
      return;
    }
    // cleanProps(id, useProp.getPropsResources().getType());

    UserRunProfile userRunProfile = getById(id);

    if (Objects.isNull(userRunProfile)) {
      log.warn("切换道具用户资料不存在!!!");
      return;
    }

    List<UsePropsDTO> usePropsEntities = (CollectionUtils.isEmpty(userRunProfile.getUseProps())
        ? new ArrayList<UsePropsDTO>()
        : userRunProfile.getUseProps())
        .stream().filter(use -> use.getExpireTime() > TimestampUtils.now().getTime()
            && !Objects.equals(use.propsResourcesType(), useProp.propsResourcesType()))
        .collect(Collectors.toList());

    usePropsEntities.add(useProp);

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("useProps", usePropsEntities)
            .set("updateTime", TimestampUtils.now()),
        UserRunProfile.class);
  }

  private void cleanProps(Long userId, String propsType) {
    // aws 不支持propsResources.type 带点
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(userId)),
        new Update()
            .pull("useProps", new BasicDBObject("expireTime",
                new BasicDBObject("$lte", TimestampUtils.now().getTime())))
            .pull("useProps", new BasicDBObject("propsResources.type",
                new BasicDBObject("$eq", propsType)))
            .set("updateTime", TimestampUtils.now()),
        UserRunProfile.class);
  }

  @Override
  public void updateUserWearBadgesOverlay(Long id, List<UseBadgeDTO> wearBadges) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("wearBadge", filterBadgeNotAvailable(wearBadges))
            .set("wearHonor", filterHonorNotAvailable(wearBadges))
            ,
        UserRunProfile.class);
  }

  @Override
  public void removeExpiredTimeSource(Long id) {
    UserRunProfile userRunProfile = getById(id);
    if (Objects.isNull(userRunProfile)) {
      return;
    }

    if (CollectionUtils.isNotEmpty(userRunProfile.getUseProps())) {
      updateUserUsePropsOverlay(userRunProfile.getId(),
          filterPropsNotAvailable(userRunProfile.getUseProps()));
      return;
    }

    if (CollectionUtils.isNotEmpty(userRunProfile.getWearBadge())) {
      updateUserWearBadgesOverlay(userRunProfile.getId(),
          filterBadgeNotAvailable(userRunProfile.getWearBadge()));
      return;
    }

    if (Objects.nonNull(userRunProfile.getOwnSpecialId())
        && !checkAvailable(getOwnSpecialIdExpiredTime(userRunProfile))) {
      updateUserOwnSpecialId(userRunProfile.getId(), null);
    }

  }

  private List<UsePropsDTO> filterPropsNotAvailable(List<UsePropsDTO> useProps) {
    if (CollectionUtils.isNotEmpty(useProps)) {
      return useProps.stream().filter(badge -> checkAvailable(badge.getExpireTime()))
          .collect(Collectors.toList());
    }
    return useProps;
  }

  private List<UseBadgeDTO> filterBadgeNotAvailable(List<UseBadgeDTO> wearBadge) {
    if (CollectionUtils.isNotEmpty(wearBadge)) {
      return wearBadge.stream()
              .filter(badge -> !BadgeConfigTypeEnum.isHonorType(badge.getType()))
              .filter(badge -> checkAvailable(badge.getExpireTime()))
          .collect(Collectors.toList());
    }
    return wearBadge;
  }

    private List<UseBadgeDTO> filterHonorNotAvailable(List<UseBadgeDTO> wearBadge) {
        if (CollectionUtils.isNotEmpty(wearBadge)) {
            return wearBadge.stream()
                    .filter(badge -> BadgeConfigTypeEnum.isHonorType(badge.getType()))
                    .filter(badge -> checkAvailable(badge.getExpireTime()))
                    .collect(Collectors.toList());
        }
        return wearBadge;
    }

  @Override
  public UserRunProfile getById(Long id) {
    return mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)), UserRunProfile.class);
  }

  @Override
  public UserRunProfile getByAccount(String account) {
    return mongoTemplate.findOne(Query.query(Criteria.where("account").is(account)),
        UserRunProfile.class);
  }

  @Override
  public List<UserRunProfile> listByAccount(Set<String> accounts) {
    return mongoTemplate.find(Query.query(Criteria.where("account").in(accounts)),
        UserRunProfile.class);
  }

  @Override
  public List<UserRunProfile> listByIds(Set<Long> ids) {
    if (CollectionUtils.isEmpty(ids)) {
      return Lists.newArrayList();
    }
    return mongoTemplate.find(Query.query(Criteria.where("id").in(ids)), UserRunProfile.class);
  }

  @Override
  public void remove(Long id) {
    if (Objects.nonNull(id)) {
      mongoTemplate.remove(Query.query(Criteria.where("id").is(id)), UserRunProfile.class);
    }
  }

  @Override
  public void remove(Collection<Long> ids) {
    if (CollectionUtils.isNotEmpty(ids)) {
      mongoTemplate.remove(Query.query(Criteria.where("_id").in(ids)), UserRunProfile.class);
    }
  }

  private long getOwnSpecialIdExpiredTime(UserRunProfile userRunProfile) {
    return userRunProfile.getOwnSpecialId().getExpiredTime().getTime();
  }

  /**
   * true 可用. false 不可用.
   */
  private boolean checkAvailable(Long expireTime) {
    return Objects.isNull(expireTime) || expireTime > TimestampUtils.now().getTime();
  }

  @Override
  public void updateBackgroundPhotoStatus(Long userId, String photoUrl, Integer status, String rejectReason) {
    UserRunProfile profile = getById(userId);
    if (profile == null || CollectionUtils.isEmpty(profile.getBackgroundPhotos())) {
      return;
    }

    profile.getBackgroundPhotos().stream()
        .filter(photo -> photo.getUrl().equals(photoUrl))
        .findFirst()
        .ifPresent(photo -> {
          photo.setStatus(status);
          photo.setRejectReason(rejectReason);
        });

    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(userId)),
        new Update()
            .set("backgroundPhotos", profile.getBackgroundPhotos())
            .set("updateTime", TimestampUtils.now()),
        UserRunProfile.class
    );
  }

  @Override
  public void updatePersonalPhotoStatus(Long userId, String photoUrl, Integer status, String rejectReason) {
    UserRunProfile profile = getById(userId);
    if (profile == null || CollectionUtils.isEmpty(profile.getPersonalPhotos())) {
      return;
    }

    profile.getPersonalPhotos().stream()
        .filter(photo -> photo.getUrl().equals(photoUrl))
        .findFirst()
        .ifPresent(photo -> {
          photo.setStatus(status);
          photo.setRejectReason(rejectReason);
        });

    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(userId)),
        new Update()
            .set("personalPhotos", profile.getPersonalPhotos())
            .set("updateTime", TimestampUtils.now()),
        UserRunProfile.class
    );
  }
}
