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

import com.google.common.collect.Maps;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.infra.database.mongo.entity.live.RoomContributionRankCount;
import com.red.circle.other.infra.database.mongo.service.live.RoomContributionRankCountService;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.LocalDateUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.ImmutableTriple;
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 2021/9/15
 */
@Service
@RequiredArgsConstructor
public class RoomContributionRankCountServiceImpl implements RoomContributionRankCountService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void incr(String sysOrigin, Long roomId, Long userId, BigDecimal quantity) {

    if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
      return;
    }

    // 类型，过期时间，分组日期
    List<ImmutableTriple<String, Timestamp, Integer>> dates = List.of(
        ImmutableTriple.of("HOUR",
            TimestampUtils.nowPlusHours(1),
            dateNumberNowHour()
        ),
        ImmutableTriple.of("DAY",
            TimestampUtils.nowPlusDays(1),
            dateNumberNowDay()
        ),
        ImmutableTriple.of("WEEK",
            TimestampUtils.nowPlusDays(7),
            dateNumberNowWeek()
        ),
        ImmutableTriple.of("MONTH",
            TimestampUtils.nowPlusDays(31),
            dateNumberNowMonth()
        )
    );

    BigDecimal finalQuantity = quantity;
    List<Pair<Query, Update>> queryUpdatePair = dates.stream()
        .map(date -> Pair.of(
            Query.query(
                Criteria.where("id").is(getId(roomId, userId, date.getLeft(), date.getRight()))),
            new Update()
                .set("id", getId(roomId, userId, date.getLeft(), date.getRight()))
                .set("sysOrigin", sysOrigin)
                .set("userId", userId)
                .set("roomId", roomId)
                .set("dateType", date.getLeft())
                .inc("quantity", finalQuantity)
                .set("dateNumber", date.getRight())
                .set("updateTime", TimestampUtils.now())
                .set("expiredTime", date.getMiddle())
        )).collect(Collectors.toList());

    try {
      mongoTemplate
          .bulkOps(BulkOperations.BulkMode.UNORDERED, RoomContributionRankCount.class)
          .upsert(queryUpdatePair)
          .execute();
    } catch (Exception e) {
      // ignore
    }
  }

  @Override
  public void decr(Long roomId, Long userId, BigDecimal quantity) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("roomId").is(roomId)
            .and("userId").is(userId)
        ), new Update()
            .inc("quantity", -quantity.longValue())
        , RoomContributionRankCount.class);
  }

  @Override
  public List<RoomContributionRankCount> listTop(String dateType, Long roomId, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("roomId").is(roomId)
                .and("dateType").is(dateType)
                .and("dateNumber").is(getDateNumber(dateType)))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(PageConstant.limitSizeStrict(size)), RoomContributionRankCount.class);
  }

  private Integer getDateNumber(String dateType) {
    if (Objects.equals(dateType, "HOUR")) {
      return dateNumberNowHour();
    }
    if (Objects.equals(dateType, "DAY")) {
      return dateNumberNowDay();
    }
    if (Objects.equals(dateType, "WEEK")) {
      return dateNumberNowWeek();
    }
    if (Objects.equals(dateType, "MONTH")) {
      return dateNumberNowMonth();
    }
    return 0;
  }

  private Integer dateNumberNowHour() {
    return Integer.valueOf(ZonedDateTimeAsiaRiyadhUtils.nowFormat("yyyyMMddHH"));
  }

  private Integer dateNumberNowMonth() {
    return ZonedDateTimeAsiaRiyadhUtils.nowYearMonthToInt();
  }

  private Integer dateNumberNowWeek() {
    return ZonedDateTimeAsiaRiyadhUtils.nowWeekMondayToInt();
  }

  private Integer dateNumberNowDay() {
    return ZonedDateTimeAsiaRiyadhUtils.nowDateToInt();
  }

  @Override
  public List<RoomContributionRankCount> listDayTop(Long roomId, Integer size) {
    return listTop("DAY", roomId, size);
  }

  @Override
  public Map<Long, Long> mapDayByUserIds(Long roomId, Set<Long> userIds) {
    if (CollectionUtils.isEmpty(userIds)) {
      return Maps.newHashMap();
    }
    List<RoomContributionRankCount> roomContributionCountDailies = mongoTemplate
        .find(Query.query(
            Criteria.where("roomId").is(roomId)
                .and("dateType").is("DAY")
                .and("dateNumber").is(LocalDateUtils.nowToInteger())
                .and("userId").in(userIds)
        ), RoomContributionRankCount.class);

    if (CollectionUtils.isEmpty(roomContributionCountDailies)) {
      return userIds.stream()
          .collect(Collectors.toMap(k -> k, v -> 0L));
    }

    Map<Long, Long> quantityMap = roomContributionCountDailies.stream()
        .collect(Collectors
            .toMap(RoomContributionRankCount::getUserId, RoomContributionRankCount::getQuantity));

    return userIds.stream().map(userId -> {
      Long quantity = quantityMap.get(userId);
      return Objects.isNull(quantity) ? ImmutablePair.of(userId, 0L)
          : ImmutablePair.of(userId, quantity);
    }).collect(Collectors.toMap(ImmutablePair::getLeft, ImmutablePair::getRight));
  }

  @Override
  public RoomContributionRankCount getDayTopOne(Long roomId) {
    return mongoTemplate
        .findOne(Query.query(Criteria.where("roomId").is(roomId)
                    .and("dateType").is("DAY")
                    .and("dateNumber").is(LocalDateUtils.nowToInteger()))
                .with(Sort.by(Sort.Order.desc("quantity"))),
            RoomContributionRankCount.class);
  }

  @Override
  public RoomContributionRankCount getDayByUserId(Long roomId, Long userId) {
    return mongoTemplate
        .findOne(Query.query(Criteria.where("roomId").is(roomId)
                .and("userId").is(userId)
                .and("dateType").is("DAY")
                .and("dateNumber").is(LocalDateUtils.nowToInteger())),
            RoomContributionRankCount.class);
  }

  @Override
  public void correctionData(BigDecimal quantity, Long roomId, Long userId) {

    if (Objects.isNull(quantity) || quantity.longValue() <= 0) {
      return;
    }

    updateCorrectionData(roomId, userId, "HOUR", quantity.longValue());
    updateCorrectionData(roomId, userId, "DAY", quantity.longValue());
    updateCorrectionData(roomId, userId, "WEEK", quantity.longValue());
    updateCorrectionData(roomId, userId, "MONTH", quantity.longValue());

  }

  /**
   * 矫正数据
   */
  private void updateCorrectionData(Long roomId, Long userId, String dateType, Long quantity) {

    RoomContributionRankCount count = mongoTemplate
        .findOne(Query.query(Criteria.where("roomId").is(roomId)
            .and("userId").is(userId)
            .and("dateType").is(dateType)
            .and("dateNumber").is(getDateNumber(dateType))), RoomContributionRankCount.class);
    if (Objects.isNull(count) || Objects.isNull(count.getQuantity())) {
      return;
    }

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(count.getId())),
        new Update().set("contributionValue",
            count.getQuantity() > quantity ? count.getQuantity() - quantity : 0L),
        RoomContributionRankCount.class);

  }

  public String getId(Long roomId, Long userId, String dateType, Integer dateNumber) {
    return roomId + "_" + userId + "_" + dateType + "_" + dateNumber;
  }

}
