package com.luojia_channel.modules.post.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.domain.page.ScrollPageResponse;
import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.PageUtil;
import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.message.mq.domain.NotificationMessage;
import com.luojia_channel.modules.post.algorithm.PostSelector;
import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO;
import com.luojia_channel.modules.post.dto.req.CommentSaveDTO;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.entity.Comment;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.CommentMapper;
import com.luojia_channel.modules.post.mapper.PostMapper;
import com.luojia_channel.modules.post.mq.producer.NotificationProducer;
import com.luojia_channel.modules.post.service.CommentService;
import com.luojia_channel.modules.post.utils.ValidatePostUtil;
import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {

    private final ValidatePostUtil validatePostUtil;
    private final CommentMapper commentMapper;
    private final UserMapper userMapper;
    private final NotificationProducer notificationProducer;
    private final RedisUtil redisUtil;
    private final PostMapper postMapper;
    private final PostSelector postSelector;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveComment(CommentSaveDTO commentSaveDTO) {
        Long userId = UserContext.getUserId();
        if (userId == null) {
            throw new UserException("用户未登录");
        }
        validatePostUtil.validateComment(commentSaveDTO);

        Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class);
        comment.setUserId(userId);
        comment.setCreateTime(LocalDateTime.now());
        comment.setUpdateTime(LocalDateTime.now());
        comment.setReplyCount(0L);
        comment.setLikeCount(0L);

        if (!save(comment)) {
            throw new PostException("创建评论失败");
        }

        // 保存成功后，若为根级评论，设置 topId 为评论 id
        if (comment.getParentCommentId() == null) {
            comment.setTopId(comment.getId());
            comment.setParentCommentId(0L);
            comment.setReplyUserId(0L);
            if (!updateById(comment)) {
                throw new PostException("更新根级评论 topId 失败");
            }
            // 同时，更新帖子的zset列表
            String key = "post:comment_by_time:" + comment.getPostId();
            redisUtil.zAdd(key, comment.getId(), System.currentTimeMillis());
        }

        // 帖子的回复数加一
        Long postId = comment.getPostId();
        Post post = postMapper.selectById(postId);
        if (post == null) {
            throw new PostException("回复的帖子不存在");
        }
        post.setCommentCount(post.getCommentCount() + 1);
        if(postMapper.updateById(post) <= 0){
            throw new PostException("回复帖子失败");
        }

        Long receiveUserId = post.getUserId();
        Long parentCommentId = comment.getParentCommentId();
        // 消息通知,回复帖子
        if (!userId.equals(receiveUserId) && parentCommentId == 0) {
            String content = String.format("%s 回复了你的帖子: %s",
                    UserContext.getUsername(),
                    StringUtils.abbreviate(commentSaveDTO.getContent(), 20));
            NotificationMessage notificationMessage = NotificationMessage.builder()
                    .senderId(userId)
                    .senderName(UserContext.getUsername())
                    .senderAvatar(UserContext.getAvatar())
                    .receiverId(receiveUserId)
                    .content(content)
                    .messageType(2)
                    .build();
            notificationProducer.sendMessage(notificationMessage);
        }

        if (parentCommentId != 0) {
            // 是回复的评论
            Comment parentComment = commentMapper.selectById(parentCommentId);
            if (parentComment == null) {
                throw new PostException("父评论不存在");
            }
            // 设置顶级评论id
            comment.setTopId(parentComment.getTopId());
            comment.setReplyUserId(parentComment.getUserId());
            updateById(comment);

            // 更新评论的zset回复列表
            String key = "comment:reply_by_time:" + comment.getTopId();

            redisUtil.zAdd(key, comment.getId(), System.currentTimeMillis());

            // 更新顶级评论回复数
            LambdaUpdateWrapper<Comment> updateWrapper = Wrappers.lambdaUpdate(Comment.class)
                    .eq(Comment::getId, comment.getTopId())
                    .setSql("reply_count = reply_count + 1");
            int update = commentMapper.update(null, updateWrapper);
            if (update <= 0) {
                throw new PostException("回复顶级评论失败");
            }

            // 更新父评论回复数
            if(!parentComment.getTopId().equals(parentComment.getId())){
                parentComment.setReplyCount(parentComment.getReplyCount()+1);
                updateById(parentComment);
                postSelector.calculateCommentScore(parentComment);
            }
            postSelector.updateCommentScore(comment.getTopId());

            // 消息通知，回复评论
            if(!userId.equals(comment.getReplyUserId())){
                String content = String.format("%s 回复了你的评论: %s",
                        UserContext.getUsername(),
                        StringUtils.abbreviate(commentSaveDTO.getContent(), 20));
                NotificationMessage notificationMessage = NotificationMessage.builder()
                        .senderId(userId)
                        .senderName(UserContext.getUsername())
                        .senderAvatar(UserContext.getAvatar())
                        .receiverId(parentComment.getUserId())
                        .content(content)
                        .messageType(2)
                        .build();
                notificationProducer.sendMessage(notificationMessage);
            }
        }
        postSelector.calculateCommentScore(comment);
        postSelector.calculatePostScore(post);
        redisUtil.delete("post:detail:" + comment.getPostId());
        return comment.getId();
    }

    @Override
    public void updateComment(CommentSaveDTO commentSaveDTO) {
        validatePostUtil.validateComment(commentSaveDTO);
        validatePostUtil.validateCommentOwnership(commentSaveDTO.getId());
        Comment comment = BeanUtil.copyProperties(commentSaveDTO, Comment.class);
        comment.setUserId(UserContext.getUserId());
        comment.setUpdateTime(LocalDateTime.now());
        if(!updateById(comment)){
            throw new PostException("更新评论失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteComment(Long id) {
        validatePostUtil.validateCommentOwnership(id);
        Comment comment = commentMapper.selectById(id);
        if(comment.getId().equals(comment.getTopId())) {
            LambdaQueryWrapper<Comment> queryWrapper = Wrappers.lambdaQuery(Comment.class)
                    .eq(Comment::getTopId, id);
            int delete = commentMapper.delete(queryWrapper);
            if(delete <= 0) {
                throw new PostException("删除根评论失败");
            }
            Post post = postMapper.selectById(comment.getPostId());
            post.setCommentCount(post.getCommentCount() - delete);
            postMapper.updateById(post);

            redisUtil.zRemove("post:comment_by_time:" + comment.getPostId(), comment.getId());
            redisUtil.zRemove("post:comment_by_hot:" + comment.getPostId(), comment.getId());
            redisUtil.delete("comment:reply_by_time:" + comment.getTopId());
            redisUtil.delete("comment:reply_by_hot:" + comment.getTopId());
        }else{
            int delete = commentMapper.deleteById(id);
            if(delete <= 0){
                throw new PostException("删除评论失败");
            }
            Post post = postMapper.selectById(comment.getPostId());
            post.setCommentCount(post.getCommentCount() - delete);
            postMapper.updateById(post);

            Comment parentComment = commentMapper.selectById(comment.getParentCommentId());
            Comment topComment = commentMapper.selectById(comment.getTopId());
            parentComment.setReplyCount(parentComment.getReplyCount() - delete);
            topComment.setReplyCount(topComment.getReplyCount() - delete);
            updateById(parentComment);
            updateById(topComment);

            redisUtil.zRemove("comment:reply_by_time:" + comment.getTopId(), comment.getId());
            redisUtil.zRemove("comment:reply_by_hot:" + comment.getTopId(), comment.getId());
        }

        // TODO 如果根评论删除，那么其他评论怎么办，目前做法是删除其下所有的子评论
    }

    // 分页查询一系列根评论
    @Override
    public ScrollPageResponse<CommentInfoDTO> getCommentsByPostId(CommentPageQueryDTO commentPageQueryDTO) {
        if(commentPageQueryDTO.getPostId() == null || commentPageQueryDTO.getPostId() < 0){
            throw new PostException("帖子id不合法");
        }
        /*
        LambdaQueryWrapper<Comment> queryWrapper = Wrappers.lambdaQuery(Comment.class)
                .eq(Comment::getPostId, commentPageQueryDTO.getPostId())
                .eq(Comment::getParentCommentId, 0L)
                .orderByDesc(Comment::getCreateTime);
        return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper);
         */
        String condition = commentPageQueryDTO.getType().equals(0) ? "time:" : "hot:";
        String key = "post:comment_by_" + condition + commentPageQueryDTO.getPostId();
        return redisUtil.scrollPageQuery(key, CommentInfoDTO.class, commentPageQueryDTO,
                (commentIds) -> {
                    List<Comment> comments = commentMapper.selectByIdsOrderByField(commentIds);
                    Set<Long> userIds = new HashSet<>();
                    comments.forEach(comment -> userIds.add(comment.getUserId()));
                    List<User> users = userMapper.selectBatchIds(userIds);
                    Map<Long, User> userMap = users.stream()
                            .collect(Collectors.toMap(User::getId, user -> user));
                    List<CommentInfoDTO> commentInfoDTOS = new ArrayList<>();
                    for(Comment comment : comments){
                        CommentInfoDTO commentInfoDTO = BeanUtil.copyProperties(comment, CommentInfoDTO.class);
                        User user = userMap.getOrDefault(comment.getUserId(), new User());
                        commentInfoDTO.setUserAvatar(user.getAvatar());
                        commentInfoDTO.setUserName(user.getUsername());
                        commentInfoDTO.setIsLike(isLikedComment(comment.getId()));
                        commentInfoDTOS.add(commentInfoDTO);
                    }
                    return commentInfoDTOS;
                });
    }

    @Override
    public ScrollPageResponse<CommentInfoDTO> getReplyById(CommentPageQueryDTO commentPageQueryDTO) {
        if(commentPageQueryDTO.getParentCommentId() == null || commentPageQueryDTO.getParentCommentId() < 0){
            throw new PostException("评论id不合法");
        }
        /*
        LambdaQueryWrapper<Comment> queryWrapper = Wrappers.lambdaQuery(Comment.class)
                .eq(Comment::getTopId, commentPageQueryDTO.getCommentId())
                .orderByDesc(Comment::getCreateTime);
        return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper);
         */
        String condition = commentPageQueryDTO.getType().equals(0) ? "time:" : "hot:";
        String key = "comment:reply_by_" + condition + commentPageQueryDTO.getParentCommentId();
        return redisUtil.scrollPageQuery(key, CommentInfoDTO.class, commentPageQueryDTO,
                (commentIds) -> {
                    List<Comment> comments = commentMapper.selectByIdsOrderByField(commentIds);
                    Set<Long> userIds = new HashSet<>();
                    comments.forEach(comment -> userIds.add(comment.getUserId()));
                    for(Comment comment : comments){
                        userIds.add(comment.getUserId());
                        userIds.add(comment.getReplyUserId());
                    }
                    List<User> users = userMapper.selectBatchIds(userIds);
                    Map<Long, User> userMap = users.stream()
                            .collect(Collectors.toMap(User::getId, user -> user));
                    List<CommentInfoDTO> commentInfoDTOS = new ArrayList<>();
                    for(Comment comment : comments){
                        CommentInfoDTO commentInfoDTO = BeanUtil.copyProperties(comment, CommentInfoDTO.class);
                        User user = userMap.getOrDefault(comment.getUserId(), new User());
                        commentInfoDTO.setUserAvatar(user.getAvatar());
                        commentInfoDTO.setUserName(user.getUsername());
                        commentInfoDTO.setIsLike(isLikedComment(comment.getId()));
                        User replyUser = userMap.getOrDefault(comment.getReplyUserId(), new User());
                        commentInfoDTO.setReplyUserName(replyUser.getUsername());
                        commentInfoDTOS.add(commentInfoDTO);
                    }
                    return commentInfoDTOS;
                });
    }

    @Override
    public void likeComment(Long id) {
        Long userId = UserContext.getUserId();
        String likeCommentKey = "comment:like:" + id;
        RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
        // 检查是否未点赞
        if (!isLikedComment(id)) {
            // 数据库点赞记录加一
            boolean success = updateCommentLikeCount(id, 1);
            if (success) {
                redisTemplate.opsForSet().add(likeCommentKey, userId, System.currentTimeMillis());
            }
        } else {
            // 数据库点赞记录减一
            boolean success = updateCommentLikeCount(id, -1);
            if (success) {
                redisTemplate.opsForSet().remove(likeCommentKey, userId);
            }
        }
    }

    private boolean isLikedComment(Long commentId) {
        Long userId = UserContext.getUserId();
        String likeCommentKey = "comment:like:" + commentId;
        RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeCommentKey, userId));
    }

    private boolean updateCommentLikeCount(Long commentId, int increment) {
        LambdaUpdateWrapper<Comment> updateWrapper = Wrappers.lambdaUpdate(Comment.class)
                .setSql("like_count = like_count + " + increment)
                .eq(Comment::getId, commentId);
        return commentMapper.update(null, updateWrapper) > 0;
    }

    /* 适用于旧版普通分页的转换方法
    private PageResponse<CommentInfoDTO> getCommentInfoDTOPageResponse(CommentPageQueryDTO commentPageQueryDTO, LambdaQueryWrapper<Comment> queryWrapper) {
        IPage<Comment> commentPage = commentMapper.selectPage(PageUtil.convert(commentPageQueryDTO), queryWrapper);
        List<Long> userIds = new ArrayList<>();
        commentPage.getRecords().forEach(comment -> userIds.add(comment.getUserId()));
        List<User> users = userMapper.selectBatchIds(userIds);
        Map<Long, User> userMap = users.stream()
                .collect(Collectors.toMap(User::getId, user -> user));
        return PageUtil.convert(commentPage, (comment) -> {
            CommentInfoDTO commentInfoDTO = BeanUtil.copyProperties(comment, CommentInfoDTO.class);
            User user = userMap.getOrDefault(comment.getUserId(), new User());
            commentInfoDTO.setUserAvatar(user.getAvatar());
            commentInfoDTO.setUserName(user.getUsername());
            commentInfoDTO.setIsLike(isLikedComment(comment.getId()));
            return commentInfoDTO;
        });
    }
     */

    private List<CommentInfoDTO> convertToDTO(List<Comment> commentList) {
        List<CommentInfoDTO> dtos = new ArrayList<>();
        for (Comment comment : commentList) {
            CommentInfoDTO dto = new CommentInfoDTO();
            BeanUtils.copyProperties(comment, dto);
            dtos.add(dto);
        }
        return dtos;
    }

    // 根据评论创建嵌套评论,目前没用到
    private List<CommentInfoDTO> buildNestedCommentList(List<Comment> comments) {
        Map<Long, CommentInfoDTO> map = new HashMap<>();
        List<CommentInfoDTO> rootComments = new ArrayList<>();
        for (Comment comment : comments) {
            CommentInfoDTO dto = new CommentInfoDTO();
            BeanUtils.copyProperties(comment, dto);
            map.put(comment.getId(), dto);
            if (comment.getParentCommentId() == null) {
                rootComments.add(dto);
            } else {
                CommentInfoDTO parentDto = map.get(comment.getParentCommentId());
                if (parentDto != null && parentDto.getCommentInfoDTOList() == null) {
                    parentDto.setCommentInfoDTOList(new ArrayList<>());
                }
                if (parentDto != null) {
                    parentDto.getCommentInfoDTOList().add(dto);
                }
            }
        }
        return rootComments;
    }
}
