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

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Filters;
import com.red.circle.component.mongodb.config.MongoPrimaryService;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.infra.database.mongo.dto.CountExclusiveGift;
import com.red.circle.other.infra.database.mongo.dto.GiftStatisticResult;
import com.red.circle.other.infra.database.mongo.dto.RoomActiveIndexDTO;
import com.red.circle.other.infra.database.mongo.dto.query.GiftGiveRunningWaterQuery;
import com.red.circle.other.infra.database.mongo.dto.query.NewSumAnchorTargetQuery;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftAcceptUser;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftGiveProcessLog;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftGiveRunningWater;
import com.red.circle.other.infra.database.mongo.entity.gift.GiftValue;
import com.red.circle.other.infra.database.mongo.service.gift.GiftGiveRunningWaterService;
import com.red.circle.other.inner.model.cmd.gift.SumAnchorTargetQryCmd;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.LocalDateTimeUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.bson.types.Decimal128;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
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.stereotype.Service;

/**
 * @author pengliang on 2022/7/25
 */
@Service
@RequiredArgsConstructor
public class GiftGiveRunningWaterServiceImpl implements GiftGiveRunningWaterService {

  private final MongoTemplate mongoTemplate;
  private final MongoPrimaryService mongoPrimaryService;

  @Override
  public void removeById(Long id) {
    mongoTemplate.remove(Query.query(Criteria.where("id").is(id)), GiftGiveRunningWater.class);
  }

  @Override
  public Boolean add(GiftGiveRunningWater runningWater) {
    if (Objects.isNull(runningWater.getExpiredTime())) {
      runningWater.setExpiredTime(
          // 过期时间更平均
          TimestampUtils.nowPlusDays(120 - (LocalDate.now().getDayOfMonth() - 1)));
    }
    try {
      mongoTemplate.save(runningWater);
    } catch (DuplicateKeyException ex) {
      // ignore
      return false;
    }
    return true;
  }

  @Override
  public GiftGiveRunningWater getByIdPrimary(Long id) {
    return mongoPrimaryService.find("gift_give_running_water",
        Filters.eq("_id", id),
        documents -> {
          Document document = documents.get(0);
          Document giftValue = document.get("giftValue", Document.class);
          List<Document> acceptUsers = document.getList("acceptUsers", Document.class);
          List<Document> logs = document.getList("logs", Document.class);
          return GiftGiveRunningWater.builder()
              .id(document.getLong("_id"))
              .songId(document.getLong("songId"))
              .trackId(document.getString("trackId"))
              .originId(document.getString("originId"))
              .dynamicContentId(document.getString("dynamicContentId"))
              .sysOrigin(document.getString("sysOrigin"))
              .requestPlatform(document.getString("requestPlatform"))
              .userId(document.getLong("userId"))
              .anchor(document.getBoolean("anchor"))
              .giftId(document.getLong("giftId"))
              .refunded(document.getBoolean("refunded"))
              .processed(document.getBoolean("processed"))
              .giftType(document.getString("giftType"))
              .luckyGift(document.getBoolean("luckyGift"))
              .checkBlessingsGift(document.getBoolean("checkBlessingsGift"))
              .giftValue(GiftValue.builder()
                  .currencyType(giftValue.getString("currencyType"))
                  .giftType(giftValue.getString("giftType"))
                  .unitPrice(getDocBigDecimal(giftValue, "unitPrice"))
                  .quantity(giftValue.getInteger("quantity"))
                  .userSize(giftValue.getInteger("userSize"))
                  .giftValue(getDocBigDecimal(giftValue, "giftValue"))
                  .actualAmount(getDocBigDecimal(giftValue, "actualAmount"))
                  .bag(giftValue.getBoolean("bag"))
                  .build())
              .acceptUsers(acceptUsers.stream().map(acceptUser -> GiftAcceptUser.builder()
                      .acceptUserId(acceptUser.getLong("acceptUserId"))
                      .anchor(acceptUser.getBoolean("anchor"))
                      .receiptId(acceptUser.getLong("receiptId"))
                      .acceptAmount(getDocBigDecimal(acceptUser, "acceptAmount"))
                      .targetAmount(getDocBigDecimal(acceptUser, "targetAmount"))
                      .giftValue(getDocBigDecimal(acceptUser, "giftValue"))
                      .countTargetAmount(acceptUser.getBoolean("countTargetAmount"))
                      .countBillId(acceptUser.getLong("countBillId"))
                      .percentage(getDocBigDecimal(acceptUser, "percentage"))
                      .selfPercentage(getDocBigDecimal(acceptUser, "selfPercentage"))
                      .region(acceptUser.getString("region"))
                      .regionEq(acceptUser.getBoolean("regionEq"))
                      .regionIdStr(acceptUser.getString("regionIdStr"))
                      .seatIndex(acceptUser.getInteger("seatIndex"))
                      .build())
                  .collect(Collectors.toList()))
              .logs(logs.stream().map(log -> GiftGiveProcessLog.builder()
                  .content(log.getString("content"))
                  .createTime(getTimestamp(log, "createTime"))
                  .build()).collect(Collectors.toList()))
              .createDateRiyadhNum(document.getInteger("createDateRiyadhNum"))
              .createTime(getTimestamp(document, "createTime"))
              .actualCreateTime(getTimestamp(document, "actualCreateTime"))
              .updateTime(getTimestamp(document, "updateTime"))
              .expiredTime(getTimestamp(document, "expiredTime"))
              .build();
        });
  }

  private Timestamp getTimestamp(Document document, String field) {
    Date val = document.getDate(field);
    if (Objects.isNull(val)) {
      return null;
    }
    return new Timestamp(val.getTime());
  }

  private BigDecimal getDocBigDecimal(Document document, String field) {
    Decimal128 val = document.get(field, Decimal128.class);
    if (Objects.isNull(val)) {
      return null;
    }
    return val.bigDecimalValue();
  }

  @Override
  public void addLog(Long id, String content) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update().push("logs", new GiftGiveProcessLog()
            .setContent(content)
            .setCreateTime(TimestampUtils.now())),
        GiftGiveRunningWater.class);
  }

  @Override
  public List<GiftGiveRunningWater> list(GiftGiveRunningWaterQuery query) {
    Criteria criteria = StringUtils.isBlank(query.getSysOrigin())
        ? new Criteria()
        : Criteria.where("sysOrigin").is(query.getSysOrigin());

    if (Objects.nonNull(query.getGiftId())) {
      criteria.and("giftId").is(query.getGiftId());
    }

    if (StringUtils.isNotBlank(query.getTrackId())) {
      criteria.and("trackId").is(query.getTrackId());
    }

    if (Objects.nonNull(query.getOriginId())) {
      criteria.and("originId").is(query.getOriginId());
    }

    if (Objects.nonNull(query.getSendUserId())) {
      criteria.and("userId").is(query.getSendUserId());
    }

    if (StringUtils.isNotBlank(query.getGiftValueType())) {
      criteria.and("giftValue.giftType").is(query.getGiftValueType());
    }

    if (Objects.nonNull(query.getLastId())) {
      criteria.and("id").lt(query.getLastId());
    }

    if (Objects.nonNull(query.getStartTime()) && Objects.nonNull(query.getEndTime())) {
      criteria.andOperator(
          Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
          Criteria.where("createTime").lte(query.endTimeToLocalDateTime())
      );
    }

    if (Objects.nonNull(query.getAcceptUserId())) {
      criteria.and("acceptUsers")
          .elemMatch(Criteria.where("acceptUserId").is(query.getAcceptUserId()));
    }

    if (Objects.isNull(query.getLimit())) {
      query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE);
    }
    return mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(query.getLimit()),
        GiftGiveRunningWater.class);
  }

  @Override
  public List<GiftGiveRunningWater> listActivityDouble(List<Long> giftIds, Timestamp startTime,
      Long lastId) {
    Criteria criteria = Criteria.where("giftId").in(giftIds)
        .and("createTime").gte(startTime)
        .and("createDateRiyadhNum").lte(20230410)
        .and("refunded").is(Boolean.FALSE);

    if (Objects.nonNull(lastId)) {
      criteria.and("id").lt(lastId);
    }
    return mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(30000),
        GiftGiveRunningWater.class);
  }

  @Override
  public List<GiftGiveRunningWater> listThisMonthAnchorNotCountTarget(Integer limit) {
    return mongoTemplate.find(Query.query(Criteria.where("acceptUsers")
                .elemMatch(Criteria.where("countTargetAmount").is(Boolean.FALSE)
                    .and("anchor").is(Boolean.TRUE))
                // 排除已退款
                .and("refunded").is(Boolean.FALSE)
                .and("createTime").gte(LocalDateTimeUtils.nowFirstDayOfMonthStart().minusHours(8)))
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(limit),
        GiftGiveRunningWater.class);
  }

  @Override
  public String sumGiftValue(GiftGiveRunningWaterQuery query) {
    Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin())
        .and("userId").is(query.getSendUserId())
        .and("refunded").is(Boolean.FALSE)
        .and("acceptUsers")
        .elemMatch(Criteria.where("acceptUserId").is(query.getAcceptUserId()))
        .andOperator(
            Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
            Criteria.where("createTime").lte(query.endTimeToLocalDateTime()));
    if (Objects.nonNull(query.getGiftId())) {
      criteria.and("giftId").is(query.getGiftId());
    }
    if (StringUtils.isNotBlank(query.getTrackId())) {
      criteria.and("trackId").is(query.getTrackId());
    }
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(criteria),
        Aggregation.group("userId").sum("giftValue.giftValue").as("totalAmount")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isNotEmpty(basicDBObjectList)) {
      return basicDBObjectList.get(0).getString("totalAmount");
    }
    return "0";
  }

  @Override
  public String sumGiftQuantity(GiftGiveRunningWaterQuery query) {
    Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin())
        .and("giftId").is(query.getGiftId())
        .and("refunded").is(Boolean.FALSE)
        .and("acceptUsers")
        .elemMatch(Criteria.where("acceptUserId").is(query.getAcceptUserId()))
        .andOperator(
            Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
            Criteria.where("createTime").lte(query.endTimeToLocalDateTime()));

    if (Objects.nonNull(query.getSendUserId())) {
      criteria.and("userId").is(query.getSendUserId());
    }

    if (Objects.nonNull(query.getTrackId())) {
      criteria.and("trackId").is(query.getTrackId());
    }
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(criteria),
        Aggregation.group("giftId").sum("giftValue.quantity").as("totalAmount")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isNotEmpty(basicDBObjectList)) {
      return basicDBObjectList.get(0).getString("totalAmount");
    }
    return "0";
  }

  @Override
  public void updateAcceptUserReceiptId(Long id, Long acceptUserId, Long receiptId) {
    if (Objects.isNull(id) || Objects.isNull(acceptUserId) || Objects.isNull(receiptId)) {
      return;
    }
    mongoTemplate
        .updateFirst(Query.query(Criteria.where("id").is(id)),
            new Update()
                .set("acceptUsers.$[i].receiptId", receiptId)
                .filterArray(Criteria.where("i.acceptUserId").is(acceptUserId)),
            GiftGiveRunningWater.class);
  }

  @Override
  public void updateAcceptUserCountTargetAmountTrue(Long id, Long countBillId, Long acceptUserId) {
    if (Objects.isNull(id) || Objects.isNull(acceptUserId)) {
      return;
    }
    mongoTemplate
        .updateFirst(Query.query(Criteria.where("id").is(id)),
            new Update()
                .set("acceptUsers.$[i].countTargetAmount", Boolean.TRUE)
                .set("acceptUsers.$[i].countBillId", countBillId)
                .filterArray(Criteria.where("i.acceptUserId").is(acceptUserId)),
            GiftGiveRunningWater.class);
  }


  @Override
  public List<Long> listLatestRoomContributionUserIs(Long roomId,
      Timestamp startTime, Integer size) {
    Query query = Query.query(Criteria.where("originId").is(roomId.toString())
            // 排除已退款数据
            .and("refunded").is(Boolean.FALSE)
            .andOperator(
                Criteria.where("createTime").gte(startTime),
                Criteria.where("createTime").lte(TimestampUtils.now())))
        .with(Sort.by(Sort.Order.desc("id")))
        .limit(size);
    query.fields().include("userId");
    List<GiftGiveRunningWater> giftGiveRunningWaters = mongoTemplate.find(query,
        GiftGiveRunningWater.class);

    if (CollectionUtils.isEmpty(giftGiveRunningWaters)) {
      return Lists.newArrayList();
    }

    return giftGiveRunningWaters.stream().map(GiftGiveRunningWater::getUserId)
        .collect(Collectors.toList());
  }

  @Override
  public RoomActiveIndexDTO countRoomActiveIndex(Long roomId, Timestamp startTime,
      Timestamp endTime) {

    // TODO 升级后两个 Aggregation.group方式不支持存在错误； 检查点：springboot2.7.9
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(Criteria.where("originId").is(roomId.toString())
            // 排除已退款礼物
            .and("refunded").is(Boolean.FALSE)
            .andOperator(
                Criteria.where("createTime").gte(startTime),
                Criteria.where("createTime").lte(endTime))),
        Aggregation.group("originId").sum("giftValue.actualAmount").as("sendGiftAmount"),
        Aggregation.group("userId").count().as("sendGiftUserQuantity")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isNotEmpty(basicDBObjectList)) {
      BasicDBObject basicDBObject = basicDBObjectList.get(0);
      return new RoomActiveIndexDTO()
          .setSendGiftUserQuantity(basicDBObject.getLong("sendGiftUserQuantity"))
          .setSendGiftAmount(new BigDecimal(basicDBObject.getString("sendGiftAmount")));
    }
    return new RoomActiveIndexDTO()
        .setSendGiftUserQuantity(0L)
        .setSendGiftAmount(BigDecimal.ZERO);
  }

  @Override
  public List<CountExclusiveGift> countExclusiveGift(Integer start, Integer end) {

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(Criteria.where("giftValue.giftType").is("EXCLUSIVE")
            .and("refunded").is(Boolean.FALSE)
            .andOperator(
                Criteria.where("createDateRiyadhNum").gte(start),
                Criteria.where("createDateRiyadhNum").lte(end))),
        Aggregation.group("sysOrigin", "giftId").sum("giftValue.actualAmount").as("quantity")
    );

    AggregationResults<CountExclusiveGift> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        CountExclusiveGift.class);
    List<CountExclusiveGift> countExclusiveGifts = aggregationResults.getMappedResults();

    return CollectionUtils.isNotEmpty(countExclusiveGifts)
        ? countExclusiveGifts.stream()
        .sorted(Comparator.comparing(CountExclusiveGift::getQuantity, Comparator.reverseOrder()))
        .collect(Collectors.toList())
        : countExclusiveGifts;
  }

  @Override
  public BigDecimal sumAnchorTarget(SumAnchorTargetQryCmd query) {

    Criteria acceptUserElemMatch = Criteria.where("acceptUserId").is(query.getAcceptUserId());

    if (Objects.equals(query.getCheckAnchor(), Boolean.TRUE)) {
      acceptUserElemMatch.and("anchor").is(Boolean.TRUE);
    }

    Criteria firstFilter = Criteria.where("acceptUsers")
        .elemMatch(acceptUserElemMatch)
        .and("refunded").is(Boolean.FALSE);

    if (Objects.nonNull(query.getStartTime()) && Objects.nonNull(query.getEndTime())) {
      firstFilter.andOperator(
          Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
          Criteria.where("createTime").lte(query.endTimeToLocalDateTime())
      );
    }

    if (Objects.nonNull(query.getStartDateNumber()) && Objects.nonNull(query.getEndDateNumber())) {
      firstFilter.andOperator(
          Criteria.where("createDateRiyadhNum").gte(query.getStartDateNumber()),
          Criteria.where("createDateRiyadhNum").lte(query.getEndDateNumber())
      );
    }

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(firstFilter),
        Aggregation.unwind("acceptUsers"),
        Aggregation.match(Criteria.where("acceptUsers.acceptUserId").is(query.getAcceptUserId())),
        Aggregation.group("sysOrigin")
            .sum("acceptUsers.targetAmount").as("quantity")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isEmpty(basicDBObjectList)) {
      return BigDecimal.ZERO;
    }

    return new BigDecimal(basicDBObjectList.get(0).getString("quantity"));
  }


  @Override
  public BigDecimal sumAnchorTarget(NewSumAnchorTargetQuery query) {

    Criteria acceptUserElemMatch = Criteria.where("acceptUserId").is(query.getAcceptUserId());

    if (Objects.equals(query.getCheckAnchor(), Boolean.TRUE)) {
      acceptUserElemMatch.and("anchor").is(Boolean.TRUE);
    }

    Criteria firstFilter = Criteria.where("acceptUsers").elemMatch(acceptUserElemMatch)
        .and("refunded").is(Boolean.FALSE)
        .andOperator(
            Criteria.where("createDateRiyadhNum").gte(query.getStartCreateDateRiyadhNum()),
            Criteria.where("createDateRiyadhNum").lte(query.getEndCreateDateRiyadhNum())
        );

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(firstFilter),
        Aggregation.unwind("acceptUsers"),
        Aggregation.match(Criteria.where("acceptUsers.acceptUserId").is(query.getAcceptUserId())),
        Aggregation.group("sysOrigin")
            .sum("acceptUsers.targetAmount").as("quantity")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isEmpty(basicDBObjectList)) {
      return BigDecimal.ZERO;
    }

    return new BigDecimal(basicDBObjectList.get(0).getString("quantity"));
  }


  @Override
  public BigDecimal sumAnchorTargetLuckyGift(SumAnchorTargetQryCmd query) {

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

    // 含错误的幸运礼物目标
    Long errorTargetLuckyGift = getSumAnchorTargetLuckyGift(query);
    if (Objects.isNull(errorTargetLuckyGift)) {
      return sumAnchorTarget;
    }

    // 将总目标上的幸运礼物目标全部移除
    if (sumAnchorTarget.longValue() > errorTargetLuckyGift) {
      sumAnchorTarget = sumAnchorTarget.subtract(BigDecimal.valueOf(errorTargetLuckyGift));
    }
    if (sumAnchorTarget.compareTo(BigDecimal.ZERO) <= 0) {
      return BigDecimal.ZERO;
    }

    long targetLuckyGift = errorTargetLuckyGift / 3;
    if (targetLuckyGift <= 0) {
      // 移除幸运礼物后的目标
      return sumAnchorTarget;
    }

    // 加上正确的幸运礼物目标返回
    return sumAnchorTarget.add(BigDecimal.valueOf(targetLuckyGift));

  }

  @Override
  public void updateGiftStatusRefunded(Long sendUserId, Integer startDateNumber,
      Integer endDateNumber) {

    mongoTemplate.updateMulti(Query.query(
            Criteria.where("userId").is(sendUserId).andOperator(
                Criteria.where("createDateRiyadhNum").gte(startDateNumber),
                Criteria.where("createDateRiyadhNum").lte(endDateNumber))),
        new Update().set("refunded", Boolean.TRUE)
            .set("updateTime", TimestampUtils.now()),
        GiftGiveRunningWater.class);
  }

  @Override
  public BigDecimal countRoomContribution(Long roomId, Integer startDate, Integer endDate) {

    Criteria criteria = Criteria.where("originId").is(Objects.toString(roomId))
        .and("refunded").is(Boolean.FALSE)
        .andOperator(
            Criteria.where("createDateRiyadhNum").gte(startDate),
            Criteria.where("createDateRiyadhNum").lte(endDate)
        );

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(criteria),
        Aggregation.group("sysOrigin")
            .sum("giftValue.actualAmount").as("quantity")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isEmpty(basicDBObjectList)) {
      return BigDecimal.ZERO;
    }

    return new BigDecimal(basicDBObjectList.get(0).getString("quantity"));

  }

  @Override
  public List<String> listByTrackId(List<String> trackIds) {
    return Optional.ofNullable(
            mongoTemplate.find(Query.query(Criteria.where("trackId").in(trackIds)),
                GiftGiveRunningWater.class))
        .map(list -> list.stream().map(GiftGiveRunningWater::getTrackId)
            .collect(Collectors.toList()))
        .orElseGet(Lists::newArrayList);
  }

  @Override
  public List<GiftGiveRunningWater> listLatest(LocalDateTime startTime) {
    return mongoTemplate.find(Query.query(Criteria.where("createTime").gte(startTime))
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(40000),
        GiftGiveRunningWater.class);
  }

  private Long getSumAnchorTargetLuckyGift(SumAnchorTargetQryCmd query) {

    Criteria acceptUserElemMatch = Criteria.where("acceptUserId").is(query.getAcceptUserId());

    if (Objects.equals(query.getCheckAnchor(), Boolean.TRUE)) {
      acceptUserElemMatch.and("anchor").is(Boolean.TRUE);
    }

    Criteria firstFilter = Criteria.where("acceptUsers")
        .elemMatch(acceptUserElemMatch)
        .and("refunded").is(Boolean.FALSE)
        .and("giftValue.giftType").is("LUCKY_GIFT")
        .andOperator(
            Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
            Criteria.where("createTime").lte(query.endTimeToLocalDateTime())
        );

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(firstFilter),
        Aggregation.unwind("acceptUsers"),
        Aggregation.match(Criteria.where("acceptUsers.acceptUserId").is(query.getAcceptUserId())),
        Aggregation.group("sysOrigin")
            .sum("acceptUsers.targetAmount").as("quantity")
    );

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        GiftGiveRunningWater.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjectList = aggregationResults.getMappedResults();

    if (CollectionUtils.isEmpty(basicDBObjectList)) {
      return null;
    }

    String quantity = basicDBObjectList.get(0).getString("quantity");
    if (StringUtils.isBlank(quantity)) {
      return null;
    }
    long quantityLong = Long.parseLong(quantity);
    if (quantityLong <= 0) {
      return null;
    }
    return quantityLong;
  }

  @Override
  public List<GiftStatisticResult> queryGiftStatisticGroupByGiftId(
      Long userId, Long acceptUserId, LocalDateTime startTime, LocalDateTime endTime) {

    List<AggregationOperation> operations = Lists.newArrayList();

    Criteria criteria = new Criteria();
    if (userId != null) {
      criteria.and("userId").is(userId);
    }
    if (startTime != null && endTime != null) {
      criteria.and("createTime").gte(startTime).lte(endTime);
    } else if (startTime != null) {
      criteria.and("createTime").gte(startTime);
    } else if (endTime != null) {
      criteria.and("createTime").lte(endTime);
    }

    if (!criteria.getCriteriaObject().isEmpty()) {
      operations.add(Aggregation.match(criteria));
    }

    operations.add(Aggregation.unwind("acceptUsers"));
    operations.add(Aggregation.match(Criteria.where("acceptUsers.acceptUserId").is(acceptUserId)));

    operations.add(Aggregation.group("giftId")
        .first("giftId").as("giftId")
        .first("giftType").as("giftType")
        .first("luckyGift").as("luckyGift")
        .count().as("totalCount")
        .sum("giftValue.quantity").as("totalQuantity")
        .sum("giftValue.giftValue").as("totalGiftValue")
        .sum("acceptUsers.targetAmount").as("totalTargetAmount")
    );

    operations.add(Aggregation.sort(Sort.Direction.DESC, "totalTargetAmount"));

    Aggregation aggregation = Aggregation.newAggregation(operations);

    AggregationResults<Document> results = mongoTemplate.aggregate(
        aggregation,
        "gift_give_running_water",
        Document.class
    );

    return results.getMappedResults().stream()
        .map(this::convertToGiftStatisticResult)
        .collect(Collectors.toList());
  }

  private GiftStatisticResult convertToGiftStatisticResult(Document doc) {
    GiftStatisticResult result = new GiftStatisticResult();
    result.setGiftId(doc.getLong("giftId"));
    result.setGiftType(doc.getString("giftType"));
    result.setLuckyGift(doc.getBoolean("luckyGift", false));
    result.setTotalCount(doc.getInteger("totalCount", 0));
    result.setTotalQuantity(doc.getInteger("totalQuantity", 0));
    result.setTotalGiftValue(convertToBigDecimal(doc.get("totalGiftValue")));
    result.setTotalTargetAmount(convertToBigDecimal(doc.get("totalTargetAmount")));
    return result;
  }

  private BigDecimal convertToBigDecimal(Object value) {
    if (value == null) {
      return BigDecimal.ZERO;
    }
    if (value instanceof Decimal128) {
      return ((Decimal128) value).bigDecimalValue();
    } else if (value instanceof BigDecimal) {
      return (BigDecimal) value;
    } else if (value instanceof Number) {
      return new BigDecimal(value.toString());
    }
    return BigDecimal.ZERO;
  }
}
