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

import com.google.common.collect.Maps;
import com.red.circle.other.infra.database.mongo.entity.sys.SysActivityCount;
import com.red.circle.other.infra.database.mongo.service.sys.SysActivityCountService;
import com.red.circle.other.inner.model.dto.sys.SysActivityCountType;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
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.util.Pair;
import org.springframework.stereotype.Service;

/**
 * 系统活动 实现.
 *
 * @author pengliang on 2022/8/16
 */
@Service
@RequiredArgsConstructor
public class SysActivityCountServiceImpl implements SysActivityCountService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void incrQuantity(Long activityId, Long userId, SysActivityCountType type, Long quantity) {
    if (exists(activityId, userId, type)) {
      mongoTemplate.updateFirst(Query.query(getUniquenessCriteria(activityId, userId, type)),
          new Update().inc("quantity", quantity)
              .set("updateTime", TimestampUtils.now()),
          SysActivityCount.class);
      return;
    }
    mongoTemplate.save(new SysActivityCount()
        .setId(getId(activityId, userId, type))
        .setActivityId(activityId)
        .setUserId(userId)
        .setType(type)
        .setQuantity(quantity)
        .setUpdateTime(TimestampUtils.now())
        .setExpiredTime(TimestampUtils.nowPlusDays(40 - (LocalDate.now().getDayOfMonth() - 1)))
    );
  }

  @Override
  public void incrBatchQuantity(List<SysActivityCount> sysActivityCounts) {
    mongoTemplate
        .bulkOps(BulkOperations.BulkMode.UNORDERED, SysActivityCount.class)
        .upsert(sysActivityCounts.stream()
            .map(count -> Pair.of(
                Query.query(Criteria.where("id")
                    .is(getId(count.getActivityId(), count.getUserId(), count.getType()))),
                new Update()
                    .set("id", getId(count.getActivityId(), count.getUserId(), count.getType()))
                    .set("userId", count.getUserId())
                    .set("activityId", count.getActivityId())
                    .set("type", count.getType())
                    .inc("quantity", count.getQuantity())
                    .set("updateTime", TimestampUtils.now())
                    .set("expiredTime",
                        TimestampUtils.nowPlusDays(40 - (LocalDate.now().getDayOfMonth() - 1)))
            )).collect(Collectors.toList()))
        .execute();
  }


  @Override
  public Map<SysActivityCountType, SysActivityCount> getUserCount(Long activityId,
      Long userId) {
    List<SysActivityCount> sysActivityCounts = mongoTemplate
        .find(Query.query(Criteria.where("activityId").is(activityId)
            .and("userId").is(userId)), SysActivityCount.class);
    if (CollectionUtils.isEmpty(sysActivityCounts)) {
      return Maps.newHashMap();
    }
    return sysActivityCounts.stream().collect(Collectors.toMap(SysActivityCount::getType, v -> v));
  }

  @Override
  public List<SysActivityCount> listRank(Long activityId, SysActivityCountType type,
      Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("activityId").is(activityId)
                .and("type").is(type))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), SysActivityCount.class);
  }


  private boolean exists(Long activityId, Long userId, SysActivityCountType type) {
    return mongoTemplate
        .exists(Query.query(getUniquenessCriteria(activityId, userId, type)),
            SysActivityCount.class);
  }

  private Criteria getUniquenessCriteria(Long activityId, Long userId, SysActivityCountType type) {
    return Criteria.where("id").is(getId(activityId, userId, type));
  }

  private String getId(Long activityId, Long userId, SysActivityCountType type) {
    return activityId + "_" + userId + "_" + type;
  }

}
