package com.red.circle.order.infra.database.mongo.order.impl;

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService;
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
import com.red.circle.order.infra.database.mongo.order.entity.assist.CountUserInAppPurchase;
import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice;
import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseStatusStep;
import com.red.circle.order.infra.database.mongo.order.query.InAppPurchaseDetailsQuery;
import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus;
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
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/10/24
 */
@Service
@RequiredArgsConstructor
public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void create(InAppPurchaseDetails inAppPurchaseDetails) {
    mongoTemplate.insert(inAppPurchaseDetails);
  }

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

  @Override
  public InAppPurchaseDetails getByOrderId(String orderId) {
    return mongoTemplate.findOne(Query.query(Criteria.where("orderId").is(orderId))
            .with(Sort.by(Order.desc("createTime"))),
        InAppPurchaseDetails.class);
  }

  @Override
  public boolean existsPayNotice(String id, String eventId, String noticeType) {
    return mongoTemplate.exists(Query.query(
        Criteria.where("id").is(id)
            .elemMatch(Criteria.where("eventId").is(eventId).is("noticeType").is(noticeType))
    ), InAppPurchaseDetails.class);
  }

  @Override
  public boolean existsPayNoticeByEventId(String id, String eventId) {
    return mongoTemplate.exists(Query.query(
        Criteria.where("id").is(id).elemMatch(Criteria.where("eventId").is(eventId))
    ), InAppPurchaseDetails.class);
  }

  @Override
  public boolean existsPayNoticeByNoticeType(String id, String noticeType) {
    return mongoTemplate.exists(Query.query(
        Criteria.where("id").is(id).elemMatch(Criteria.where("noticeType").is(noticeType))
    ), InAppPurchaseDetails.class);
  }

  @Override
  public void addPayNotices(String id, InAppPurchaseEventNotice payNotice) {
    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(id)),
        new Update()
            .push("payNotices", payNotice)
            .set("updateTime", TimestampUtils.now()),
        InAppPurchaseDetails.class
    );
  }

  @Override
  public void addPayNoticesByOrderId(String orderId, InAppPurchaseEventNotice payNotice) {
    mongoTemplate.updateFirst(
        Query.query(Criteria.where("orderId").is(orderId)),
        new Update()
            .push("payNotices", payNotice)
            .set("updateTime", TimestampUtils.now()),
        InAppPurchaseDetails.class
    );
  }

  @Override
  public void updateStatus(String id, InAppPurchaseStatus status) {
    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("status", status)
            .push("statusSteps", new InAppPurchaseStatusStep()
                .setStatus(status)
                .setCreateTime(TimestampUtils.now())
            )
            .set("updateTime", TimestampUtils.now()),
        InAppPurchaseDetails.class
    );
  }

  @Override
  public void updateStatusReason(String id, InAppPurchaseStatus status, String reason) {
    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("status", status)
            .set("reason", reason)
            .push("statusSteps", new InAppPurchaseStatusStep()
                .setStatus(status)
                .setCreateTime(TimestampUtils.now())
            )
            .set("updateTime", TimestampUtils.now()),
        InAppPurchaseDetails.class
    );
  }

  @Override
  public boolean addMetadata(String id, String key, String val) {
    InAppPurchaseDetails details = getById(id);
    if (Objects.isNull(details)) {
      return false;
    }

    // 各大云厂商mongo版本差异和自定义特性在属性字段上可能存在支持上会出现偏差，不允许字段出现. or $特殊属性
    String newKey = key.replaceAll("\\.", "_").replaceAll("\\$", "_");
    details.getMetadata().put(newKey, val);
    return mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(details.getId())
            .and("version").is(details.getVersion())),
        new Update()
            .set("metadata", details.getMetadata())
            .set("version", details.getVersion() + 1)
            .set("updateTime", TimestampUtils.now()),
        InAppPurchaseDetails.class
    ).getModifiedCount() > 0;
  }

  @Override
  public List<InAppPurchaseDetails> list(InAppPurchaseDetailsQuery query) {

    Criteria criteria = new Criteria();

    if (StringUtils.isNotBlank(query.getId())) {
      query.setLastId(null);
      criteria.and("id").is(query.getId());
    }
    if (Objects.nonNull(query.getReceiptType())) {
      criteria.and("receiptType").is(query.getReceiptType());
    }
    if (StringUtils.isNotBlank(query.getTrackId())) {
      criteria.and("trackId").is(query.getTrackId());
    }
    if (StringUtils.isNotBlank(query.getSysOrigins())) {
      List<String> sysOrigins = Arrays.stream(query.getSysOrigins().split(","))
          .map(String::trim)
          .filter(StringUtils::isNotBlank).collect(Collectors.toList());
      if (CollectionUtils.isNotEmpty(sysOrigins)) {
        criteria.and("sysOrigin").in(sysOrigins);
      }
    }
    if (Objects.nonNull(query.getAcceptUserId())) {
      criteria.and("acceptUserId").is(query.getAcceptUserId());
    }
    if (StringUtils.isNotEmpty(query.getSubscribeId())) {
      criteria.and("subscribeId").is(query.getSubscribeId());
    }
    if (StringUtils.isNotEmpty(query.getOrderId())) {
      criteria.and("orderId").is(query.getOrderId());
    }
    if (StringUtils.isNotEmpty(query.getEnv())) {
      criteria.and("env").is(query.getEnv());
    }
    if (StringUtils.isNotBlank(query.getFactoryPlatform())) {
      criteria.and("factory.platform").is(query.getFactoryPlatform());
    }
    if (Objects.nonNull(query.getStatus())) {
      criteria.and("status").is(query.getStatus());
    }
    if (StringUtils.isNotEmpty(query.getCountryCode())) {
      criteria.and("countryCode").is(query.getCountryCode());
    }

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

    if (StringUtils.isNotBlank(query.getLastId())) {
      criteria.and("id").lt(query.getLastId());
    }

    if (Objects.isNull(query.getLimit()) || query.getLimit() <= 0) {
      query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE);
    }

    return mongoTemplate
        .find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(query.getLimit()), InAppPurchaseDetails.class);
  }

  @Override
  public Map<String, Object> statistics(InAppPurchaseDetailsQuery query) {
    Criteria criteria = new Criteria();

    if (StringUtils.isNotBlank(query.getId())) {
      criteria.and("id").is(query.getId());
    }
    if (Objects.nonNull(query.getReceiptType())) {
      criteria.and("receiptType").is(query.getReceiptType());
    }
    if (StringUtils.isNotBlank(query.getTrackId())) {
      criteria.and("trackId").is(query.getTrackId());
    }
    if (StringUtils.isNotBlank(query.getSysOrigins())) {
      List<String> sysOrigins = Arrays.stream(query.getSysOrigins().split(","))
          .map(String::trim)
          .filter(StringUtils::isNotBlank).collect(Collectors.toList());
      if (CollectionUtils.isNotEmpty(sysOrigins)) {
        criteria.and("sysOrigin").in(sysOrigins);
      }
    }
    if (Objects.nonNull(query.getAcceptUserId())) {
      criteria.and("acceptUserId").is(query.getAcceptUserId());
    }
    if (StringUtils.isNotEmpty(query.getSubscribeId())) {
      criteria.and("subscribeId").is(query.getSubscribeId());
    }
    if (StringUtils.isNotEmpty(query.getOrderId())) {
      criteria.and("orderId").is(query.getOrderId());
    }
    if (StringUtils.isNotEmpty(query.getEnv())) {
      criteria.and("env").is(query.getEnv());
    }
    if (StringUtils.isNotBlank(query.getFactoryPlatform())) {
      criteria.and("factory.platform").is(query.getFactoryPlatform());
    }
    if (Objects.nonNull(query.getStatus())) {
      criteria.and("status").is(query.getStatus());
    }
    if (StringUtils.isNotEmpty(query.getCountryCode())) {
      criteria.and("countryCode").is(query.getCountryCode());
    }
    if (Objects.nonNull(query.getStartTime()) && Objects.nonNull(query.getEndTime())) {
      criteria.andOperator(
          Criteria.where("createTime").gte(query.getStartTime()),
          Criteria.where("createTime").lte(query.getEndTime())
      );
    }

    // 使用聚合管道计算统计
    AggregationResults<BasicDBObject> results = mongoTemplate.aggregate(
        Aggregation.newAggregation(
            Aggregation.match(criteria),
            Aggregation.group()
                .count().as("totalOrderCount")
                .sum("amount").as("totalAmount")
                .sum("amountUsd").as("totalAmountUsd")
                .avg("amount").as("averageOrderAmount")
        ),
        InAppPurchaseDetails.class,
        BasicDBObject.class);

    Map<String, Object> resultMap = new HashMap<>();
    if (CollectionUtils.isNotEmpty(results.getMappedResults())) {
      BasicDBObject obj = results.getMappedResults().get(0);
      resultMap.put("totalOrderCount", obj.getLong("totalOrderCount"));
      resultMap.put("totalAmount", obj.get("totalAmount") != null ? new BigDecimal(obj.get("totalAmount").toString()) : BigDecimal.ZERO);
      resultMap.put("totalAmountUsd", obj.get("totalAmountUsd") != null ? new BigDecimal(obj.get("totalAmountUsd").toString()) : BigDecimal.ZERO);
      resultMap.put("averageOrderAmount", obj.get("averageOrderAmount") != null ? new BigDecimal(obj.get("averageOrderAmount").toString()) : BigDecimal.ZERO);
    } else {
      resultMap.put("totalOrderCount", 0L);
      resultMap.put("totalAmount", BigDecimal.ZERO);
      resultMap.put("totalAmountUsd", BigDecimal.ZERO);
      resultMap.put("averageOrderAmount", BigDecimal.ZERO);
    }
    return resultMap;
  }

  @Override
  public List<CountUserInAppPurchase> countUserInAppPurchase(String sysOrigin,
      LocalDateTime startTime,
      LocalDateTime endTime,
      Integer limit) {

    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(
        Aggregation.newAggregation(
            Aggregation.match(Criteria
                .where("sysOrigin").is(sysOrigin)
                .and("status").in(InAppPurchaseStatus.SUCCESS, InAppPurchaseStatus.REFUND)
                .andOperator(
                    Criteria.where("createTime").gte(startTime),
                    Criteria.where("createTime").lte(endTime)
                )),
            Aggregation.group("acceptUserId").sum("amountUsd").as("amount"),
            Aggregation.sort(Sort.by(Sort.Order.desc("amount"))),
            Aggregation.limit(limit)
        ),
        InAppPurchaseDetails.class,
        BasicDBObject.class);

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

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

    return basicDBObjectList.stream().map(obj -> new CountUserInAppPurchase()
            .setUserId(obj.getLong("_id"))
            .setAmount(new BigDecimal(obj.getString("amount")))
        )
        .collect(Collectors.toList());
  }

  @Override
  public List<InAppPurchaseDetails> listAll() {
    return mongoTemplate.find(Query.query(new Criteria()), InAppPurchaseDetails.class);
  }

  @Override
  public void updateMetadata(String id, Map<String, String> metadata) {
    mongoTemplate.updateFirst(
        Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("metadata", metadata),
        InAppPurchaseDetails.class
    );
  }

  @Override
  public List<CountUserInAppPurchase> batchUserRechargeStatistics(
      List<Long> userIds,
      String sysOrigin,
      LocalDateTime startTime,
      LocalDateTime endTime) {

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

    // 构建查询条件
    Criteria criteria = Criteria.where("sysOrigin").is(sysOrigin)
        .and("acceptUserId").in(userIds)
        .and("status").is(InAppPurchaseStatus.SUCCESS);  // 只统计支付成功的订单

    // 如果指定了时间范围，添加时间条件
    if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
      criteria.andOperator(
          Criteria.where("createTime").gte(startTime),
          Criteria.where("createTime").lte(endTime)
      );
    }

    // 使用 MongoDB 聚合管道按用户ID分组统计
    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(
        Aggregation.newAggregation(
            Aggregation.match(criteria),
            Aggregation.group("acceptUserId")
                .sum("amountUsd").as("totalAmount")
                .count().as("orderCount"),
            Aggregation.sort(Sort.by(Sort.Order.asc("_id")))  // 按用户ID排序
        ),
        InAppPurchaseDetails.class,
        BasicDBObject.class);

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

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

    // 转换为 CountUserInAppPurchase 对象
    return basicDBObjectList.stream().map(obj -> new CountUserInAppPurchase()
            .setUserId(obj.getLong("_id"))  // MongoDB group 的 _id 就是 acceptUserId
            .setAmount(obj.get("totalAmount") != null
                ? new BigDecimal(obj.get("totalAmount").toString())
                : BigDecimal.ZERO)
            .setOrderCount(obj.getInt("orderCount", 0))
        )
        .collect(Collectors.toList());
  }
}
