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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.tmerclub.cloud.api.payment.constant.PaySysType;
import com.tmerclub.cloud.api.supplier.bo.SupplierSimpleBO;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.SysTypeEnum;
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.mongodb.util.MongoPageUtil;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.common.util.PriceUtil;
import com.tmerclub.cloud.supplier.constant.SupplierWalletAmountType;
import com.tmerclub.cloud.supplier.constant.SupplierWalletChangeReason;
import com.tmerclub.cloud.supplier.constant.SupplierWalletIoType;
import com.tmerclub.cloud.supplier.dto.SupplierWalletLogSearchDTO;
import com.tmerclub.cloud.supplier.mapper.SupplierWalletMapper;
import com.tmerclub.cloud.supplier.model.SupplierDetail;
import com.tmerclub.cloud.supplier.mongo.MongoSupplierWalletLogBO;
import com.tmerclub.cloud.supplier.service.SupplierAllinpayService;
import com.tmerclub.cloud.supplier.service.SupplierDetailService;
import com.tmerclub.cloud.supplier.service.SupplierWalletLogService;
import com.tmerclub.cloud.supplier.vo.SupplierWalletLogExcelVO;
import com.tmerclub.cloud.supplier.vo.SupplierWalletVO;
import com.tmerclub.cloud.supplier.vo.mongo.MongoSupplierWalletVO;
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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 供应商钱包记录
 *
 * @author lhd
 * @date 2022-03-14 17:04:32
 */
@Service
public class SupplierWalletLogServiceImpl implements SupplierWalletLogService {

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

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private MongoPageUtil mongoPageUtil;

    @Autowired
    private SupplierDetailService supplierDetailService;

    @Autowired
    private SupplierWalletMapper supplierWalletMapper;

    @Autowired
    private SupplierAllinpayService supplierAllinpayService;

    @Override
    public PageVO<MongoSupplierWalletLogBO> pageByParam(PageDTO pageDTO, SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = getCriteria(supplierWalletLogSearchDTO);
        Query query = new Query(criteria);
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        return mongoPageUtil.doPage(MongoSupplierWalletLogBO.class, pageDTO, query);
    }

    @Override
    public void saveMongoBatch(List<MongoSupplierWalletLogBO> supplierWalletLogBOList) {
        if (CollUtil.isEmpty(supplierWalletLogBOList)) {
            logger.info("供应商日志为空，返回");
            return;
        }
        // 批量操作
        logger.info("添加供应商钱包日志至mongodb{}", Json.toJsonString(supplierWalletLogBOList));
        mongoTemplate.insert(supplierWalletLogBOList, MongoSupplierWalletLogBO.class);
    }

    @Override
    public PageVO<MongoSupplierWalletLogBO> pageAllSupplier(PageDTO pageDTO, SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = getCriteria(supplierWalletLogSearchDTO);
        Query query = new Query(criteria);
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));

        PageVO<MongoSupplierWalletLogBO> pageVO = mongoPageUtil.doPage(MongoSupplierWalletLogBO.class, pageDTO, query);

        // 供应商名称
        Map<Long, String> supplierMap = getSupplierMap(pageVO.getList().stream().map(MongoSupplierWalletLogBO::getSupplierId));
        for (MongoSupplierWalletLogBO mongoSupplierWalletLogBO : pageVO.getList()) {
            if (supplierMap.containsKey(mongoSupplierWalletLogBO.getSupplierId())) {
                mongoSupplierWalletLogBO.setSupplierName(supplierMap.get(mongoSupplierWalletLogBO.getSupplierId()));
            }
        }
        return pageVO;
    }

    @Override
    public long countInAmountByOrderId(Long orderId, Integer shopWalletChangeReason) {
        Criteria criteria = Criteria.where("orderId").is(orderId).and("reason").is(shopWalletChangeReason);
        return countByMongoDb(criteria);
    }

    @Override
    public long countOutAmountByRefundId(Long refundId, Integer shopWalletChangeReason) {
        Criteria criteria = Criteria.where("refundId").is(refundId).and("reason").is(shopWalletChangeReason);
        return countByMongoDb(criteria);
    }

    @Override
    public List<MongoSupplierWalletLogBO> listByParam(SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = getCriteria(supplierWalletLogSearchDTO);
        Query query = new Query(criteria);
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        return mongoTemplate.find(query, MongoSupplierWalletLogBO.class);
    }

    @Override
    public SupplierWalletVO getSupplierWalletByTime(SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = getCriteria(supplierWalletLogSearchDTO);
        ProjectionOperation projectionOperation = Aggregation.project()
                // 待结算金额收入： 用户支付 拒绝用户退款
                .and(ConditionalOperators.when(Criteria.where("reason").in(0, 3)).then(ConditionalOperators.when(Criteria.where("amountType").is(0)).then("$changeAmount").otherwise(0)).otherwise(0)).as("inUnsettleAmount")
                // 待结算金额支出： 用户退款成功 系统扣除订单中需要颁发给用户的分销金额
                .and(ConditionalOperators.when(Criteria.where("reason").in(2, 9)).then(ConditionalOperators.when(Criteria.where("amountType").is(0)).then("$changeAmount").otherwise(0)).otherwise(0)).as("outUnsettleAmount")
                // 总结算金额：订单结算 余额充值成功 预售失败结算（超时未支付尾款）
                .and(ConditionalOperators.when(Criteria.where("reason").in(1)).then(ConditionalOperators.when(Criteria.where("amountType").is(1)).then("$changeAmount").otherwise(0)).otherwise(0)).as("totalSettleAmount")
                // 冻结金额收入：提现申请
                .and(ConditionalOperators.when(Criteria.where("reason").in(4)).then(ConditionalOperators.when(Criteria.where("amountType").is(2)).then("$changeAmount").otherwise(0)).otherwise(0)).as("inFreezeAmount")
                // 冻结金额支出：提现申请被拒绝 提现申请通过
                .and(ConditionalOperators.when(Criteria.where("reason").in(5, 6)).then(ConditionalOperators.when(Criteria.where("amountType").is(2)).then("$changeAmount").otherwise(0)).otherwise(0)).as("outFreezeAmount");
        // 分组
        GroupOperation groupOperation = Aggregation.group()
                .sum("inUnsettleAmount").as("inUnsettleAmount")
                .sum("outUnsettleAmount").as("outUnsettleAmount")
                .sum("totalSettleAmount").as("totalSettleAmount")
                .sum("inFreezeAmount").as("inFreezeAmount")
                .sum("outFreezeAmount").as("outFreezeAmount");

        TypedAggregation<MongoSupplierWalletVO> typedAggregation = Aggregation.newAggregation(
                MongoSupplierWalletVO.class,
                Aggregation.match(criteria),
                projectionOperation,
                groupOperation
        );

        logger.info("getSupplierWalletByTime()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoSupplierWalletVO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoSupplierWalletVO.class);
        // 取出最终结果
        List<MongoSupplierWalletVO> list = aggregationResults.getMappedResults();
        SupplierWalletVO supplierWalletVO = new SupplierWalletVO();
        if (CollUtil.isEmpty(list)) {
            return supplierWalletVO;
        }
        MongoSupplierWalletVO mongoSupplierWalletVO = list.get(0);
        supplierWalletVO.setUnsettledAmount(mongoSupplierWalletVO.getInUnsettleAmount() - mongoSupplierWalletVO.getOutUnsettleAmount());
        supplierWalletVO.setTotalSettledAmount(mongoSupplierWalletVO.getTotalSettleAmount());
        supplierWalletVO.setFreezeAmount(mongoSupplierWalletVO.getInFreezeAmount() - mongoSupplierWalletVO.getOutFreezeAmount());
        return supplierWalletVO;
    }

    @Override
    public PageVO<SupplierWalletVO> pageSupplierWalletByTime(PageDTO pageDTO, SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = getCriteria(supplierWalletLogSearchDTO);
        ProjectionOperation projectionOperation = Aggregation.project("supplierId")
                // 待结算金额收入： 用户支付 拒绝用户退款
                .and(ConditionalOperators.when(Criteria.where("reason").in(0, 3)).then(ConditionalOperators.when(Criteria.where("amountType").is(0)).then("$changeAmount").otherwise(0)).otherwise(0)).as("inUnsettleAmount")
                // 待结算金额支出： 用户退款成功 系统扣除订单中需要颁发给用户的分销金额
                .and(ConditionalOperators.when(Criteria.where("reason").in(2, 9)).then(ConditionalOperators.when(Criteria.where("amountType").is(0)).then("$changeAmount").otherwise(0)).otherwise(0)).as("outUnsettleAmount")
                // 总结算金额：订单结算 余额充值成功 预售失败结算（超时未支付尾款）
                .and(ConditionalOperators.when(Criteria.where("reason").in(1)).then(ConditionalOperators.when(Criteria.where("amountType").is(1)).then("$changeAmount").otherwise(0)).otherwise(0)).as("totalSettleAmount")
                // 冻结金额收入：提现申请
                .and(ConditionalOperators.when(Criteria.where("reason").in(4)).then(ConditionalOperators.when(Criteria.where("amountType").is(2)).then("$changeAmount").otherwise(0)).otherwise(0)).as("inFreezeAmount")
                // 冻结金额支出：提现申请被拒绝 提现申请通过
                .and(ConditionalOperators.when(Criteria.where("reason").in(5, 6)).then(ConditionalOperators.when(Criteria.where("amountType").is(2)).then("$changeAmount").otherwise(0)).otherwise(0)).as("outFreezeAmount");
        // 分组
        GroupOperation groupOperation = Aggregation.group("supplierId")
                .first("supplierId").as("supplierId")
                .sum("inUnsettleAmount").as("inUnsettleAmount")
                .sum("outUnsettleAmount").as("outUnsettleAmount")
                .sum("totalSettleAmount").as("totalSettleAmount")
                .sum("inFreezeAmount").as("inFreezeAmount")
                .sum("outFreezeAmount").as("outFreezeAmount");

        SortOperation sortOperation = Aggregation.sort(Sort.Direction.ASC, "supplierId");
        // 分页数据
        SkipOperation skipOperation = Aggregation.skip((long) pageDTO.getPageSize() * (pageDTO.getPageNum() - 1));
        LimitOperation limitOperation = Aggregation.limit(pageDTO.getPageSize());
        TypedAggregation<MongoSupplierWalletVO> typedAggregation = Aggregation.newAggregation(
                MongoSupplierWalletVO.class,
                Aggregation.match(criteria),
                projectionOperation,
                groupOperation,
                sortOperation,
                skipOperation,
                limitOperation
        );

        logger.info("pageSupplierWalletByTime()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoSupplierWalletVO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoSupplierWalletVO.class);
        // 取出最终结果
        List<MongoSupplierWalletVO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new PageVO<>();
        }
        // 总条数
        TypedAggregation<MongoSupplierWalletVO> aggregation = Aggregation.newAggregation(
                MongoSupplierWalletVO.class,
                Aggregation.match(criteria),
                projectionOperation,
                groupOperation
        );

        // 执行聚合操作
        AggregationResults<MongoSupplierWalletVO> aggregate = mongoTemplate.aggregate(aggregation, MongoSupplierWalletVO.class);
        // 取出最终结果
        List<MongoSupplierWalletVO> mappedResults = aggregate.getMappedResults();
        // 组装数据
        List<SupplierWalletVO> result = new ArrayList<>(Constant.INITIAL_CAPACITY);
        Map<Long, String> supplierMap = getSupplierMap(list.stream().map(MongoSupplierWalletVO::getSupplierId));
        for (MongoSupplierWalletVO mongoSupplierWalletVO : list) {
            SupplierWalletVO supplierWalletVO = new SupplierWalletVO();
            String supplierName = supplierMap.get(mongoSupplierWalletVO.getSupplierId());
            supplierWalletVO.setSupplierId(mongoSupplierWalletVO.getSupplierId());
            supplierWalletVO.setSupplierName(supplierName);
            supplierWalletVO.setUnsettledAmount(mongoSupplierWalletVO.getInUnsettleAmount() - mongoSupplierWalletVO.getOutUnsettleAmount());
            supplierWalletVO.setTotalSettledAmount(mongoSupplierWalletVO.getTotalSettleAmount());
            supplierWalletVO.setFreezeAmount(mongoSupplierWalletVO.getInFreezeAmount() - mongoSupplierWalletVO.getOutFreezeAmount());
            result.add(supplierWalletVO);
        }
        PageVO<SupplierWalletVO> page = new PageVO<>();
        page.setTotal((long) mappedResults.size());
        page.setList(result);
        page.setPages(PageUtil.getPages(mappedResults.size(), pageDTO.getPageSize()));
        return page;
    }

    private Map<Long, String> getSupplierMap(Stream<Long> list) {
        List<Long> supplierIds = list.collect(Collectors.toList());
        List<SupplierDetail> shopDetails = supplierDetailService.listBySupplierIds(supplierIds);
        return shopDetails.stream().collect(Collectors.toMap(SupplierDetail::getSupplierId, SupplierDetail::getSupplierName));
    }

    @Override
    public List<SupplierWalletLogExcelVO> listSupplierWalletLogExcel(SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria;
        if (CollUtil.isNotEmpty(supplierWalletLogSearchDTO.getSupplierIds())) {
            Integer paySysType = supplierAllinpayService.getIsAllinpay() ? PaySysType.ALLINPAY.value() : PaySysType.DEFAULT.value();
            criteria = Criteria.where("supplierId").in(supplierWalletLogSearchDTO.getSupplierIds()).and("paySysType").is(paySysType);
        } else {
            criteria = getCriteria(supplierWalletLogSearchDTO);
        }
        ProjectionOperation project = Aggregation.project("supplierId", "orderId", "refundId", "amountType", "ioType", "changeAmount", "reason",
                "userAmount", "platformAmount", "distributionAmount", "platformCommission", "createTime");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.ASC, "supplierId").and(Sort.Direction.DESC, "createTime");
        TypedAggregation<MongoSupplierWalletLogBO> typedAggregation = Aggregation.newAggregation(
                MongoSupplierWalletLogBO.class,
                Aggregation.match(criteria),
                project,
                sortOperation
        );

        logger.info("listSupplierWalletLogExcel()的mongodb查询语句： {}", typedAggregation);

        // 执行聚合操作
        AggregationResults<MongoSupplierWalletLogBO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoSupplierWalletLogBO.class);
        // 取出最终结果
        List<MongoSupplierWalletLogBO> list = aggregationResults.getMappedResults();
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>(0);
        }
        Map<Long, String> shopMap = getSupplierMap(list.stream().map(MongoSupplierWalletLogBO::getSupplierId));
        List<SupplierWalletLogExcelVO> result = new ArrayList<>(Constant.INITIAL_CAPACITY);
        int index = 1;
        for (MongoSupplierWalletLogBO mongoSupplierWalletLogBO : list) {
            SupplierWalletLogExcelVO supplierWalletLogExcel = new SupplierWalletLogExcelVO();
            supplierWalletLogExcel.setSeq(String.valueOf(index));
            supplierWalletLogExcel.setSupplierName(shopMap.get(mongoSupplierWalletLogBO.getSupplierId()));
            supplierWalletLogExcel.setAmountType(SupplierWalletAmountType.getAmountTypeName(mongoSupplierWalletLogBO.getAmountType()));
            supplierWalletLogExcel.setIoType(SupplierWalletIoType.getIoName(mongoSupplierWalletLogBO.getIoType()));
            supplierWalletLogExcel.setChangeAmount(PriceUtil.toDecimalPrice(mongoSupplierWalletLogBO.getChangeAmount()).toString());
            supplierWalletLogExcel.setOrderId(Objects.isNull(mongoSupplierWalletLogBO.getOrderId()) ? "" : mongoSupplierWalletLogBO.getOrderId().toString());
            supplierWalletLogExcel.setRefundId(Objects.isNull(mongoSupplierWalletLogBO.getRefundId()) ? "" : mongoSupplierWalletLogBO.getRefundId().toString());
            supplierWalletLogExcel.setUserAmount(PriceUtil.toDecimalPrice(mongoSupplierWalletLogBO.getUserAmount()).toString());
            supplierWalletLogExcel.setPlatformAmount(PriceUtil.toDecimalPrice(mongoSupplierWalletLogBO.getPlatformAmount()).toString());
            supplierWalletLogExcel.setDistributionAmount(PriceUtil.toDecimalPrice(mongoSupplierWalletLogBO.getDistributionAmount()).toString());
            supplierWalletLogExcel.setPlatformCommission(PriceUtil.toDecimalPrice(mongoSupplierWalletLogBO.getPlatformCommission()).toString());
            supplierWalletLogExcel.setReasonStr(SupplierWalletChangeReason.getReason(mongoSupplierWalletLogBO.getReason()));
            supplierWalletLogExcel.setCreateTime(mongoSupplierWalletLogBO.getCreateTime());
            result.add(supplierWalletLogExcel);
            index++;
        }
        return result;
    }

    private long countByMongoDb(Criteria criteria) {
        return mongoTemplate.count(new Query(criteria), MongoSupplierWalletLogBO.class);
    }

    /**
     * 获取MongoDB的搜索条件
     */
    private Criteria getCriteria(SupplierWalletLogSearchDTO supplierWalletLogSearchDTO) {
        Criteria criteria = new Criteria();
        Integer paySysType = supplierAllinpayService.getIsAllinpay() ? PaySysType.ALLINPAY.value() : PaySysType.DEFAULT.value();
        Integer sysType = Objects.nonNull(AuthUserContext.get()) ? AuthUserContext.get().getSysType() : null;
        criteria.and("paySysType").is(paySysType);
        if (Objects.nonNull(supplierWalletLogSearchDTO.getOrderId())) {
            criteria.and("orderId").is(supplierWalletLogSearchDTO.getOrderId());
        }
        if (Objects.nonNull(supplierWalletLogSearchDTO.getRefundId())) {
            criteria.and("refundId").is(supplierWalletLogSearchDTO.getRefundId());
        }
        if (Objects.nonNull(supplierWalletLogSearchDTO.getAmountType())) {
            criteria.and("amountType").is(supplierWalletLogSearchDTO.getAmountType());
        }

        if (Objects.equals(sysType, SysTypeEnum.MULTISHOP.value()) && Objects.equals(supplierWalletLogSearchDTO.getReason(), SupplierWalletChangeReason.APPLY_CASH.value())) {
            // 商家端搜索原因为提现申请的钱包记录,reason包括4,5,6
            criteria.and("reason")
                    .in(SupplierWalletChangeReason.APPLY_CASH.value(), SupplierWalletChangeReason.REFUSE_CASH.value(), SupplierWalletChangeReason.PASS_CASH.value());
        }else if (Objects.nonNull(supplierWalletLogSearchDTO.getReason())){
            criteria.and("reason").is(supplierWalletLogSearchDTO.getReason());
        }
        if (Objects.nonNull(supplierWalletLogSearchDTO.getSupplierId())) {
            criteria.and("supplierId").is(supplierWalletLogSearchDTO.getSupplierId());
        }
        if (Objects.nonNull(supplierWalletLogSearchDTO.getIoType())) {
            criteria.and("ioType").is(supplierWalletLogSearchDTO.getIoType());
        }
        if (StrUtil.isNotBlank(supplierWalletLogSearchDTO.getSupplierName())) {
            SupplierSimpleBO supplierSimpleBO = new SupplierSimpleBO();
            supplierSimpleBO.setSupplierName(supplierWalletLogSearchDTO.getSupplierName());
            List<SupplierSimpleBO> shopSimpleList = supplierDetailService.listSimple(supplierSimpleBO);
            if (CollUtil.isNotEmpty(shopSimpleList)) {
                List<Long> supplierIds = shopSimpleList.stream().map(SupplierSimpleBO::getSupplierId).collect(Collectors.toList());
                criteria.and("supplierId").in(supplierIds);
            }
        }
        if (Objects.nonNull(supplierWalletLogSearchDTO.getStartTime())) {
            criteria.and("createTime").gt(supplierWalletLogSearchDTO.getStartTime()).lt(supplierWalletLogSearchDTO.getEndTime());
        }
        return criteria;
    }

}
