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

import com.red.circle.other.infra.utils.SeasonUtils;
import com.red.circle.tool.core.date.DateFormatConstant;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.date.ZonedDateTimeUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.infra.database.mongo.entity.user.count.WeekCpValueCount;
import com.red.circle.other.infra.database.mongo.service.user.count.WeekCpValueCountService;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.data.domain.Sort;
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.stereotype.Service;

/**
 * 每周cp值统计.
 *
 * @author pengliang on 2022/3/2
 */
@Service
@RequiredArgsConstructor
public class WeekCpValueCountServiceImpl implements WeekCpValueCountService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void incrThisWeekQuantity(String sysOrigin, Long userIdOne, Long userIdTwo,
      Long quantity) {
    if (existsThisWeek(userIdOne, userIdTwo)) {
      mongoTemplate.updateFirst(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)),
          new Update().inc("quantity", quantity),
          WeekCpValueCount.class);
      return;
    }
    addThisWeek(sysOrigin, userIdOne, userIdTwo, quantity);
  }

  @Override
  public void incrThisWeekQuantity(String sysOrigin, String userRegionCode, Long userIdOne,
      Long userIdTwo,
      Long quantity) {
    if (existsThisWeek(userIdOne, userIdTwo)) {
      mongoTemplate.updateFirst(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)),
          new Update().inc("quantity", quantity)
              .set("regionCode", userRegionCode)
          ,
          WeekCpValueCount.class);
      return;
    }
    addThisWeekV2(sysOrigin, userRegionCode, userIdOne, userIdTwo, quantity);
  }

  @Override
  public void incrThisSeasonQuantity(String sysOrigin, Long userIdOne, Long userIdTwo,
      Long quantity) {
    if (existsThisSeason(userIdOne, userIdTwo)) {
      mongoTemplate.updateFirst(Query.query(getSeasonUniquenessCriteria(userIdOne, userIdTwo)),
          new Update().inc("quantity", quantity),
          WeekCpValueCount.class);
      return;
    }
    addThisSeason(sysOrigin, userIdOne, userIdTwo, quantity);
  }

  @Override
  public void removeThisSeason(Long userIdOne, Long userIdTwo) {
    mongoTemplate.remove(Query.query(getSeasonUniquenessCriteria(userIdOne, userIdTwo)
        .and("group").is(getThisSeasonCycleKey())), WeekCpValueCount.class);
  }

  @Override
  public WeekCpValueCount getUserThisSeasonCount(String sysOrigin, Long userIdOne, Long userIdTwo) {
    return mongoTemplate
        .findOne(Query.query(getSeasonUniquenessCriteria(userIdOne, userIdTwo)
                .and("sysOrigin").is(sysOrigin))
            , WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listThisSeasonTop(String sysOrigin, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getThisSeasonCycleKey()))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listLastSeasonTop(String sysOrigin, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getLastSeasonCycleKey()))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listSeasonTopByCycleKey(String sysOrigin, String cycleKey, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(cycleKey))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  private void addThisWeek(String sysOrigin, Long userIdOne, Long userIdTwo, Long quantity) {
    mongoTemplate.save(new WeekCpValueCount()
        .setId(getWeekUniqueId(userIdOne, userIdTwo))
        .setSysOrigin(sysOrigin)
        .setQuantity(quantity)
        .setGroup(getThisWeekDate())
        .setCreateTime(TimestampUtils.now())
        .setExpiredTime(TimestampUtils.nowPlusDays(15))
    );
  }

  private void addThisWeekV2(String sysOrigin, String userRegionCode, Long userIdOne,
      Long userIdTwo, Long quantity) {
    mongoTemplate.save(new WeekCpValueCount()
        .setId(getWeekUniqueId(userIdOne, userIdTwo))
        .setSysOrigin(sysOrigin)
        .setRegionCode(userRegionCode)
        .setQuantity(quantity)
        .setGroup(getThisWeekDate())
        .setCreateTime(TimestampUtils.now())
        .setExpiredTime(TimestampUtils.nowPlusDays(15))
    );
  }

  private void addThisSeason(String sysOrigin, Long userIdOne, Long userIdTwo, Long quantity) {
    mongoTemplate.save(new WeekCpValueCount()
        .setId(getSeasonUniqueId(userIdOne, userIdTwo))
        .setSysOrigin(sysOrigin)
        .setQuantity(quantity)
        .setGroup(getThisSeasonCycleKey())
        .setCreateTime(TimestampUtils.now())
        .setExpiredTime(TimestampUtils.nowPlusDays(60))
    );
  }

  @Override
  public WeekCpValueCount getUserThisWeekCount(String sysOrigin, Long userIdOne, Long userIdTwo) {
    return mongoTemplate
        .findOne(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)
                .and("sysOrigin").is(sysOrigin))
            , WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listThisWeekTop(String sysOrigin, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getThisWeekDate()))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listThisWeekTop(String sysOrigin, String userRegionCode,
      Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getThisWeekDate())
                .and("regionCode").is(userRegionCode))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listLastWeekTop(String sysOrigin, Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getLastWeekDate()))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public List<WeekCpValueCount> listLastWeekTop(String sysOrigin, String userRegionCode,
      Integer size) {
    return mongoTemplate
        .find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)
                .and("group").is(getLastWeekDate())
                .and("regionCode").is(userRegionCode))
            .with(Sort.by(Sort.Order.desc("quantity")))
            .limit(size), WeekCpValueCount.class);
  }

  @Override
  public void removeThisWeek(Long userIdOne, Long userIdTwo) {
    mongoTemplate.remove(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)
        .and("group").is(getThisWeekDate())), WeekCpValueCount.class);
  }

  @Override
  public ImmutablePair<Long, Long> parseUserIdPair(String id) {
    if (StringUtils.isBlanks(id)) {
      return ImmutablePair.nullPair();
    }
    String[] ids = id.split("_");
    if (ids.length < 2) {
      return ImmutablePair.nullPair();
    }
    return ImmutablePair.of(DataTypeUtils.toLong(ids[0]), DataTypeUtils.toLong(ids[1]));
  }

  @Override
  public void correctionData(Long userIdOne, Long userIdTwo, Long quantity) {

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

    WeekCpValueCount count = mongoTemplate
        .findOne(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)), WeekCpValueCount.class);
    if (Objects.isNull(count)) {
      return;
    }
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(count.getId())),
        new Update()
            .set("quantity", count.getQuantity() > quantity ? count.getQuantity() - quantity : 0L),
        WeekCpValueCount.class);

  }

  private String getLastWeekDate() {
    return ZonedDateTimeAsiaRiyadhUtils.format(ZonedDateTimeAsiaRiyadhUtils.getLastWeekMonday(),
        DateFormatConstant.yyyyMMdd);
  }

  private String getWeekUniqueId(Long userOne, Long userIdTwo) {
    return userOne + "_" + userIdTwo + "_" + getThisWeekDate();
  }

  private String getSeasonUniqueId(Long userOne, Long userIdTwo) {
    return userOne + "_" + userIdTwo + "_" + getThisSeasonCycleKey();
  }

  private boolean existsThisWeek(Long userIdOne, Long userIdTwo) {
    return mongoTemplate
        .exists(Query.query(getUniquenessCriteria(userIdOne, userIdTwo)),
            WeekCpValueCount.class);
  }

  private boolean existsThisSeason(Long userIdOne, Long userIdTwo) {
    return mongoTemplate
        .exists(Query.query(getSeasonUniquenessCriteria(userIdOne, userIdTwo)),
            WeekCpValueCount.class);
  }

  private Criteria getUniquenessCriteria(Long userIdOne, Long userIdTwo) {
    return Criteria.where("id").is(getWeekUniqueId(userIdOne, userIdTwo));
  }

  private Criteria getSeasonUniquenessCriteria(Long userIdOne, Long userIdTwo) {
    return Criteria.where("id").is(getSeasonUniqueId(userIdOne, userIdTwo));
  }

  private String getThisWeekDate() {
    return ZonedDateTimeUtils.format(ZonedDateTimeAsiaRiyadhUtils.nowWeekMonday(),
        DateFormatConstant.yyyyMMdd);
  }

  private String getThisSeasonCycleKey() {
    return SeasonUtils.getCurrentSeasonCycleKey();
  }

  private String getLastSeasonCycleKey() {
    return SeasonUtils.getLastSeasonCycleKey();
  }

}
