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

import cn.hutool.core.collection.CollUtil;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.feign.SearchSpuFeignClient;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.StatusEnum;
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.leaf.manager.SegmentManager;
import com.tmerclub.cloud.common.mongodb.util.MongoPageUtil;
import com.tmerclub.cloud.common.product.constant.SpuType;
import com.tmerclub.cloud.common.product.vo.search.SpuSearchVO;
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.product.bo.mongo.MongoSpuBrowseLogBO;
import com.tmerclub.cloud.product.constant.DistributedIdKey;
import com.tmerclub.cloud.product.dto.SpuBrowseLogDTO;
import com.tmerclub.cloud.product.mapper.SpuMapper;
import com.tmerclub.cloud.product.model.SpuBrowseLog;
import com.tmerclub.cloud.product.service.SpuBrowseLogService;
import com.tmerclub.cloud.product.vo.SpuBrowseLogVO;
import com.tmerclub.cloud.product.vo.mongo.MongoSpuBrowseLogVO;
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 java.util.*;
import java.util.stream.Collectors;

/**
 * 商品浏览记录表
 *
 * @author YXF
 * @date 2021-03-19 14:28:14
 */
@Service
public class SpuBrowseLogServiceImpl implements SpuBrowseLogService {
    private static final Logger logger = LoggerFactory.getLogger(SpuBrowseLogServiceImpl.class);

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private SegmentManager segmentManager;
    @Autowired
    private SpuMapper spuMapper;
    @Autowired
    private MongoPageUtil mongoPageUtil;
    @DubboReference
    private SearchSpuFeignClient searchSpuFeignClient;

    @Override
    public PageVO<SpuBrowseLogVO> page(PageDTO pageDTO) {
        List<Long> selectSpuIds = spuMapper.listSpuIdsByExcludeStatus(StatusEnum.DELETE.value());
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(AuthUserContext.get().getUserId())
                .and("status").is(StatusEnum.ENABLE.value())
                .and("spuId").in(selectSpuIds));
        query.with(Sort.by(Sort.Direction.DESC, "updateTime"));
        PageVO<MongoSpuBrowseLogBO> mongoSpuBrowseLogPage = mongoPageUtil.doPage(MongoSpuBrowseLogBO.class, pageDTO, query);

        // 处理商品分页列表
        PageVO<SpuBrowseLogVO> pageVO = new PageVO<>();
        pageVO.setPages(mongoSpuBrowseLogPage.getPages());
        pageVO.setTotal(mongoSpuBrowseLogPage.getTotal());
        pageVO.setList(new ArrayList<>(Constant.INITIAL_CAPACITY));
        if (CollUtil.isEmpty(mongoSpuBrowseLogPage.getList())) {
            return pageVO;
        }
        List<Long> spuIds = mongoSpuBrowseLogPage.getList().stream().map(MongoSpuBrowseLogBO::getSpuId).collect(Collectors.toList());
        ServerResponseEntity<List<SpuSearchVO>> spuResponse = searchSpuFeignClient.listSpuBySpuIds(spuIds);
        Map<Long, SpuSearchVO> spuMap = spuResponse.getData().stream().collect(Collectors.toMap(SpuSearchVO::getSpuId, s -> s));
        for (MongoSpuBrowseLogBO mongoSpuBrowseLogBO : mongoSpuBrowseLogPage.getList()) {
            if (!spuMap.containsKey(mongoSpuBrowseLogBO.getSpuId())) {
                continue;
            }
            SpuBrowseLogVO spuBrowseLogVO = new SpuBrowseLogVO();
            spuBrowseLogVO.setSpuId(mongoSpuBrowseLogBO.getSpuId());
            SpuSearchVO spuSearchVO = spuMap.get(spuBrowseLogVO.getSpuId());
            spuBrowseLogVO.setSpuStatus(spuSearchVO.getSpuStatus());
            spuBrowseLogVO.setMainImgUrl(spuSearchVO.getMainImgUrl());
            spuBrowseLogVO.setSpuName(spuSearchVO.getSpuName());
            spuBrowseLogVO.setSpuType(spuSearchVO.getSpuType());
            spuBrowseLogVO.setScoreFee(spuSearchVO.getScoreFee());
            spuBrowseLogVO.setPriceFee(spuSearchVO.getPriceFee());
            spuBrowseLogVO.setBrowseTime(mongoSpuBrowseLogBO.getUpdateTime());
            spuBrowseLogVO.setSpuBrowseLogId(mongoSpuBrowseLogBO.getSpuBrowseLogId());
            pageVO.getList().add(spuBrowseLogVO);
        }
        return pageVO;
    }

    @Override
    public void save(MongoSpuBrowseLogBO spuBrowseLog) {
        Long spuBrowseLogId = segmentManager.getSegmentId(DistributedIdKey.tmerclub_SPU_BROWSE_LOG);
        spuBrowseLog.setSpuBrowseLogId(spuBrowseLogId);
        spuBrowseLog.setCreateTime(new Date());
        spuBrowseLog.setUpdateTime(new Date());
        spuBrowseLog.setStatus(StatusEnum.ENABLE.value());
        mongoTemplate.insert(spuBrowseLog);
    }

    @Override
    public void updateStatus(MongoSpuBrowseLogBO spuBrowseLog) {
        Update update = new Update();
        update.set("status", spuBrowseLog.getStatus());
        update.set("updateTime", new Date());
        Query query = new Query();
        query.addCriteria(Criteria.where("spuBrowseLogId").is(spuBrowseLog.getSpuBrowseLogId()));
        if (Objects.nonNull(spuBrowseLog.getUserId())) {
            query.addCriteria(Criteria.where("userId").is(spuBrowseLog.getUserId()));
        }
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, MongoSpuBrowseLogBO.class);
        if (updateResult.getModifiedCount() < 1) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    public void delete(SpuBrowseLogDTO spuBrowseLogDTO) {
        if (CollUtil.isEmpty(spuBrowseLogDTO.getSpuBrowseLogIds())) {
            return;
        }
        Long userId = AuthUserContext.get().getUserId();
        Query query = new Query();
        query.addCriteria(Criteria.where("spuBrowseLogId").in(spuBrowseLogDTO.getSpuBrowseLogIds()));
        query.addCriteria(Criteria.where("userId").is(userId));
        Update update = new Update();
        update.set("status", StatusEnum.DELETE.value());
        UpdateResult updateResult = mongoTemplate.updateMulti(query, update, MongoSpuBrowseLogBO.class);
        if (updateResult.getModifiedCount() < 1) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    public Long recommendCategoryId(Integer spuType) {
        if (Objects.isNull(spuType)) {
            spuType = SpuType.NORMAL.value();
        }
        // 过滤
        Criteria criteria = Criteria.where("status").is(StatusEnum.ENABLE.value())
                .and("userId").is(AuthUserContext.get().getUserId())
                .and("spuType").is(spuType);
        // 更新时间倒序
        SortOperation updateTimeSort = Aggregation.sort(Sort.Direction.DESC, "updateTime");
        // 使用商品浏览记录进行推荐时，使用的数据数量
        LimitOperation limitOperation = Aggregation.limit(Constant.MAX_SPU_BROWSE_NUM);
        // 分组
        GroupOperation groupOperation = Aggregation.group("categoryId")
                .count().as("num")
                .last("categoryId").as("categoryId");
        // 分类数倒序
        SortOperation numSort = Aggregation.sort(Sort.Direction.DESC, "num");
        // 只获取一个结果
        LimitOperation one = Aggregation.limit(1);
        ProjectionOperation projectionOperation = Aggregation.project("num", "categoryId");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<MongoSpuBrowseLogBO> typedAggregation = Aggregation.newAggregation(
                MongoSpuBrowseLogBO.class,
                Aggregation.match(criteria),
                updateTimeSort,
                limitOperation,
                groupOperation,
                numSort,
                one,
                projectionOperation
        );
        logger.info("recommendCategoryId()的mongodb查询语句： {}", typedAggregation);
        // 执行聚合操作
        AggregationResults<MongoSpuBrowseLogVO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoSpuBrowseLogVO.class);
        // 取出最终结果
        List<MongoSpuBrowseLogVO> spuBrowseLogList = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(spuBrowseLogList)) {
            return null;
        }
        return spuBrowseLogList.get(0).getCategoryId();
    }

    @Override
    public SpuBrowseLog getCurrentLogBySpuIdAndUserId(SpuBrowseLogDTO spuBrowseLogDTO) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(spuBrowseLogDTO.getUserId())
                .and("spuId").is(spuBrowseLogDTO.getSpuId())
                .and("spuType").is(spuBrowseLogDTO.getSpuType())
                .and("createTime").lte(new Date()));
        MongoSpuBrowseLogBO spuBrowseLogBO = mongoTemplate.findOne(query, MongoSpuBrowseLogBO.class);
        return BeanUtil.map(spuBrowseLogBO, SpuBrowseLog.class);
    }

    @Override
    public List<Long> recentBrowse(Long userId, Long shopId) {
        List<Long> spuIds = spuMapper.listSpuIdByShopIdsAndStatus(SysTypeEnum.MULTISHOP.value(), Collections.singletonList(shopId), null, StatusEnum.DELETE.value());
        Criteria criteria = new Criteria();
        criteria.and("status").is(StatusEnum.ENABLE.value())
                .and("userId").is(userId)
                .and("spuId").in(spuIds);
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "updateTime");
        LimitOperation limitOperation = Aggregation.limit(3);
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                sortOperation,
                limitOperation
        );
        logger.info("recentBrowse()的mongodb查询语句： {}", aggregation);
        AggregationResults<MongoSpuBrowseLogBO> aggregate = mongoTemplate.aggregate(aggregation, MongoSpuBrowseLogBO.class, MongoSpuBrowseLogBO.class);
        List<MongoSpuBrowseLogBO> spuBrowseLogList = aggregate.getMappedResults();
        if (CollUtil.isEmpty(spuBrowseLogList)) {
            return new ArrayList<>(0);
        }
        return spuBrowseLogList.stream().map(MongoSpuBrowseLogBO::getSpuId).collect(Collectors.toList());
    }
}
