package com.webchat.service.message;

import com.webchat.common.bean.APIPageResponseBean;
import com.webchat.common.constants.MessageConstants;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.ResourceTypeEnum;
import com.webchat.common.util.JsonUtil;
import com.webchat.controller.client.ChatWebSocket;
import com.webchat.domain.vo.dto.BaseMessageDTO;
import com.webchat.domain.vo.response.MessageResourceVO;
import com.webchat.domain.vo.response.MessageResponseVO;
import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
import com.webchat.domain.vo.response.UserUnReadMessageCountVO;
import com.webchat.domain.vo.response.mess.ChatMessageResponseVO;
import com.webchat.repository.dao.IMessageDAO;
import com.webchat.repository.entity.MessageEntity;
import com.webchat.service.MomentCacheService;
import com.webchat.service.RedisService;
import com.webchat.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.websocket.Session;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Author 程序员七七
 * @webSite https://www.coderutil.com
 * @Date 2022/11/13 00:25
 * @description
 */
@Slf4j
@Service
public class MessageService<T extends BaseMessageDTO> {

    @Autowired
    private IMessageDAO messageDAO;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserService userService;
    @Autowired
    private MomentCacheService momentCacheService;
    @Autowired
    private ChatWebSocket chatWebSocket;

    /**
     * 发送消息
     *
     * @param data
     */
    public void send(T data) {
        MessageEntity message = this.saveMessage2DB(data);
        this.addMessage2Cache(message);
        this.hincrexUnReadCount(message.getToUserId(), message.getCategory());

        ChatMessageResponseVO chatMessageResponseVO = new ChatMessageResponseVO();
        chatMessageResponseVO.setMomentMessage(true);
        Session receiverSession = ChatWebSocket.clients.get(message.getToUserId()).getSession();
        receiverSession.getAsyncRemote().sendText(JsonUtil.toJsonString(chatMessageResponseVO));
    }

    /**
     * 批量读取消息
     *
     * @param userId
     * @param category
     */
    public void batchReadMessage(String userId, String category) {
        messageDAO.updateUserMessageReadState(userId, category);
        this.updateMessageStateCache(userId, category);
    }

    /**
     * 单条读取消息
     *
     * @param userId
     * @param messageId
     */
    public void readMessage(String userId, Long messageId) {
        MessageEntity message = messageDAO.findById(messageId).orElse(null);
        Assert.isTrue(message != null, "消息不存在");
        Assert.isTrue(message.getToUserId().equals(userId), "无读取权限");
        message.setState(true);
        messageDAO.save(message);
        this.hdecrexUnReadCount(userId, message.getCategory());
    }

    /**
     * 获取最新的几条消息
     *
     * @param currentUserId
     * @param category
     * @param size
     * @return
     */
    public List<MessageResponseVO> listMessage(String currentUserId, String category, int size) {
        String key = createUserMessageListCacheKey(currentUserId, category);
        long total = redisService.zSetSize(key);
        if (total == 0) {
            return Collections.emptyList();
        }
        Set<String> messageCacheSet = redisService.zreverseRange(key, 0, size);
        List<MessageEntity> messages = messageCacheSet.stream().map(cache -> {
            return JsonUtil.fromJson(cache, MessageEntity.class);
        }).collect(Collectors.toList());
        return convertMessageResponseVoList(messages);
    }

    /**
     * 消息翻页查询
     *
     * @return
     */
    public APIPageResponseBean<List<MessageResponseVO>> pageMessage(String currentUserId, String category,
                                                                    int pageNo, int pageSize) {
        String key = createUserMessageListCacheKey(currentUserId, category);
        long total = redisService.zSetSize(key);
        if (total == 0) {
            return APIPageResponseBean.success(pageNo, pageSize, 0, Collections.emptyList());
        }
        if (pageNo == 1) {
            batchReadMessage(currentUserId, category);
        }
        Set<String> messageCacheSet = redisService.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
        List<MessageEntity> messages = this.batchGetMessageFromCache(messageCacheSet);
        return APIPageResponseBean.success(pageNo, pageSize, total, convertMessageResponseVoList(messages));
    }

    /**
     * 消息转换
     *
     * @param messageEntities
     * @return
     */
    private List<MessageResponseVO> convertMessageResponseVoList(List<MessageEntity> messageEntities) {
        if (CollectionUtils.isEmpty(messageEntities)) {
            return Collections.emptyList();
        }
        /**
         * 查询消息发送人信息
         */
        Map<String, UserBaseResponseInfoVO> userMap = this.getMessageFromUserInfoMap(messageEntities);
        /**
         * 查询消息中涉及到的资源信息
         */
        Map<String, Map<String, MessageResourceVO>> resourceMap = this.getMessageResourceInfoMap(messageEntities);

        List<MessageResponseVO> messageResponseVOList = new ArrayList<>();

        for (MessageEntity message : messageEntities) {
            MessageResponseVO messageResponse = new MessageResponseVO();
            BeanUtils.copyProperties(message, messageResponse);
            messageResponse.setCategoryName(MessageConstants.getCategoryName(message.getCategory()));
            messageResponse.setType(message.getType());
            messageResponse.setTypeName(MessageConstants.getTypeName(message.getType()));
            messageResponse.setTime(message.getCreateDate().getTime());
            messageResponse.setFromUser(userMap.get(message.getFromUserId()));
            messageResponse.setToUser(userMap.get(message.getToUserId()));

            MessageResourceVO resource = null;
            String resourceType = message.getResourceType();
            String resourceId = message.getResourceId();
            if (ResourceTypeEnum.MOMENT.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.MOMENT.getType()).get(resourceId);
            }
            messageResponse.setResource(resource);
            messageResponseVOList.add(messageResponse);
        }
        return messageResponseVOList;
    }

    /**
     * 批量查询消息发送人用户详情
     *
     * @param messageEntities
     * @return
     */
    private Map<String, UserBaseResponseInfoVO> getMessageFromUserInfoMap(List<MessageEntity> messageEntities) {
        if (CollectionUtils.isEmpty(messageEntities)) {
            return Collections.emptyMap();
        }
        Set<String> userIds = messageEntities.stream().map(MessageEntity::getFromUserId).collect(Collectors.toSet());
        Map<String, UserBaseResponseInfoVO> userMap = userService.batchGetUserInfoFromCache(new ArrayList<>(userIds));
        userMap.put(MessageConstants.FROM_SYSTEM, userService.getSystemUserInfo());
        return userMap;
    }

    /**
     * 查询消息中涉及到的资源信息
     *
     * @param messageEntities
     * @return
     */
    private Map<String, Map<String, MessageResourceVO>> getMessageResourceInfoMap(List<MessageEntity> messageEntities) {
        Map<String, Map<String, MessageResourceVO>> resourceMap = new HashMap<>();
        List<String> momentIdList = new ArrayList<>();
        for (MessageEntity messageEntity : messageEntities) {
            if (StringUtils.isBlank(messageEntity.getResourceType())) {
                continue;
            }
            switch (messageEntity.getResourceType())
            {
                case "MOMENT":
                    momentIdList.add(messageEntity.getResourceId());
                    break;
            }
        }
        if (CollectionUtils.isNotEmpty(momentIdList)) {
            resourceMap.put("BBS", momentCacheService.batchGetMessResource(momentIdList));
        }
        return resourceMap;
    }

    /**
     * 查询用户未读消息数量
     * @param userId
     * @return
     */
    public UserUnReadMessageCountVO getUserUnReadMessageCountFromCache(String userId) {
        UserUnReadMessageCountVO unReadMessageCountVO = new UserUnReadMessageCountVO();
        unReadMessageCountVO.setUnReadLikeMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.LIKE_COLLECT));
        unReadMessageCountVO.setUnReadCommentMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.COMMENT_REPLY));
        unReadMessageCountVO.setUnReadFocusMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.FOCUS));
        unReadMessageCountVO.setUnReadSystemMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.SYSTEM));
        return unReadMessageCountVO;
    }

    private Long getUnReadMessageCountFromCache(String userId, MessageConstants.CategoryEnum categoryEnum) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(categoryEnum.getCategory());
        String cache = redisService.hget(unReadKey, userId);
        return StringUtils.isNotBlank(cache) ? Long.valueOf(cache) : 0L;
    }

    /**
     * 消息入库
     *
     * @param data
     * @return
     */
    private MessageEntity saveMessage2DB(T data) {
        MessageEntity message = new MessageEntity();
        BeanUtils.copyProperties(data, message);
        message.setState(false);
        message.setCreateDate(new Date());
        return messageDAO.save(message);
    }

    /**
     * 消息加入redis缓存
     *
     * @param message
     */
    private void addMessage2Cache(MessageEntity message) {
        String messageListKey = this.createUserMessageListCacheKey(message.getToUserId(), message.getCategory());
        String messageDetailKey = this.createUserMessageDetailCacheKey(message.getId());
        redisService.set(messageDetailKey, JsonUtil.toJsonString(message), RedisKeyEnum.MESSAGE_DETAIL_CACHE.getExpireTime());
        redisService.zadd(messageListKey, message.getId().toString(), message.getCreateDate().getTime(), RedisKeyEnum.MESSAGE_LIST_CACHE.getExpireTime());
    }

    private List<MessageEntity> batchGetMessageFromCache(Set<String> messageCacheSet) {
        if (CollectionUtils.isEmpty(messageCacheSet)) {
            return Collections.emptyList();
        }
        List<String> messageKeys = messageCacheSet.stream().map(mid ->
                createUserMessageDetailCacheKey(Long.valueOf(mid))).collect(Collectors.toList());
        List<String> caches = redisService.mget(messageKeys);
        List<MessageEntity> messageEntities = new ArrayList<>();
        for (String cache : caches) {
            if (StringUtils.isBlank(cache)) {
                continue;
            }
            messageEntities.add(JsonUtil.fromJson(cache, MessageEntity.class));
        }
        return messageEntities;
    }

    /**
     * 未读消息数原子+1
     *
     * @param category
     * @param toUserId
     */
    private void hincrexUnReadCount(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisService.hincrex(unReadKey, toUserId);
    }

    /**
     * 未读消息数原子-1
     *
     * @param category
     * @param toUserId
     */
    private void hdecrexUnReadCount(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisService.hdecrex(unReadKey, toUserId);
    }

    /**
     * 更新消息为已读状态
     *
     * @param toUserId
     * @param category
     */
    private void updateMessageStateCache(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisService.hset(unReadKey, toUserId, "0");
    }

    /**
     * 消息列表缓存KEY
     *
     * @param userId
     * @param category
     * @return
     */
    private String createUserMessageListCacheKey(String userId, String category) {
        return RedisKeyEnum.MESSAGE_LIST_CACHE.getKey(category, userId);
    }

    /**
     * 消息详情缓存KEY
     *
     * @param messageId
     * @return
     */
    private String createUserMessageDetailCacheKey(Long messageId) {
        return RedisKeyEnum.MESSAGE_DETAIL_CACHE.getKey(messageId.toString());
    }

    /**
     * 消息列表缓存KEY
     *
     * @param category
     * @return
     */
    private String createUserUnReadMessageCountCacheKey(String category) {
        return RedisKeyEnum.MESSAGE_UNREAD_COUNT_CACHE.getKey(category);
    }
}
