package com.webchat.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.webchat.common.bean.APIPageResponseBean;
import com.webchat.common.constants.MessageConstants;
import com.webchat.common.enums.CommonStatusEnum;
import com.webchat.common.enums.ContentConfigEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.ResourceTypeEnum;
import com.webchat.common.exception.AuthException;
import com.webchat.common.helper.SessionHelper;
import com.webchat.common.util.DateUtils;
import com.webchat.common.util.JsonUtil;
import com.webchat.common.util.ThreadPoolExecutorUtil;
import com.webchat.domain.vo.dto.CommentReplyMessageDTO;
import com.webchat.domain.vo.request.CommentSaveVO;
import com.webchat.domain.vo.response.CommentOutResponseVO;
import com.webchat.domain.vo.response.CommentResponseVO;
import com.webchat.domain.vo.response.ResourceBaseInfoVO;
import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
import com.webchat.repository.dao.ICommentDAO;
import com.webchat.repository.entity.CommentEntity;
import com.webchat.service.message.builder.CommentReplyMessageBuilder;
import com.webchat.service.queue.UserMessageQueue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
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.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * @Author: 程序员王七七 https://www.coderutil.com 网站作者
 * @Date: 2021-6-27 0027 15:57
 * @Description: 无描述信息
 */
@Slf4j
@Service
public class CommentService {

    @Autowired
    private ICommentDAO commentDAO;
    @Autowired
    private UserService userService;
    @Autowired
    private RedisService redisService;
//    @Autowired
//    private UserMessageQueue messageQueue;
    @Autowired
    private MomentService momentService;
    @Autowired
    private UserMessageQueue messageQueue;

    private static final int DEFAULT_REPLY_COMMENT_SIZE = 20;
    /***
     * 默认外显评论条数
     */
    private static final int DEFAULT_OUT_COMMENT_SIZE = 3;

    private static final String AI_ROBOT_USER_ID = "CU_dasidh9823e90234032jdo23807urod";

    /***
     * 发布评论
     */
    public Long saveComment(CommentSaveVO commentSaveVO) {
        Long replyId = commentSaveVO.getReplyId();
        Long parentId = null;
        if (replyId != null) {
            CommentEntity replyComment = commentDAO.findAllById(replyId);
            Assert.isTrue(replyComment != null, "回复评论不存在!");
            if(replyComment.getParentId() != null) {
                parentId = replyComment.getParentId();
            } else {
                parentId = replyComment.getId();
            }
        }
        CommentEntity commentEntity = new CommentEntity();
        commentEntity.setResourceId(commentSaveVO.getResourceId());
        commentEntity.setResourceType(commentSaveVO.getResourceType());
        commentEntity.setAuthor(commentSaveVO.getAuthorId());
        String commentContent = commentSaveVO.getContent();
        commentEntity.setContent(commentContent);
        commentEntity.setImages(commentSaveVO.getImages());
        commentEntity.setPubDate(new Date());
        commentEntity.setReplyId(replyId);
        commentEntity.setParentId(parentId);
        commentEntity.setStatus(CommonStatusEnum.PUBLISHED.getStatus());

        Long commentId = commentDAO.save(commentEntity).getId();
        if (replyId == null) {
            // 刷新评论数量
            addCommentCount2Cache(commentSaveVO.getResourceType(), commentSaveVO.getResourceId());
            // 发送评论消息
            asyncSendCommentMessage(true, commentSaveVO);
        } else {
            // TODO 发送评论回复消息
        }
        // 刷新外显评论缓存
        ThreadPoolExecutorUtil.execute(() ->
                refreshOutCommentCache(commentSaveVO.getResourceType(), commentSaveVO.getResourceId()));
        return commentId;
    }

    public void deleteComment(Long commentId, String userId) {
        CommentEntity commentEntity = commentDAO.findAllById(commentId);
        if (!commentEntity.getAuthor().equals(userId)) {
            throw new AuthException("无权限");
        }
        commentEntity.setStatus(CommonStatusEnum.DELETED.getStatus());
        commentEntity.setUpdateBy(userId);
        commentDAO.save(commentEntity);
        if (commentEntity.getReplyId() == null) {
            // 刷新评论数量
            reduceCommentCount2Cache(commentEntity.getResourceType(), commentEntity.getResourceId());
        }
        log.info("{} 删除了评论 {}!", userId, commentId);
        // 刷新外显评论缓存
        ThreadPoolExecutorUtil.execute(() ->
                refreshOutCommentCache(commentEntity.getResourceType(), commentEntity.getResourceId()));
    }

    /**
     * 批量查询外显评论
     * @param resourceType
     * @param resourceIdList
     * @return
     */
    public Map<Long, List<CommentOutResponseVO>> batchGetOutCommentFromCache(String resourceType, List<Long> resourceIdList) {
        Map<Long, List<CommentOutResponseVO>> bulkQueryResultMap = new HashMap<>();
        if (CollectionUtils.isEmpty(resourceIdList)) {
            return bulkQueryResultMap;
        }
        List<String> bulkKey = resourceIdList.stream()
                .map(rid -> RedisKeyEnum.COMMENT_OUT_LIST_CACHE.getKey(resourceType, String.valueOf(rid)))
                .collect(Collectors.toList());
        List<String> bulkQueryResult = redisService.mget(bulkKey);
        for (int i = 0; i < resourceIdList.size(); i++) {
            String val = bulkQueryResult.get(i);
            Long resourceId = resourceIdList.get(i);
            List<CommentOutResponseVO> commentOutResponseVOList;
            if (StringUtils.isBlank(val)) {
                commentOutResponseVOList = refreshOutCommentCache(resourceType, resourceId);
            } else {
                commentOutResponseVOList = JsonUtil.fromJson(val, new TypeReference<List<CommentOutResponseVO>>() { } );
            }
            for (CommentOutResponseVO commentOutResponseVO : commentOutResponseVOList) {
                String author = commentOutResponseVO.getAuthor();
                String replyAuthor = commentOutResponseVO.getReplyAuthor();
                if (StringUtils.isNotBlank(author)) {
                    commentOutResponseVO.setAuthorInfo(userService.getUserBaseInfoByUserId(author));
                }
                if (StringUtils.isNotBlank(replyAuthor)) {
                    commentOutResponseVO.setReplyAuthorInfo(userService.getUserBaseInfoByUserId(replyAuthor));
                }
            }
            bulkQueryResultMap.put(resourceId, commentOutResponseVOList);
        }
        return bulkQueryResultMap;
    }

    /***
     * 查询外显评论
     *
     * @param resourceType
     * @param resourceId
     * @return
     */
    public List<CommentOutResponseVO> getOutCommentFromCache(String resourceType, Long resourceId) {
        String key = RedisKeyEnum.COMMENT_OUT_LIST_CACHE.getKey(resourceType, String.valueOf(resourceId));
        String val = redisService.get(key);
        List<CommentOutResponseVO> commentOutResponseVOList;
        if (StringUtils.isBlank(val)) {
            commentOutResponseVOList = refreshOutCommentCache(resourceType, resourceId);
        } else {
            commentOutResponseVOList = JsonUtil.fromJson(val, new TypeReference<List<CommentOutResponseVO>>() { } );
        }
        for (CommentOutResponseVO commentOutResponseVO : commentOutResponseVOList) {
            String author = commentOutResponseVO.getAuthor();
            String replyAuthor = commentOutResponseVO.getReplyAuthor();
            if (StringUtils.isNotBlank(author)) {
                commentOutResponseVO.setAuthorInfo(userService.getUserBaseInfoByUserId(author));
            }
            if (StringUtils.isNotBlank(replyAuthor)) {
                commentOutResponseVO.setReplyAuthorInfo(userService.getUserBaseInfoByUserId(replyAuthor));
            }
        }
        return commentOutResponseVOList;
    }

    /***
     * 刷新外显评论
     * @param resourceType
     * @param resourceId
     * @return
     */
    private List<CommentOutResponseVO> refreshOutCommentCache(String resourceType, Long resourceId) {
        String key = RedisKeyEnum.COMMENT_OUT_LIST_CACHE.getKey(resourceType, String.valueOf(resourceId));
        List<CommentEntity> commentEntities = commentDAO.getOutSideCommentListLimit(resourceType, resourceId,
                CommonStatusEnum.PUBLISHED.getStatus(), DEFAULT_OUT_COMMENT_SIZE);
        List<CommentOutResponseVO> commentOutResponseVOList = new ArrayList<>();
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(commentEntities)) {
            commentEntities.forEach(c -> {
                CommentOutResponseVO commentOutResponseVO = new CommentOutResponseVO();
                commentOutResponseVO.setId(c.getId());
                String content = c.getContent() == null ? "" : c.getContent();
                if (StringUtils.isNotBlank(c.getImages())) {
                    for (String image : c.getImages().split(",")) {
                        content += " [图片]";
                    }
                }
                commentOutResponseVO.setContent(content);
                commentOutResponseVO.setAuthor(c.getAuthor());
                if (c.getReplyId() != null) {
                    CommentEntity replyComment = commentDAO.findAllById(c.getReplyId());
                    commentOutResponseVO.setReplyAuthor(replyComment.getAuthor());
                }
                commentOutResponseVOList.add(commentOutResponseVO);
            });
        }
        redisService.set(key, JsonUtil.toJsonString(commentOutResponseVOList), RedisKeyEnum.COMMENT_OUT_LIST_CACHE.getExpireTime());
        return commentOutResponseVOList;
    }

    /***
     * 刷新评论数量
     * @param resourceType
     * @param resourceId
     */
    public Long getCommentCountFromCache(Long resourceId, String resourceType) {
        String resourceIdStr = String.valueOf(resourceId);
        String key = RedisKeyEnum.COMMENT_COUNT_CACHE.getKey(resourceType);
        String countCache = redisService.hget(key, resourceIdStr);
        if (StringUtils.isNotBlank(countCache)) {
            return Long.valueOf(countCache);
        }
        return refreshResourceCommentCountCache(resourceId, resourceType);
    }

    public Map<Long, Long> batchGetCommentCountFromCache(List<Long> resourceIdList, String resourceType) {
        Map<Long, Long> resultMap = new HashMap<>();
        String key = RedisKeyEnum.COMMENT_COUNT_CACHE.getKey(resourceType);
        List<String> resourceIdKeys = resourceIdList.stream().map(String::valueOf).collect(Collectors.toList());
        List<String> resourceVals = redisService.hmget(key, resourceIdKeys);
        for (int i = 0; i < resourceIdList.size(); i++) {
            Long id = resourceIdList.get(i);
            String cache = resourceVals.get(i);
            Long count = 0L;
            if (StringUtils.isNotBlank(cache)) {
                count = Long.valueOf(cache);
            } else {
                count = refreshResourceCommentCountCache(id, resourceType);
            }
            resultMap.put(id, count);
        }
        return resultMap;
    }

    /***
     * 刷新评论数量
     * @param resourceType
     * @param resourceId
     */
    private void addCommentCount2Cache(String resourceType, Long resourceId) {
        String resourceIdStr = String.valueOf(resourceId);
        String key = RedisKeyEnum.COMMENT_COUNT_CACHE.getKey(resourceType);
        Long count = redisService.hincrex(key, resourceIdStr);
        if (count == 1) {
            refreshResourceCommentCountCache(resourceId, resourceType);
        }
    }

    /***
     * 刷新评论数量
     * @param resourceType
     * @param resourceId
     */
    private void reduceCommentCount2Cache(String resourceType, Long resourceId) {
        String resourceIdStr = String.valueOf(resourceId);
        String key = RedisKeyEnum.COMMENT_COUNT_CACHE.getKey(resourceType);
        Long count = redisService.hdecrex(key, resourceIdStr);
        if (count == 0) {
            refreshResourceCommentCountCache(resourceId, resourceType);
        }
    }

    private Long refreshResourceCommentCountCache(Long resourceId, String resourceType) {
        String key = RedisKeyEnum.COMMENT_COUNT_CACHE.getKey(resourceType);
        Long count = getResourceCommentCountFromDB(resourceId, resourceType);
        redisService.hset(key, String.valueOf(resourceId), String.valueOf(count), RedisKeyEnum.COMMENT_COUNT_CACHE.getExpireTime());
        return count;
    }

    private Long getResourceCommentCountFromDB(Long resourceId, String resourceType) {
        return commentDAO.countByResourceIdAndResourceTypeAndStatusAndParentIdIsNull(resourceId, resourceType, CommonStatusEnum.PUBLISHED.getStatus());
    }

    /**
     * 按条件查询评论列表
     * @return
     */
    public APIPageResponseBean<List<CommentResponseVO>> queryCommentByCondition(
            String currentUserId,
            String content, Long resourceId, String resourceType, Integer pageNo, Integer pageSize, boolean isHot,
            boolean isNew) {
        // 排序规则
        Sort sort = buildSortCondition(isHot, isNew);
        // 分页条件
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
        // 构建查询条件
        Specification<CommentEntity> specification = buildCommentQuerySpecification(content, resourceId, resourceType);
        // 查询评论
        Page<CommentEntity> commentEntityPage = commentDAO.findAll(specification, pageable);

        List<CommentResponseVO> commentResponseVOList = new ArrayList<>();
        if (commentEntityPage != null && commentEntityPage.getTotalElements() > 0) {
            for (CommentEntity commentEntity : commentEntityPage.getContent()) {
                CommentResponseVO vo = convertCommentEntityToVo(commentEntity);
                vo.setReplyToList(getCommentReplyCommentVoList(vo.getId()));
                commentResponseVOList.add(vo);
            }
        }

        // 设置当前用户操作权限
        this.setCurrentUserCommentPermission(currentUserId, commentResponseVOList);

        return APIPageResponseBean.success(pageNo, pageSize, commentEntityPage.getTotalElements(), commentResponseVOList);
    }

    /***
     * 设置当前用户评论的操作权限
     *
     * @param currentUserId
     * @param commentResponseVOList
     */
    private void setCurrentUserCommentPermission(String currentUserId, List<CommentResponseVO> commentResponseVOList) {
        if (StringUtils.isBlank(currentUserId) || CollectionUtils.isEmpty(commentResponseVOList)) {
            return;
        }
        for (CommentResponseVO commentResponseVO : commentResponseVOList) {
            // 管理员或者作者本人有操作权限
            commentResponseVO.setPermission(currentUserId.equals(commentResponseVO.getAuthor()));
            List<CommentResponseVO> replyToList = commentResponseVO.getReplyToList();
            if (!CollectionUtils.isEmpty(replyToList)) {
                for (CommentResponseVO replyToComment : replyToList) {
                    replyToComment.setPermission(currentUserId.equals(replyToComment.getAuthor()));
                }
            }
        }
    }

    private Sort buildSortCondition(boolean isHot, boolean isNew) {
        List<Sort.Order> orderList = new ArrayList<>();
        orderList.add(new Sort.Order(Sort.Direction.DESC, "topDate"));
        if (isNew) {
            orderList.add(new Sort.Order(Sort.Direction.DESC, "id"));
        }
        if (isHot) {
            orderList.add(new Sort.Order(Sort.Direction.DESC, "likeCount"));
        }
        return Sort.by(orderList);
    }

    private Specification<CommentEntity> buildCommentQuerySpecification(String content, Long resourceId,
                                                                        String resourceType) {

        Specification<CommentEntity> specification = new Specification<CommentEntity>() {
            @Override
            public Predicate toPredicate(Root<CommentEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(criteriaBuilder.equal(root.get("status").as(String.class), "PUBLISHED"));
                predicates.add(criteriaBuilder.isNull(root.get("parentId").as(Long.class)));
                if (resourceId != null) {
                    predicates.add(criteriaBuilder.equal(root.get("resourceId").as(Long.class), resourceId));
                }
                if (StringUtils.isNotBlank(resourceType)) {
                    predicates.add(criteriaBuilder.equal(root.get("resourceType").as(String.class), resourceType));
                }
                if (StringUtils.isNotBlank(content)) {
                    predicates.add(
                            criteriaBuilder.or(
                                    criteriaBuilder.like(root.get("content").as(String.class), "%" + content + "%")));
                }
                Predicate[] pre = new Predicate[predicates.size()];
                criteriaQuery.where(predicates.toArray(pre));
                return criteriaQuery.getRestriction();
            }
        };
        return specification;
    }

    private List<CommentResponseVO> getCommentReplyCommentVoList(Long parentId) {
        List<CommentResponseVO> commentResponseVOList = new ArrayList<>();
        List<CommentEntity> replyComments =
                commentDAO.findAllByParentIdAndStatus(parentId, CommonStatusEnum.PUBLISHED.getStatus(), DEFAULT_REPLY_COMMENT_SIZE);
        if (CollectionUtils.isEmpty(replyComments)) {
            return commentResponseVOList;
        }
        for (CommentEntity commentEntity : replyComments) {
            commentResponseVOList.add(convertCommentEntityToVo(commentEntity));
        }
        return commentResponseVOList;
    }

    private CommentResponseVO convertCommentEntityToVo(CommentEntity commentEntity) {
        CommentResponseVO commentResponseVO = new CommentResponseVO();
        BeanUtils.copyProperties(commentEntity, commentResponseVO);
        commentResponseVO.setPubDateStr(DateUtils.getDate2String(commentEntity.getPubDate()));
        commentResponseVO.setAuthorInfo(userService.getUserInfoByUserId(commentEntity.getAuthor()));
        if (commentEntity.getReplyId() != null) {
            CommentEntity replyEntity = commentDAO.findAllById(commentEntity.getReplyId());
            if (replyEntity != null) {
                CommentResponseVO replyVO = new CommentResponseVO();
                BeanUtils.copyProperties(replyEntity, replyVO);
                replyVO.setPubDateStr(DateUtils.getDate2String(replyEntity.getPubDate()));
                replyVO.setAuthorInfo(userService.getUserInfoByUserId(replyEntity.getAuthor()));
                commentResponseVO.setReply(replyVO);
            }
        }
        return commentResponseVO;
    }

    /***
     * 发送评论消息
     */
    private void asyncSendCommentMessage(boolean isComment, CommentSaveVO commentSaveVO) {
        sendCommentMessage(isComment, commentSaveVO);
    }

    private void sendCommentMessage(boolean isComment, CommentSaveVO commentSaveVO) {
        String fromUserId = commentSaveVO.getAuthorId();
        ResourceBaseInfoVO resourceBaseInfoVO = getResourceBaseInfoVO(commentSaveVO.getResourceId(), commentSaveVO.getResourceType());
        if (resourceBaseInfoVO == null) {
            return;
        }
        UserBaseResponseInfoVO user = userService.getUserInfoByUserId(commentSaveVO.getAuthorId());
        if (user == null) {
            return;
        }
        String commentContent = commentSaveVO.getContent();
        String content = StringUtils.isBlank(commentContent) ? "[图片]" : commentContent.substring(0, commentContent.length() > 20 ? 20 : commentContent.length());
        MessageConstants.TypeEnum typeEnum = isComment ? MessageConstants.TypeEnum.COMMENT : MessageConstants.TypeEnum.REPLY;
        CommentReplyMessageBuilder commentReplyMessageBuilder = new CommentReplyMessageBuilder();
        CommentReplyMessageDTO commentReplyMessage = commentReplyMessageBuilder.build(typeEnum, fromUserId,
                resourceBaseInfoVO.getAuthor(),
                ResourceTypeEnum.valueOf(commentSaveVO.getResourceType()),
                commentSaveVO.getResourceId().toString(),
                content);
        messageQueue.submit(commentReplyMessage);
    }

    private String getCommentContentConfigCodeByResourceType(String resourceType) {
        if (ResourceTypeEnum.MOMENT.getType().equals(resourceType)) {
            return  ContentConfigEnum.MESSAGE_MOMENT_COMMENT.name();
        }
        return null;
    }

    private ResourceBaseInfoVO getResourceBaseInfoVO(Long resourceId, String resourceType) {
        if (ResourceTypeEnum.MOMENT.getType().equals(resourceType)) {
            return momentService.getResourceBaseInfo(resourceId);
        }
        return null;
    }

    /***
     * 处理特殊字符
     * @param content
     * @return
     */
    private String handleSpecialHtmlTag(String content) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        content = content.replaceAll("&lt;br&gt;", "<br>");
        content = content.replaceAll("&lt;b&gt;", "<b>");
        return content;
    }
}
