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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.BooleanUtil;
import com.google.common.collect.Lists;
import com.tmerclub.cloud.api.order.constant.OrderStatus;
import com.tmerclub.cloud.api.order.feign.OrderFeignClient;
import com.tmerclub.cloud.api.user.bo.UserScoreBO;
import com.tmerclub.cloud.api.user.dto.UserScoreLockDTO;
import com.tmerclub.cloud.common.constant.Constant;
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.order.bo.OrderStatusBO;
import com.tmerclub.cloud.common.order.constant.OrderType;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.rocketmq.config.RocketMqConstant;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.user.bo.mongo.MongoUserScoreLogBO;
import com.tmerclub.cloud.user.constant.DistributedIdKey;
import com.tmerclub.cloud.user.constant.ScoreGetLogStatusEnum;
import com.tmerclub.cloud.user.constant.ScoreLogTypeEnum;
import com.tmerclub.cloud.user.mapper.UserExtensionMapper;
import com.tmerclub.cloud.user.mapper.UserScoreGetLogMapper;
import com.tmerclub.cloud.user.mapper.UserScoreLockMapper;
import com.tmerclub.cloud.user.model.UserScoreGetLog;
import com.tmerclub.cloud.user.model.UserScoreLock;
import com.tmerclub.cloud.user.service.UserExtensionService;
import com.tmerclub.cloud.user.service.UserScoreGetLogService;
import com.tmerclub.cloud.user.service.UserScoreLockService;
import com.tmerclub.cloud.user.service.UserScoreLogService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 积分锁定信息
 *
 * @author tmerclub
 * @date 2021-05-19 19:54:55
 */
@Service
public class UserScoreLockServiceImpl implements UserScoreLockService {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserScoreLockServiceImpl.class);

    @Autowired
    private UserScoreLockMapper userScoreLockMapper;
    @Autowired
    private UserExtensionMapper userExtensionMapper;
    @Autowired
    private UserScoreGetLogMapper userScoreGetLogMapper;
    @Autowired
    private UserScoreLogService userScoreLogService;
    @Autowired
    private UserScoreGetLogService userScoreGetLogService;
    @Autowired
    private UserExtensionService userExtensionService;
    @Autowired
    private RocketMQTemplate userScoreTemplate;
    @Autowired
    private SegmentManager segmentManager;
    @DubboReference
    private OrderFeignClient orderFeignClient;

    @Override
    public PageVO<UserScoreLock> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> userScoreLockMapper.list());
    }

    @Override
    public UserScoreLock getById(Long id) {
        return userScoreLockMapper.getById(id);
    }

    @Override
    public void save(UserScoreLock userScoreLock) {
        userScoreLockMapper.save(userScoreLock);
    }

    @Override
    public void update(UserScoreLock userScoreLock) {
        userScoreLockMapper.update(userScoreLock);
    }

    @Override
    public void deleteById(Long id) {
        userScoreLockMapper.deleteById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void lock(List<UserScoreLockDTO> userScoreLocks) {
        Long userId = AuthUserContext.get().getUserId();
        Integer orderType = userScoreLocks.get(0).getOrderType();
        List<MongoUserScoreLogBO> saveUserScoreLogList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        // 1.先把已经过期但还没标记的积分全部查出来，标记成已过期，更新数据
        List<UserScoreGetLog> expireUserScoreGetLogList = userScoreGetLogService.listByUserIdAndExpireTimeAndStatus(userId, new Date(), 1);
        if (!CollUtil.isEmpty(expireUserScoreGetLogList)) {
            Date date = new Date();
            for (UserScoreGetLog userScoreGetLog : expireUserScoreGetLogList) {
                userScoreGetLog.setUpdateTime(date);
                userScoreGetLog.setStatus(-1);
            }
            userScoreGetLogService.updateBatchById(expireUserScoreGetLogList);
            // 计算过期的积分总和
            long expireScore = expireUserScoreGetLogList.stream().mapToLong(UserScoreGetLog::getUsableScore).sum();
            // 将过期积分写入积分变动日志记录表
            MongoUserScoreLogBO userScoreLog = new MongoUserScoreLogBO();
            userScoreLog.setLogId(segmentManager.getSegmentId(DistributedIdKey.tmerclub_USER_SCORE_LOG));
            userScoreLog.setUserId(userId);
            userScoreLog.setSource(ScoreLogTypeEnum.EXPIRE.value());
            userScoreLog.setScore(expireScore);
            userScoreLog.setBizId(null);
            userScoreLog.setIoType(0);
            saveUserScoreLogList.add(userScoreLog);
            // 修改 user_extension 表里的积分
            List<Long> longList = new ArrayList<>(Constant.INITIAL_CAPACITY);
            longList.add(userId);
            userExtensionService.updateUserScoreOrGrowth(longList, -expireScore, null);
        }

        // 2.开始锁定积分
        List<UserScoreLock> userScoreLockLogs = Lists.newArrayList();
        Set<Long> orderIdSet = new HashSet<>(Constant.INITIAL_CAPACITY);
        Long useScore = 0L;
        useScore = addUserScoreLockLog(userScoreLocks, userId, userScoreLockLogs, orderIdSet, useScore);
        if (!userScoreLockLogs.isEmpty()) {
            // 批量保存锁定记录
            userScoreLockMapper.saveBatch(userScoreLockLogs);
            // 扣减用户积分
            int updateStatus = userExtensionMapper.lockScoreBySubmitOrder(userId, useScore);
            if (updateStatus < 1) {
                throw new LuckException("积分不足");
            }
            MongoUserScoreLogBO userScoreLog = new MongoUserScoreLogBO();
            userScoreLog.setLogId(segmentManager.getSegmentId(DistributedIdKey.tmerclub_USER_SCORE_LOG));
            userScoreLog.setUserId(userId);
            if (Objects.equals(orderType, OrderType.SCORE.value())) {
                userScoreLog.setSource(ScoreLogTypeEnum.SCORE_PRODUCT_EXCHANGE.value());
                userScoreLog.setBizId(userScoreLocks.get(0).getOrderId());
            } else {
                userScoreLog.setSource(ScoreLogTypeEnum.SCORE_CASH.value());
                userScoreLog.setBizId(null);
            }
            userScoreLog.setScore(useScore);
            userScoreLog.setIoType(0);
            saveUserScoreLogList.add(userScoreLog);

            // 发送消息一个小时后解锁积分(包括哪些订单)
            List<Long> orderIds = new ArrayList<>(orderIdSet);
            UserScoreBO userScoreBo = new UserScoreBO();
            userScoreBo.setUserId(userId);
            userScoreBo.setOrderIds(orderIds);
            SendStatus sendStatus = userScoreTemplate.syncSend(RocketMqConstant.SCORE_UNLOCK_TOPIC, new GenericMessage<>(userScoreBo), RocketMqConstant.TIMEOUT, RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL + 1).getSendStatus();
            if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
                // 消息发不出去就抛异常，发的出去无所谓
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        userScoreLogService.saveBatchToMongodb(saveUserScoreLogList);

    }

    /**
     * 添加用户积分锁定日志
     *
     * @param userScoreLocks    锁定积分日志
     * @param userId            用户id
     * @param userScoreLockLogs 用户积分锁定日志
     * @param orderIds          订单id列表
     * @param useScore          用户积分
     * @return 用户积分
     */
    private Long addUserScoreLockLog(List<UserScoreLockDTO> userScoreLocks, Long userId, List<UserScoreLock> userScoreLockLogs, Set<Long> orderIds, Long useScore) {
        for (UserScoreLockDTO userScoreLockParam : userScoreLocks) {
            if (Objects.nonNull(userScoreLockParam.getScore())) {
                List<UserScoreGetLog> temp = userScoreGetLogService.listByCreateTime(userId, 1);
                long sumTotalUsableScore = temp.stream().mapToLong(UserScoreGetLog::getUsableScore).sum();
                if (sumTotalUsableScore < userScoreLockParam.getScore()) {
                    throw new LuckException(ResponseEnum.EXCEPTION);
                }
                if (!temp.isEmpty()) {
                    Date date = new Date();
                    // 按积分创建时间优先扣减
                    Long usableScore = userScoreLockParam.getScore();
                    List<UserScoreGetLog> userScoreGetLogList;
                    long sumUsableScore;
                    int i = 1;
                    List<Long> scoreGetLogIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
                    do {
                        userScoreGetLogList = userScoreGetLogService.listByCreateTimeAndPage(userId, 1, 0, i * 10);
                        sumUsableScore = userScoreGetLogList.stream().mapToLong(UserScoreGetLog::getUsableScore).sum();
                        if (usableScore > sumUsableScore) {
                            i = i + 1;
                        }
                    } while (usableScore > sumUsableScore);

                    for (UserScoreGetLog userScoreGetLog : userScoreGetLogList) {
                        if (usableScore > 0) {
                            if (usableScore >= userScoreGetLog.getUsableScore()) {
                                userScoreGetLog.setUpdateTime(date);
                                userScoreGetLog.setStatus(0);
                                userScoreGetLogService.update(userScoreGetLog);

                                usableScore = usableScore - userScoreGetLog.getUsableScore();
                                scoreGetLogIds.add(userScoreGetLog.getUserScoreGetLogId());
                            } else {
                                UserScoreGetLog userScoreGetLog2 = new UserScoreGetLog();
                                userScoreGetLog2.setCreateTime(userScoreGetLog.getCreateTime());
                                userScoreGetLog2.setUpdateTime(date);
                                userScoreGetLog2.setUserId(userScoreGetLog.getUserId());
                                userScoreGetLog2.setUsableScore(userScoreGetLog.getUsableScore() - usableScore);
                                userScoreGetLog2.setBizId(userScoreGetLog.getBizId());
                                userScoreGetLog2.setExpireTime(userScoreGetLog.getExpireTime());
                                userScoreGetLog2.setStatus(userScoreGetLog.getStatus());
                                userScoreGetLogService.save(userScoreGetLog2);
                                scoreGetLogIds.add(userScoreGetLog2.getUserScoreGetLogId());

                                userScoreGetLog.setUpdateTime(date);
                                userScoreGetLog.setUsableScore(usableScore);
                                userScoreGetLog.setStatus(0);
                                userScoreGetLogService.update(userScoreGetLog);
                                scoreGetLogIds.add(userScoreGetLog.getUserScoreGetLogId());
                                usableScore = 0L;
                            }
                        }
                    }

                    if (!scoreGetLogIds.isEmpty()) {
                        UserScoreLock userScoreLock = new UserScoreLock();
                        userScoreLock.setUserId(userId);
                        userScoreLock.setScore(userScoreLockParam.getScore());
                        userScoreLock.setOrderId(userScoreLockParam.getOrderId());
                        userScoreLock.setScoreGetLogIds(Json.toJsonString(scoreGetLogIds));
                        useScore += userScoreLock.getScore();
                        userScoreLock.setStatus(0);
                        userScoreLockLogs.add(userScoreLock);
                        orderIds.add(userScoreLockParam.getOrderId());
                    }
                }
            }
        }
        return useScore;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(UserScoreBO userScoreBo) {
        ServerResponseEntity<List<OrderStatusBO>> ordersStatusResponse = orderFeignClient.getOrdersStatus(userScoreBo.getOrderIds());
        if (!ordersStatusResponse.isSuccess()) {
            throw new LuckException(ordersStatusResponse.getMsg());
        }
        List<MongoUserScoreLogBO> saveUserScoreLogList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<OrderStatusBO> orderStatusList = ordersStatusResponse.getData();
        LOGGER.info("积分解锁-订单状态返回{}", orderStatusList);
        for (OrderStatusBO orderStatusBO : orderStatusList) {
            // 该订单没有下单成功，或订单已取消以及取消支付，赶紧解锁积分
            if (orderStatusBO.getStatus() == null || Objects.equals(orderStatusBO.getStatus(), OrderStatus.CLOSE.value()) || BooleanUtil.isTrue(userScoreBo.getFlag())) {
                LOGGER.info("订单下单失败或取消订单，解锁积分{}", orderStatusBO);
                List<Long> userScoreLockIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
                Long useScore = 0L;
                List<UserScoreLock> userScoreLocks = userScoreLockMapper.listUserScoreLocksByOrderId(orderStatusBO.getOrderId());
                List<Long> userScoreGetIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
                for (UserScoreLock userScoreLock : userScoreLocks) {
                    if (Objects.equals(userScoreLock.getStatus(), -1)) {
                        continue;
                    }
                    List<Long> tempUserScoreGetIds = Json.parseArray(userScoreLock.getScoreGetLogIds(), Long[].class);
                    userScoreGetIds.addAll(tempUserScoreGetIds);
                    userScoreLockIds.add(userScoreLock.getId());
                    useScore = useScore + userScoreLock.getScore();
                }
                List<Long> needUserScoreGetIds = new ArrayList<>(userScoreGetIds);
                if (CollectionUtil.isEmpty(needUserScoreGetIds)) {
                    return;
                }
                // todo 批量将用户的积分变成未使用状态，是否要把已经拆开的积分合并？
                userScoreGetLogMapper.batchUpdateUserScoreGetStatus(ScoreGetLogStatusEnum.NORMAL.value(), needUserScoreGetIds);
                userExtensionMapper.updateScoreByUserId(userScoreBo.getUserId(), useScore);
                MongoUserScoreLogBO userScoreLog = new MongoUserScoreLogBO();
                userScoreLog.setLogId(segmentManager.getSegmentId(DistributedIdKey.tmerclub_USER_SCORE_LOG));
                userScoreLog.setUserId(userScoreBo.getUserId());
                if (Objects.equals(orderStatusBO.getOrderType(), OrderType.SCORE.value())) {
                    userScoreLog.setSource(ScoreLogTypeEnum.SCORE_PRODUCT_EXCHANGE_BACK.value());
                    userScoreLog.setBizId(orderStatusBO.getOrderId());
                } else {
                    userScoreLog.setSource(ScoreLogTypeEnum.SCORE_CASH_BACK.value());
                    userScoreLog.setBizId(null);
                }
                userScoreLog.setScore(useScore);
                userScoreLog.setIoType(1);
                saveUserScoreLogList.add(userScoreLog);
                // 将锁定状态标记为已解锁
                if (CollUtil.isNotEmpty(userScoreLockIds)) {
                    int updateStatus = userScoreLockMapper.unLockByIds(-1, userScoreLockIds);
                    if (updateStatus == 0) {
                        throw new LuckException(ResponseEnum.EXCEPTION);
                    }
                }
            }
            if (Objects.equals(orderStatusBO.getStatus(), OrderStatus.PAYED.value())
                    || BooleanUtil.isTrue(userScoreBo.getUnLock())) {
                LOGGER.info("订单支付成功，解锁积分{}", orderStatusBO);
                List<Long> userScoreLockIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
                List<UserScoreLock> userScoreLocks = userScoreLockMapper.listUserScoreLocksByOrderId(orderStatusBO.getOrderId());
                List<Long> userScoreGetIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
                for (UserScoreLock userScoreLock : userScoreLocks) {
                    if (Objects.equals(userScoreLock.getStatus(), 1)) {
                        continue;
                    }
                    List<Long> tempUserScoreGetIds = Json.parseArray(userScoreLock.getScoreGetLogIds(), Long[].class);
                    userScoreGetIds.addAll(tempUserScoreGetIds);
                    userScoreLockIds.add(userScoreLock.getId());
                }
                if (CollectionUtil.isEmpty(userScoreGetIds)) {
                    return;
                }
                if (CollUtil.isNotEmpty(userScoreLockIds)) {
                    int updateStatus = userScoreLockMapper.unLockByIds(1, userScoreLockIds);
                    if (updateStatus == 0) {
                        throw new LuckException(ResponseEnum.EXCEPTION);
                    }
                }
            }
        }
        userScoreLogService.saveBatchToMongodb(saveUserScoreLogList);
    }

    @Override
    public void deleteByUserId(Long userId) {
        userScoreLockMapper.deleteByUserId(userId);
    }
}
