
package com.tmerclub.cloud.user.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.tmerclub.cloud.api.order.feign.OrderRefundFeignClient;
import com.tmerclub.cloud.api.order.feign.OrderSettlementFeignClient;
import com.tmerclub.cloud.api.order.vo.OrderRefundSimpleVO;
import com.tmerclub.cloud.api.order.vo.OrderSettlementSimpleVO;
import com.tmerclub.cloud.api.payment.constant.PaySysType;
import com.tmerclub.cloud.api.payment.vo.PaySettlementConfigVO;
import com.tmerclub.cloud.api.payment.vo.ShopAccountDetailVO;
import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.api.user.bo.BalanceRefundBO;
import com.tmerclub.cloud.api.user.dto.FinanceDetailDTO;
import com.tmerclub.cloud.api.user.vo.FinanceDetailExcelVO;
import com.tmerclub.cloud.api.user.vo.FinanceDetailVO;
import com.tmerclub.cloud.api.user.vo.UserManagerVO;
import com.tmerclub.cloud.api.user.vo.UserPayInfoVO;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.PayType;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.util.PageUtil;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.leaf.manager.SegmentManager;
import com.tmerclub.cloud.common.mongodb.util.MongoPageUtil;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.common.util.DateUtils;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.common.util.PriceUtil;
import com.tmerclub.cloud.user.bo.mongo.MongoUserBalanceLogBO;
import com.tmerclub.cloud.user.constant.DistributedIdKey;
import com.tmerclub.cloud.user.constant.RechargeIoTypeEnum;
import com.tmerclub.cloud.user.constant.RechargeTypeEnum;
import com.tmerclub.cloud.user.mapper.UserBalanceLogMapper;
import com.tmerclub.cloud.user.mapper.UserExtensionMapper;
import com.tmerclub.cloud.user.mapper.UserLevelLogMapper;
import com.tmerclub.cloud.user.mapper.UserRechargeMapper;
import com.tmerclub.cloud.user.model.UserBalanceLog;
import com.tmerclub.cloud.user.model.UserExtension;
import com.tmerclub.cloud.user.model.UserRecharge;
import com.tmerclub.cloud.user.service.UserBalanceLogService;
import com.tmerclub.cloud.user.vo.UserBalanceLogVO;
import com.tmerclub.cloud.user.vo.mongo.MongoBalanceFinanceDetailVO;
import com.tmerclub.cloud.user.vo.mongo.MongoBalanceShopAccountDetailVO;
import com.tmerclub.cloud.user.vo.mongo.MongoBalanceUserManagerVO;
import com.tmerclub.cloud.user.vo.mongo.MongoBalanceUserPayInfoVO;
import com.mongodb.client.result.UpdateResult;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
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;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 余额记录
 *
 * @author tmerclub
 * @date 2021-04-27 15:51:36
 */
@Service
public class UserBalanceLogServiceImpl implements UserBalanceLogService {

    private static final Logger logger = LoggerFactory.getLogger(UserBalanceLogServiceImpl.class);

    @Autowired
    private UserBalanceLogMapper userBalanceLogMapper;
    @Autowired
    private UserExtensionMapper userExtensionMapper;
    @DubboReference
    private OrderSettlementFeignClient orderSettlementFeignClient;
    @DubboReference
    private OrderRefundFeignClient orderRefundFeignClient;
    @Autowired
    private UserLevelLogMapper userLevelLogMapper;
    @DubboReference
    private ConfigFeignClient configFeignClient;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private SegmentManager segmentManager;
    @Autowired
    private MongoPageUtil mongoPageUtil;
    @Autowired
    private UserRechargeMapper userRechargeMapper;

    @Override
    public void saveToMongdb(MongoUserBalanceLogBO mongoUserBalanceLogBO) {
        if (Objects.isNull(mongoUserBalanceLogBO) || Objects.isNull(mongoUserBalanceLogBO.getBalanceLogId())) {
            throw new LuckException("余额日志信息不全");
        }
        mongoUserBalanceLogBO.setCreateTime(new Date());
        mongoUserBalanceLogBO.setUpdateTime(new Date());
        logger.info("保存余额记录到mongodb{}", mongoUserBalanceLogBO);
        mongoTemplate.save(mongoUserBalanceLogBO);
    }

    @Override
    public void saveBatchToMongodb(List<MongoUserBalanceLogBO> list) {
        logger.info("批量保存余额记录到mongodb{}", list);
        for (MongoUserBalanceLogBO mongoUserBalanceLogBO : list) {
            if (Objects.isNull(mongoUserBalanceLogBO) || Objects.isNull(mongoUserBalanceLogBO.getBalanceLogId())) {
                throw new LuckException("余额日志信息不全");
            }
            mongoUserBalanceLogBO.setCreateTime(new Date());
            mongoUserBalanceLogBO.setUpdateTime(new Date());
        }
        mongoTemplate.insertAll(list);
    }

    @Override
    public MongoUserBalanceLogBO getByBalanceLogId(Long balanceLogId) {
        MongoUserBalanceLogBO userBalanceLogBO = mongoTemplate.findById(balanceLogId, MongoUserBalanceLogBO.class);
        if (Objects.isNull(userBalanceLogBO)) {
            throw new LuckException("余额记录日志不存在");
        }
        return userBalanceLogBO;
    }

    @Override
    public PageVO<UserBalanceLogVO> getPageByUserId(PageDTO pageDTO, Long userId) {
        PageVO<UserBalanceLogVO> pageVO = new PageVO<>();
        // 查询支付系统类型(默认 or 通联)
        Integer paySysType = this.getPaySysType();
        Query query = new Query();
        query.addCriteria(Criteria.where("paySysType").is(paySysType).and("userId").is(userId)
                .orOperator(Criteria.where("type").nin(1, 3, 6),
                        Criteria.where("type").in(1, 3, 6).and("isPayed").ne(0)));
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        PageVO<MongoUserBalanceLogBO> mongoUserBalanceLogPage = mongoPageUtil.doPage(MongoUserBalanceLogBO.class, pageDTO, query);
        List<UserBalanceLogVO> userBalanceLogs = BeanUtil.mapAsList(mongoUserBalanceLogPage.getList(), UserBalanceLogVO.class);
        // 通过支付单号或者退款单号查询订单号
        List<Long> refundIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<Long> payIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        userBalanceLogs.forEach(item -> {
            if (Objects.equals(item.getType(), RechargeTypeEnum.PAY.value()) && Objects.nonNull(item.getPayId())) {
                payIds.add(item.getPayId());
            } else if (Objects.equals(item.getType(), RechargeTypeEnum.REFUND.value()) && Objects.nonNull(item.getRefundId())) {
                refundIds.add(item.getRefundId());
            }
        });
        ServerResponseEntity<List<OrderSettlementSimpleVO>> orderSettlementRes = orderSettlementFeignClient.listOrderIdsByPayIds(userId, payIds);
        ServerResponseEntity<List<OrderRefundSimpleVO>> orderRefundRes = orderRefundFeignClient.listOrderIdsByRefundIds(refundIds);
        if (orderRefundRes.isSuccess() && orderRefundRes.isSuccess()) {
            Map<Long, OrderSettlementSimpleVO> orderSettlementSimpleMap = orderSettlementRes.getData().stream().collect(Collectors.toMap(OrderSettlementSimpleVO::getPayId, orderSettlementSimpleVO -> orderSettlementSimpleVO));
            Map<Long, OrderRefundSimpleVO> orderRefundSimpleMap = orderRefundRes.getData().stream().collect(Collectors.toMap(OrderRefundSimpleVO::getRefundId, orderRefundSimpleVO -> orderRefundSimpleVO));
            Map<Long, List<UserBalanceLogVO>> userBalanceLogMap = userBalanceLogs.stream().filter(o -> Objects.nonNull(o.getPayId())).collect(Collectors.groupingBy(UserBalanceLogVO::getPayId));
            userBalanceLogs.forEach(item -> {
                if (Objects.equals(item.getType(), RechargeTypeEnum.PAY.value()) && Objects.nonNull(orderSettlementSimpleMap.get(item.getPayId()))) {
                    OrderSettlementSimpleVO orderSettlementSimpleVO = orderSettlementSimpleMap.get(item.getPayId());
                    item.setOrderIds(orderSettlementSimpleVO.getOrderIds());
                    if (Objects.nonNull(orderSettlementSimpleVO.getPayIds())) {
                        // 预售订单要把定金的订单加上
                        String[] orderPayIds = orderSettlementSimpleVO.getPayIds().split(Constant.COMMA);
                        for (String orderPayId : orderPayIds) {
                            if (Objects.isNull(orderPayId) || StrUtil.equals(orderPayId, "null")) {
                                continue;
                            }
                            if (Objects.equals(orderPayId, item.getPayId().toString())) {
                                continue;
                            }
                            if (CollUtil.isNotEmpty(userBalanceLogMap.get(Long.valueOf(orderPayId)))) {
                                for (UserBalanceLogVO userBalanceLogVO : userBalanceLogMap.get(Long.valueOf(orderPayId))) {
                                    userBalanceLogVO.setOrderIds(orderSettlementSimpleVO.getOrderIds());
                                }
                            }
                        }
                    }
                } else if (Objects.equals(item.getType(), RechargeTypeEnum.REFUND.value()) && Objects.nonNull(orderRefundSimpleMap.get(item.getRefundId()))) {
                    item.setOrderIds(new ArrayList<Long>() {{
                        this.add(orderRefundSimpleMap.get(item.getRefundId()).getOrderId());
                    }});
                }
            });
        }
        pageVO.setList(userBalanceLogs);
        pageVO.setTotal(mongoUserBalanceLogPage.getTotal());
        pageVO.setPages(mongoUserBalanceLogPage.getPages());
        return pageVO;
    }

    @Override
    public MongoUserBalanceLogBO getByPayId(Long payId) {
        Query query = new Query(Criteria.where("payId").is(payId));
        List<MongoUserBalanceLogBO> list = mongoTemplate.find(query, MongoUserBalanceLogBO.class);
        if (CollUtil.isEmpty(list)) {
            throw new LuckException("余额记录日志不存在");
        }
        MongoUserBalanceLogBO userBalanceLogBO = list.get(0);
        return userBalanceLogBO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateToOrderPaySuccess(Long payId, MongoUserBalanceLogBO userBalanceLog) {
        // 扣减余额
        if (userBalanceLog.getPaySysType().equals(PaySysType.DEFAULT.value())) {
            logger.info("扣减默认余额");
            Long userId = userBalanceLog.getUserId();
            boolean updateBalanceStatus = userExtensionMapper.deductionUserBalance(userBalanceLog.getChangeBalance(), userId);
            if (!updateBalanceStatus) {
                throw new LuckException("余额不足");
            }
        } else if (Objects.equals(userBalanceLog.getPaySysType(), PaySysType.ALLINPAY.value())) {
            logger.info("扣减通联余额");
            UserExtension userExtension = userExtensionMapper.getByUserId(userBalanceLog.getUserId());
            if (userExtension.getAllinpayActualBalance() < userBalanceLog.getChangeBalance()) {
                throw new LuckException("通联余额不足");
            }
            userExtension.setAllinpayActualBalance(userExtension.getAllinpayActualBalance() - userBalanceLog.getChangeBalance());
            userExtensionMapper.update(userExtension);
        } else {
            throw new LuckException("余额记录支付系统类型错误");
        }
        // 将余额记录的支付状态变为已支付
        updateToSuccess(userBalanceLog.getBalanceLogId(), userBalanceLog.getPayId(), PayType.BALANCE.value());
    }

    @Override
    public void updateToSuccess(Long balanceLogId, Long payId, Integer payType) {
        Update update = new Update();
        update.set("isPayed", 1);
        update.set("payType", payType);
        if (Objects.nonNull(payId)) {
            update.set("payId", payId);
        }
        Query query = new Query(Criteria.where("balanceLogId").is(balanceLogId).and("isPayed").is(0));
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, MongoUserBalanceLogBO.class);
        if (updateResult.getModifiedCount() < 1) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doRefund(BalanceRefundBO balanceRefundBO) {
        Query query = new Query(Criteria.where("refundId").is(balanceRefundBO.getRefundId()).and("refundAmount").is(balanceRefundBO.getRefundNumber()));
        long refundCount = mongoTemplate.count(query, MongoUserBalanceLogBO.class);
        if (refundCount > 0L) {
            return;
        }
        // 退款记录
        MongoUserBalanceLogBO userBalanceLog = new MongoUserBalanceLogBO();
        userBalanceLog.setBalanceLogId(segmentManager.getSegmentId(DistributedIdKey.tmerclub_USER_BALANCE_LOG));
        userBalanceLog.setUserId(balanceRefundBO.getUserId());
        userBalanceLog.setChangeBalance(balanceRefundBO.getChangeBalance());
        userBalanceLog.setIoType(RechargeIoTypeEnum.INCOME.value());
        userBalanceLog.setIsPayed(0);
        userBalanceLog.setPayId(balanceRefundBO.getPayId());
        userBalanceLog.setRefundId(balanceRefundBO.getRefundId());
        userBalanceLog.setRefundNumber(balanceRefundBO.getRefundNumber());
        userBalanceLog.setType(RechargeTypeEnum.REFUND.value());
        userBalanceLog.setPaySysType(balanceRefundBO.getPaySysType());
        // 余额加钱
        // 扣减余额
        if (userBalanceLog.getPaySysType().equals(PaySysType.DEFAULT.value())) {
            logger.info("退款增加默认余额");
            userExtensionMapper.addByUserBalanceLog(userBalanceLog);
        } else if (Objects.equals(userBalanceLog.getPaySysType(), PaySysType.ALLINPAY.value())) {
            logger.info("退款增加通联余额");
            UserExtension userExtension = userExtensionMapper.getByUserId(userBalanceLog.getUserId());
            userExtension.setAllinpayActualBalance(userExtension.getAllinpayActualBalance() + userBalanceLog.getChangeBalance());
            userExtensionMapper.update(userExtension);
        }
        saveToMongdb(userBalanceLog);
    }

    @Override
    public PageVO<FinanceDetailVO> pageFinanceDetail(PageDTO pageDTO, FinanceDetailDTO financeDetailDTO) {
        Criteria criteria = getCriteria(financeDetailDTO);
        ProjectionOperation projectionOperation = Aggregation.project("payType", "payId", "userId", "rechargeId", "createTime")
                .and("createTime").as("transDate")
                .and("createTime").as("placeTime")
                .and("_id").as("orderId")
                .and("changeBalance").as("transAmount")
                .and("payId").as("payIds");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime");
        // 分页数据
        SkipOperation skipOperation = Aggregation.skip((long) pageDTO.getPageSize() * (pageDTO.getPageNum() - 1));
        LimitOperation limitOperation = Aggregation.limit(pageDTO.getPageSize());
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoBalanceFinanceDetailVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoBalanceFinanceDetailVO.class,
                        Aggregation.match(criteria),
                        projectionOperation,
                        sortOperation,
                        skipOperation,
                        limitOperation
                );
        logger.info("pageFinanceDetail()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoBalanceFinanceDetailVO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoBalanceFinanceDetailVO.class);
        // 取出最终结果
        List<MongoBalanceFinanceDetailVO> list = aggregationResults.getMappedResults();

        // 总条数
        TypedAggregation<MongoBalanceFinanceDetailVO> aggregation = Aggregation.newAggregation(
                MongoBalanceFinanceDetailVO.class,
                Aggregation.match(criteria),
                projectionOperation
        );
        AggregationResults<MongoBalanceFinanceDetailVO> aggregate = mongoTemplate.aggregate(aggregation, MongoBalanceFinanceDetailVO.class);
        List<MongoBalanceFinanceDetailVO> mappedResults = aggregate.getMappedResults();
        PageVO<FinanceDetailVO> pageVO = new PageVO<>();
        pageVO.setTotal((long) mappedResults.size());
        pageVO.setList(new ArrayList<>());
        pageVO.setPages(PageUtil.getPages(mappedResults.size(), pageDTO.getPageSize()));
        if (CollUtil.isNotEmpty(list)) {
            // 补充数据
            setInfo(financeDetailDTO, list);
            List<FinanceDetailVO> financeList = BeanUtil.mapAsList(list, FinanceDetailVO.class);
            pageVO.setList(financeList);
        }
        return pageVO;
    }

    private void setInfo(FinanceDetailDTO financeDetailDTO, List<MongoBalanceFinanceDetailVO> list) {
        Set<Long> rechargeIds = list.stream().map(MongoBalanceFinanceDetailVO::getRechargeId).collect(Collectors.toSet());
        List<UserRecharge> userRecharges = userRechargeMapper.listByRechargeIds(rechargeIds);
        Map<Long, String> rechargeMap = userRecharges.stream().collect(Collectors.toMap(UserRecharge::getRechargeId, UserRecharge::getRechargeTitle));
        for (MongoBalanceFinanceDetailVO financeDetailVO : list) {
            if (Objects.equals(financeDetailDTO.getPaySysType(), PaySysType.ALLINPAY.value())) {
                financeDetailVO.setProdName("用户充值余额");
            } else {
                String title = rechargeMap.get(financeDetailVO.getRechargeId());
                financeDetailVO.setProdName(Objects.nonNull(title) ? title : "用户充值余额");
            }
            financeDetailVO.setShopId(Constant.PLATFORM_SHOP_ID);
            financeDetailVO.setBizType(3);
            financeDetailVO.setShopName(Constant.PLATFORM_SHOP_NAME);
        }
    }

    private Criteria getCriteria(FinanceDetailDTO financeDetailDTO) {
        Criteria criteria = new Criteria();
        Integer paySysType = getPaySysType();
        criteria.and("type").is(1).and("isPayed").is(1).and("paySysType").is(paySysType);
        if (Objects.nonNull(financeDetailDTO.getStartTime()) && Objects.nonNull(financeDetailDTO.getEndTime())) {
            criteria.and("createTime").gte(Objects.requireNonNull(DateUtils.dateToIsoDate(financeDetailDTO.getStartTime())))
                    .lte(Objects.requireNonNull(DateUtils.dateToIsoDate(financeDetailDTO.getEndTime())));
        }
        if (Objects.nonNull(financeDetailDTO.getPayType()) && financeDetailDTO.getPayType() >= 0) {
            criteria.and("payType").is(financeDetailDTO.getPayType());
        }
        if (Objects.nonNull(financeDetailDTO.getOrderId())) {
            // 不支持模糊查询
            criteria.and("_id").is(financeDetailDTO.getOrderId());
        }
        return criteria;
    }

    @Override
    public List<FinanceDetailExcelVO> listFinanceDetail(FinanceDetailDTO financeDetailDTO) {
        Criteria criteria = getCriteria(financeDetailDTO);
        ProjectionOperation projectionOperation = Aggregation.project("payType", "payId", "userId", "rechargeId")
                .and("createTime").as("transDate")
                .and("createTime").as("placeTime")
                .and("_id").as("orderId")
                .and("changeBalance").as("transAmount")
                .and("payId").as("payIds");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoBalanceFinanceDetailVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoBalanceFinanceDetailVO.class,
                        Aggregation.match(criteria),
                        projectionOperation,
                        sortOperation
                );
        logger.info("listFinanceDetail()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoBalanceFinanceDetailVO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoBalanceFinanceDetailVO.class);
        // 取出最终结果
        List<MongoBalanceFinanceDetailVO> list = aggregationResults.getMappedResults();
        if (CollUtil.isNotEmpty(list)) {
            // 补充数据
            setInfo(financeDetailDTO, list);
        }
        return BeanUtil.mapAsList(list, FinanceDetailExcelVO.class);
    }

    @Override
    public ShopAccountDetailVO getPlatformAccountDetail(Date startTime, Date endTime) {
        Integer paySysType = getPaySysType();
        ShopAccountDetailVO levelInfo = userLevelLogMapper.getPlatformAccountDetail(startTime, endTime, paySysType);
        if (Objects.isNull(levelInfo)) {
            levelInfo = new ShopAccountDetailVO();
        }
        // 开启通联支付后，余额充值不归为平台收入
        ShopAccountDetailVO balanceInfo;
        if (Objects.equals(paySysType, PaySysType.ALLINPAY.value())) {
            balanceInfo = new ShopAccountDetailVO();
        } else {
            balanceInfo = getPlatformAccountDetail(startTime, endTime, paySysType);
        }
        if (Objects.isNull(balanceInfo)) {
            balanceInfo = new ShopAccountDetailVO();
        }
        ShopAccountDetailVO platformInfo = new ShopAccountDetailVO();
        platformInfo.setAlipayAmount(getDefaultValue(levelInfo.getAlipayAmount()) + getDefaultValue(balanceInfo.getAlipayAmount()));
        platformInfo.setWechatAmount(getDefaultValue(levelInfo.getWechatAmount()) + getDefaultValue(balanceInfo.getWechatAmount()));
        platformInfo.setBalanceAmount(getDefaultValue(levelInfo.getBalanceAmount()) + getDefaultValue(balanceInfo.getBalanceAmount()));
        platformInfo.setScoreCount(0L);
        platformInfo.setTotal(platformInfo.getAlipayAmount() + platformInfo.getWechatAmount() + platformInfo.getBalanceAmount());
        platformInfo.setShopId(Constant.PLATFORM_SHOP_ID);
        platformInfo.setShopName(Constant.PLATFORM_SHOP_NAME);
        return platformInfo;
    }

    private ShopAccountDetailVO getPlatformAccountDetail(Date startTime, Date endTime, Integer paySysType) {
        Criteria criteria = new Criteria();
        criteria.and("type").is(1).and("isPayed").is(1).and("paySysType").is(paySysType);
        filterTime(startTime, endTime, criteria);
        ProjectionOperation projectionOperation = Aggregation.project()
                .and(ConditionalOperators.when(Criteria.where("payType").in(1, 3, 4, 5, 8)).then("$changeBalance").otherwise(0)).as("wechatAmount")
                .and(ConditionalOperators.when(Criteria.where("payType").in(2, 6, 7)).then("$changeBalance").otherwise(0)).as("alipayAmount");
        GroupOperation groupOperation = Aggregation.group()
                .sum("wechatAmount").as("wechatAmount")
                .sum("alipayAmount").as("alipayAmount");
        ProjectionOperation project = Aggregation.project("wechatAmount", "alipayAmount");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoBalanceShopAccountDetailVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoBalanceShopAccountDetailVO.class,
                        projectionOperation,
                        groupOperation,
                        project
                );
        logger.info("getPlatformAccountDetail()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoBalanceShopAccountDetailVO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoBalanceShopAccountDetailVO.class);
        // 取出最终结果
        List<MongoBalanceShopAccountDetailVO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new ShopAccountDetailVO();
        }
        ShopAccountDetailVO shopAccountDetailVO = BeanUtil.map(list.get(0), ShopAccountDetailVO.class);
        shopAccountDetailVO.setBalanceAmount(0L);
        return shopAccountDetailVO;
    }

    private static void filterTime(Date startTime, Date endTime, Criteria criteria) {
        if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
            criteria.and("createTime").gte(Objects.requireNonNull(DateUtils.dateToIsoDate(startTime)))
                    .lte(Objects.requireNonNull(DateUtils.dateToIsoDate(endTime)));
        } else if (Objects.nonNull(startTime)) {
            criteria.and("createTime").gte(Objects.requireNonNull(DateUtils.dateToIsoDate(startTime)));
        } else if (Objects.nonNull(endTime)){
            criteria.and("createTime").lte(Objects.requireNonNull(DateUtils.dateToIsoDate(endTime)));
        }
    }

    @Override
    public List<UserPayInfoVO> listPayInfoVO(Date startTime, Date endTime) {
        Integer paySysType = getPaySysType();
        Criteria criteria = new Criteria();
        criteria.and("type").is(1).and("isPayed").is(1).and("paySysType").is(paySysType);
        filterTime(startTime, endTime, criteria);
        ProjectionOperation projectionOperation = Aggregation.project("payId", "payType", "createTime")
                .and("_id").as("orderIds")
                .and("changeBalance").as("payAmount")
                .and("createTime").as("payTime")
                .and(ConditionalOperators.when(Criteria.where("payType").in(1, 3, 4, 5, 8)).then("$changeBalance").otherwise(0)).as("wechatAmount")
                .and(ConditionalOperators.when(Criteria.where("payType").in(2, 6, 7)).then("$changeBalance").otherwise(0)).as("alipayAmount");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoBalanceUserPayInfoVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoBalanceUserPayInfoVO.class,
                        Aggregation.match(criteria),
                        projectionOperation,
                        sortOperation
                );
        logger.info("listPayInfoVO()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoBalanceUserPayInfoVO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoBalanceUserPayInfoVO.class);
        // 取出最终结果
        List<MongoBalanceUserPayInfoVO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<UserPayInfoVO> resList = BeanUtil.mapAsList(list, UserPayInfoVO.class);
        for (UserPayInfoVO userPayInfoVO : resList) {
            userPayInfoVO.setPayEntry(1);
            userPayInfoVO.setBalanceAmount(0L);
            userPayInfoVO.setScoreCount(0L);
            userPayInfoVO.setShopId(Constant.PLATFORM_SHOP_ID);
            userPayInfoVO.setShopName(Constant.PLATFORM_SHOP_NAME);
        }
        return resList;
    }

    @Override
    public PageVO<UserPayInfoVO> pagePayInfoVO(Date startTime, Date endTime, PageDTO pageDTO) {
        Integer paySysType = getPaySysType();
        PageVO<UserPayInfoVO> pageVO = new PageVO<>();
        List<UserPayInfoVO> list = listPayInfoVO(startTime, endTime);
        // 总条数
        Criteria criteria = new Criteria();
        criteria.and("type").is(1).and("isPayed").is(1).and("paySysType").is(paySysType);
        filterTime(startTime, endTime, criteria);
        TypedAggregation<MongoBalanceFinanceDetailVO> aggregation = Aggregation.newAggregation(
                MongoBalanceFinanceDetailVO.class,
                Aggregation.match(criteria)
        );
        AggregationResults<MongoBalanceFinanceDetailVO> aggregate = mongoTemplate.aggregate(aggregation, MongoBalanceFinanceDetailVO.class);
        List<MongoBalanceFinanceDetailVO> mappedResults = aggregate.getMappedResults();
        pageVO.setTotal((long) mappedResults.size());
        pageVO.setPages(PageUtil.getPages(mappedResults.size(), pageDTO.getPageSize()));
        pageVO.setList(list);
        return pageVO;
    }

    @Override
    public MongoUserBalanceLogBO getByPayIdAndType(Long payId, Integer rechargeType) {
        Query query = new Query(Criteria.where("payId").is(payId).and("rechargeType").is(rechargeType).and("isPayed").is(0));
        List<MongoUserBalanceLogBO> list = mongoTemplate.find(query, MongoUserBalanceLogBO.class);
        if (CollUtil.isEmpty(list)) {
            throw new LuckException("余额记录日志不存在");
        }
        MongoUserBalanceLogBO userBalanceLogBO = list.get(0);
        return userBalanceLogBO;
    }

    @Override
    public void initUserBalanceLogToMongodb() {
        int start = 0;
        int size = 10000;
        mongoTemplate.remove(MongoUserBalanceLogBO.class);
        while (true) {
            // 批量以10000条处理
            List<UserBalanceLog> userBalanceLogs = userBalanceLogMapper.listToMongodb(start, size);
            if (CollUtil.isEmpty(userBalanceLogs)) {
                break;
            }
            List<MongoUserBalanceLogBO> mongoUserBalanceLogs = BeanUtil.mapAsList(userBalanceLogs, MongoUserBalanceLogBO.class);
            mongoTemplate.insertAll(mongoUserBalanceLogs);
            if (userBalanceLogs.size() <= size) {
                break;
            }
            start = start + size;
        }
    }

    @Override
    public List<Long> listUserIdByRechargeAmount(Integer isPayed, Date startDate, Date endDate, Long minAmount, Long maxAmount) {
        Criteria criteria = new Criteria();
        criteria.and("isPayed").is(isPayed)
                .and("type").is(1)
                .and("changeBalance").gte(minAmount).lte(maxAmount);
        filterTime(startDate, endDate, criteria);
        // 分组
        GroupOperation groupOperation = Aggregation.group("userId")
                .first("userId").as("userId");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoUserBalanceLogBO> typedAggregation =
                Aggregation.newAggregation(
                        MongoUserBalanceLogBO.class,
                        Aggregation.match(criteria),
                        groupOperation
                );
        logger.info("listUserIdByRechargeAmount()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoUserBalanceLogBO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoUserBalanceLogBO.class);
        // 取出最终结果
        List<MongoUserBalanceLogBO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        return list.stream().map(MongoUserBalanceLogBO::getUserId).collect(Collectors.toList());
    }

    @Override
    public List<Long> listUserIdByRechargeNum(Integer isPayed, Date startDate, Date endDate, Long minNum, Long maxNum) {
        Criteria criteria = new Criteria();
        criteria.and("isPayed").is(isPayed)
                .and("type").is(1);
        filterTime(startDate, endDate, criteria);
        // 分组
        GroupOperation groupOperation = Aggregation.group("userId")
                .count().as("userNum");
        ProjectionOperation projectionOperation = Aggregation.project().and("_id").as("userId");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoUserBalanceLogBO> typedAggregation =
                Aggregation.newAggregation(
                        MongoUserBalanceLogBO.class,
                        Aggregation.match(criteria),
                        groupOperation,
                        Aggregation.match(Criteria.where("userNum").gte(minNum).lte(maxNum)),
                        projectionOperation
                );
        logger.info("listUserIdByRechargeNum()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoUserBalanceLogBO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoUserBalanceLogBO.class);
        // 取出最终结果
        List<MongoUserBalanceLogBO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        return list.stream().map(MongoUserBalanceLogBO::getUserId).collect(Collectors.toList());
    }

    @Override
    public List<UserManagerVO> setUserInfo(List<UserManagerVO> userList) {
        List<Long> userIds = userList.stream().map(UserManagerVO::getUserId).collect(Collectors.toList());
        if (CollUtil.isEmpty(userIds)) {
            return userList;
        }
        Criteria criteria = new Criteria();
        criteria.and("userId").in(userIds).and("ioType").is(1).and("type").is(1).and("isPayed").is(1);
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime");
        GroupOperation groupOperation = Aggregation.group("userId")
                .first("createTime").as("reConsTime")
                .sum("changeBalance").as("rechargeAmount")
                .count().as("rechargeTimes");
        ProjectionOperation projectionOperation = Aggregation.project("reConsTime", "rechargeAmount", "rechargeTimes")
                .and("_id").as("userId");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoBalanceUserManagerVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoBalanceUserManagerVO.class,
                        Aggregation.match(criteria),
                        sortOperation,
                        groupOperation,
                        projectionOperation
                );
        logger.info("setUserInfo()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoBalanceUserManagerVO> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, MongoBalanceUserManagerVO.class);
        // 取出最终结果
        List<MongoBalanceUserManagerVO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            userList.forEach(user -> {
                user.setRechargeAmount(PriceUtil.toDecimalPrice(0L));
                user.setRechargeTimes(0);
            });
            return userList;
        }
        Map<Long, MongoBalanceUserManagerVO> userMap = list.stream().collect(Collectors.toMap(MongoBalanceUserManagerVO::getUserId, u -> u));
        for (UserManagerVO userManagerVO : userList) {
            MongoBalanceUserManagerVO info = Objects.nonNull(userMap.get(userManagerVO.getUserId())) ? userMap.get(userManagerVO.getUserId()) : new MongoBalanceUserManagerVO();
            userManagerVO.setReConsTime(info.getReConsTime());
            userManagerVO.setRechargeAmount(PriceUtil.toDecimalPrice(info.getRechargeAmount()));
            userManagerVO.setRechargeTimes(info.getRechargeTimes());
        }
        return userList;
    }

    private Long getDefaultValue(Long value) {
        if (Objects.isNull(value)) {
            return 0L;
        }
        return value;
    }

    private Integer getPaySysType() {
        ServerResponseEntity<String> configResponse = configFeignClient.getConfig(Constant.PAY_SETTLEMENT_CONFIG);
        if (!configResponse.isSuccess()) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        PaySettlementConfigVO paySettlementConfigVO = Json.parseObject(configResponse.getData(), PaySettlementConfigVO.class);
        return paySettlementConfigVO.getPaySettlementType();
    }
}
