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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import com.tmerclub.cloud.api.order.bo.UserScoreLogOrderBO;
import com.tmerclub.cloud.api.order.feign.OrderFeignClient;
import com.tmerclub.cloud.api.product.feign.SpuFeignClient;
import com.tmerclub.cloud.api.user.vo.UserManagerVO;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.mongodb.util.MongoPageUtil;
import com.tmerclub.cloud.common.order.vo.OrderItemVO;
import com.tmerclub.cloud.common.product.vo.SpuVO;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.common.util.DateUtils;
import com.tmerclub.cloud.user.bo.mongo.MongoUserScoreLogBO;
import com.tmerclub.cloud.user.mapper.UserScoreLockMapper;
import com.tmerclub.cloud.user.mapper.UserScoreLogMapper;
import com.tmerclub.cloud.user.model.UserScoreLog;
import com.tmerclub.cloud.user.service.UserScoreLogService;
import com.tmerclub.cloud.user.vo.UserScoreLogVO;
import com.tmerclub.cloud.user.vo.mongo.MongoScoreUserManagerVO;
import com.tmerclub.cloud.user.vo.mongo.MongoScoreUserPayInfoVO;
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.stereotype.Service;

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

/**
 * 用户积分记录
 *
 * @author YXF
 * @date 2020-12-08 10:16:53
 */
@Service
public class UserScoreLogServiceImpl implements UserScoreLogService {
    private static final Logger logger = LoggerFactory.getLogger(UserScoreLogServiceImpl.class);

    @Autowired
    private UserScoreLogMapper userScoreLogMapper;
    @DubboReference
    private SpuFeignClient spuFeignClient;
    @DubboReference
    private OrderFeignClient orderFeignClient;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private MongoPageUtil mongoPageUtil;
    @Autowired
    private UserScoreLockMapper userScoreLockMapper;

    @Override
    public void saveToMongdb(MongoUserScoreLogBO mongoUserScoreLogBO) {
        if (Objects.isNull(mongoUserScoreLogBO) || Objects.isNull(mongoUserScoreLogBO.getLogId())) {
            throw new LuckException("积分日志信息不全");
        }
        mongoUserScoreLogBO.setCreateTime(new Date());
        mongoUserScoreLogBO.setUpdateTime(new Date());
        logger.info("保存积分记录到mongodb{}", mongoUserScoreLogBO);
        mongoTemplate.save(mongoUserScoreLogBO);
    }

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

    @Override
    public PageVO<UserScoreLogVO> pageByIoTypeAndSource(PageDTO pageDTO, Integer ioType, Integer source) {
        Long userId = AuthUserContext.get().getUserId();
        Criteria criteria = new Criteria();
        criteria.and("userId").is(userId).and("score").gt(0);
        if (Objects.nonNull(ioType)) {
            criteria.and("ioType").is(ioType);
        }
        if (Objects.nonNull(source)) {
            criteria.and("source").is(source);
        }
        Query query = new Query(criteria);
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        PageVO<MongoUserScoreLogBO> mongoUserScoreLogPage = mongoPageUtil.doPage(MongoUserScoreLogBO.class, pageDTO, query);
        List<UserScoreLogVO> userScoreLogs = BeanUtil.mapAsList(mongoUserScoreLogPage.getList(), UserScoreLogVO.class);
        PageVO<UserScoreLogVO> pageVO = new PageVO<>();
        pageVO.setTotal(mongoUserScoreLogPage.getTotal());
        pageVO.setPages(mongoUserScoreLogPage.getPages());
        if (CollUtil.isEmpty(userScoreLogs)) {
            pageVO.setList(new ArrayList<>());
            return pageVO;
        }
        List<Long> bizIds = userScoreLogs.stream().map(UserScoreLogVO::getBizId).collect(Collectors.toList());
        if (CollUtil.isEmpty(bizIds)) {
            userScoreLogs.forEach(userScoreLog -> userScoreLog.setIsLock(0));
            pageVO.setList(userScoreLogs);
            return pageVO;
        }
        List<Long> lockBizIds = userScoreLockMapper.listByBizIds(bizIds);
        if (CollUtil.isEmpty(lockBizIds)) {
            userScoreLogs.forEach(userScoreLog -> userScoreLog.setIsLock(0));
            pageVO.setList(userScoreLogs);
            return pageVO;
        }
        userScoreLogs.forEach(userScoreLog -> userScoreLog.setIsLock(lockBizIds.contains(userScoreLog.getBizId()) ? 1 : 0));
        pageVO.setList(userScoreLogs);
        return pageVO;
    }

    @Override
    public PageVO<UserScoreLogVO> pageByUserId(PageDTO pageDTO, Long userId) {
        Query query = new Query(Criteria.where("userId").is(userId));
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        PageVO<MongoUserScoreLogBO> page = mongoPageUtil.doPage(MongoUserScoreLogBO.class, pageDTO, query);
        PageVO<UserScoreLogVO> pageVO = new PageVO<>();
        pageVO.setList(BeanUtil.mapAsList(page.getList(), UserScoreLogVO.class));
        pageVO.setTotal(page.getTotal());
        pageVO.setPages(page.getPages());
        return pageVO;
    }

    @Override
    public void deleteByUserId(Long userId) {
        Query query = new Query(Criteria.where("userId").is(userId));
        mongoTemplate.remove(query, MongoUserScoreLogBO.class);
    }

    @Override
    public Long countByUserIdAndDateTimeAndType(Integer type, Long userId, DateTime beginOfDay, DateTime endOfDay) {
        Query query = new Query(Criteria.where("userId").is(userId).and("source").is(type)
                .and("createTime")
                .gte(Objects.requireNonNull(DateUtils.dateToIsoDate(beginOfDay)))
                .lte(Objects.requireNonNull(DateUtils.dateToIsoDate(endOfDay))));
        return mongoTemplate.count(query, MongoUserScoreLogBO.class);
    }

    @Override
    public UserScoreLog getOrderScoreLog(Long userId, Long bizId, Integer source, Integer ioType) {
        Query query = new Query(Criteria.where("userId").is(userId).and("bizId").is(bizId).and("source").is(source).and("ioType").is(ioType));
        MongoUserScoreLogBO userScoreLogBO = mongoTemplate.findOne(query, MongoUserScoreLogBO.class);
        return BeanUtil.map(userScoreLogBO, UserScoreLog.class);
    }

    @Override
    public PageVO<UserScoreLogVO> scoreProdPage(PageDTO pageDTO) {
        Long userId = AuthUserContext.get().getUserId();
        Query query = new Query(Criteria.where("userId").is(userId).and("source").gte(10).lte(11));
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        PageVO<MongoUserScoreLogBO> page = mongoPageUtil.doPage(MongoUserScoreLogBO.class, pageDTO, query);
        PageVO<UserScoreLogVO> userScoreLogPage = new PageVO<>();
        userScoreLogPage.setPages(page.getPages());
        userScoreLogPage.setTotal(page.getTotal());
        userScoreLogPage.setList(BeanUtil.mapAsList(page.getList(), UserScoreLogVO.class));
        if (CollUtil.isEmpty(userScoreLogPage.getList())) {
            return userScoreLogPage;
        }
        List<Long> orderIds = userScoreLogPage.getList().stream().map(UserScoreLogVO::getBizId).filter(Objects::nonNull).collect(Collectors.toList());
        // 获取订单信息
        ServerResponseEntity<List<OrderItemVO>> orderResponseEntity = orderFeignClient.listOrderItemByOrderIds(orderIds);
        if (!orderResponseEntity.isSuccess()) {
            throw new LuckException(orderResponseEntity.getMsg());
        }
        //获取订单数据
        ServerResponseEntity<List<UserScoreLogOrderBO>> orderResponse = orderFeignClient.getUserScoreLogDetail(new ArrayList<>(orderIds));
        if (!Objects.equals(orderResponse.getCode(), ResponseEnum.OK.value())) {
            throw new LuckException(orderResponse.getMsg());
        }
        Map<Long, UserScoreLogOrderBO> orderScoreMap = orderResponse.getData().stream().collect(Collectors.toMap(UserScoreLogOrderBO::getOrderId, u -> u));
        Map<Long, Long> orderMap = new HashMap<>(16);
        for (OrderItemVO orderItemVO : orderResponseEntity.getData()) {
            // 积分商品只有一个订单项
            orderMap.put(orderItemVO.getOrderId(), orderItemVO.getSpuId());
        }
        ServerResponseEntity<List<SpuVO>> prodResponseEntity = spuFeignClient.listSpuBySpuIds(new ArrayList<>(orderMap.values()));
        if (!prodResponseEntity.isSuccess()) {
            throw new LuckException(prodResponseEntity.getMsg());
        }
        Map<Long, SpuVO> spuMap = prodResponseEntity.getData().stream().collect(Collectors.toMap(SpuVO::getSpuId, s -> s));
        for (UserScoreLogVO userScoreLogVO : userScoreLogPage.getList()) {
            SpuVO spuVO = spuMap.get(orderMap.get(userScoreLogVO.getBizId()));
            UserScoreLogOrderBO userScoreLogOrderBO = orderScoreMap.get(userScoreLogVO.getBizId());
            if (Objects.nonNull(userScoreLogOrderBO)) {
                userScoreLogVO.setActualTotal(userScoreLogOrderBO.getActualTotal());
                userScoreLogVO.setScoreFee(userScoreLogOrderBO.getScoreFee());
                userScoreLogVO.setAllCount(userScoreLogOrderBO.getAllCount());
            }
            if (Objects.isNull(spuVO)) {
                continue;
            }
            userScoreLogVO.setSpuName(spuVO.getName());
            userScoreLogVO.setMainImgUrl(spuVO.getMainImgUrl());
            userScoreLogVO.setSingleScoreFee(spuVO.getScoreFee());
        }
        return userScoreLogPage;
    }

    @Override
    public Long getScoreCountByRefundIds(List<Long> refundIds) {
        if (CollUtil.isEmpty(refundIds)) {
            return 0L;
        }
        Criteria criteria = new Criteria("source").is(9).and("bizId").in(refundIds);
        ProjectionOperation projectionOperation = Aggregation.project()
                .and(ConditionalOperators.when(Criteria.where("score").isNull()).then(0).otherwise("$score")).as("score");
        GroupOperation groupOperation = Aggregation.group().sum("score").as("scoreCount");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoScoreUserPayInfoVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoScoreUserPayInfoVO.class,
                        Aggregation.match(criteria),
                        projectionOperation,
                        groupOperation
                );
        logger.info("getScoreCountByRefundIds()的mongodb查询语句： {}", typedAggregation);

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

    @Override
    public UserScoreLog getExpireScoreLogByUserId(Long userId) {
        Query query = new Query(Criteria.where("userId").is(userId).and("source").is(5).and("ioType").is(0).and("score").gt(0));
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        MongoUserScoreLogBO userScoreLogBO = mongoTemplate.findOne(query, MongoUserScoreLogBO.class);
        return BeanUtil.map(userScoreLogBO, UserScoreLog.class);
    }

    @Override
    public void initUserScoreLogToMongodb() {
        int start = 0;
        int size = 10000;
        mongoTemplate.remove(MongoUserScoreLogBO.class);
        while (true) {
            // 批量以10000条处理
            List<UserScoreLog> userScoreLogs = userScoreLogMapper.listToMongodb(start, size);
            if (CollUtil.isEmpty(userScoreLogs)) {
                break;
            }
            List<MongoUserScoreLogBO> mongoUserScoreLogs = BeanUtil.mapAsList(userScoreLogs, MongoUserScoreLogBO.class);
            mongoTemplate.insertAll(mongoUserScoreLogs);
            if (userScoreLogs.size() <= size) {
                break;
            }
            start = start + size;
        }
    }

    @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);
        GroupOperation groupOperation = Aggregation.group("userId")
                .sum("score").as("sumScore");
        ProjectionOperation projectionOperation = Aggregation.project("sumScore")
                .and("_id").as("userId");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoScoreUserManagerVO> typedAggregation =
                Aggregation.newAggregation(
                        MongoScoreUserManagerVO.class,
                        Aggregation.match(criteria),
                        groupOperation,
                        projectionOperation
                );
        logger.info("setUserInfo()的mongodb查询语句： {}", typedAggregation);

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