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.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.file.service.impl.FileServiceImpl;
import com.luojia_channel.modules.interact.entity.Follow;
import com.luojia_channel.modules.interact.mapper.FollowMapper;
import com.luojia_channel.modules.interact.service.impl.FollowServiceImpl;
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.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.dto.resp.PostInfoDTO;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.PostMapper;
import com.luojia_channel.modules.post.service.PostService;
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 org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    private final PostMapper postMapper;
    private final FileServiceImpl fileService;
    private final ValidatePostUtil validatePostUtil;
    private final RedisUtil redisUtil;
    private final UserMapper userMapper;
    private final PostSelector postSelector;

    // 匿名用户名与匿名头像
    public static final String ANONYMOUS_NAME = "匿名用户";
    public static final String ANONYMOUS_AVATAR = "";
    private final FollowMapper followMapper;
    private final FollowServiceImpl followService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long savePost(PostSaveDTO postSaveDTO) {
        Long userId = UserContext.getUserId();
        if(userId == null){
            throw new UserException("用户未登录");
        }
        validatePostUtil.validatePost(postSaveDTO);
        Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
        post.setUserId(UserContext.getUserId());
        post.setCreateTime(LocalDateTime.now());
        post.setUpdateTime(LocalDateTime.now());
        post.setCommentCount(0);
        post.setLikeCount(0);
        post.setViewCount(0);
        post.setFavoriteCount(0);
        if(!save(post)){
            throw new PostException("创建帖子失败");
        }
        redisUtil.delete("post:detail:" + postSaveDTO.getId());
        //redisUtil.delete("post:of:user:" + UserContext.getUserId());
        // 非匿名帖子推送给粉丝
        if(post.getStatus().equals(0)){
            List<Follow> follows = followMapper.selectList(Wrappers.lambdaQuery(Follow.class)
                    .eq(Follow::getFollowUserId, userId));
            for(Follow follow : follows){
                Long fansId = follow.getUserId();
                String key = "post:follow_of:" + fansId;
                redisUtil.zAdd(key, post.getId(), System.currentTimeMillis());
            }
            // TODO 消息通知？
        }
        postSelector.calculatePostScore(post);
        redisUtil.zAdd("post:time:"+post.getCategoryId(), post.getId(), System.currentTimeMillis());
        redisUtil.zAdd("post:time:"+"all", post.getId(), System.currentTimeMillis());
        redisUtil.zAdd("post:user:" + userId, post.getId(), System.currentTimeMillis());
        return post.getId();
    }

    @Override
    public String setCover(MultipartFile file) {
        return fileService.uploadFile(file);
    }

    @Override
    public void updatePost(PostSaveDTO postSaveDTO) {
        validatePostUtil.validatePost(postSaveDTO);
        validatePostUtil.validatePostOwnership(postSaveDTO.getId());
        Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
        post.setUserId(UserContext.getUserId());
        post.setUpdateTime(LocalDateTime.now());
        if(!updateById(post)){
            throw new PostException("更新帖子失败");
        }
        redisUtil.delete("post:detail:" + postSaveDTO.getId());
        // redisUtil.delete("post:of:user:" + UserContext.getUserId());
    }

    @Override
    public void deletePost(Long id) {
        validatePostUtil.validatePostOwnership(id);
        Long userId = UserContext.getUserId();
        Post post = postMapper.selectById(id);
        int delete = postMapper.deleteById(id);
        if(delete <= 0){
            throw new PostException("删除帖子失败");
        }
        // redisUtil.delete("post:detail:" + id.toString());
        // redisUtil.delete("post:of:user:" + UserContext.getUserId());
        redisUtil.delete("post:detail:" + id);
        redisUtil.zRemove("post:time:"+post.getCategoryId(), id);
        redisUtil.zRemove("post:hot:"+post.getCategoryId(), id);
        redisUtil.zRemove("post:time:"+"all", id);
        redisUtil.zRemove("post:hot:"+"all", id);
        redisUtil.zRemove("post:user:" + userId, id);
    }

    @Override
    public PostInfoDTO getPostDetail(Long id) {
        // TODO 定时任务更新浏览数或消息队列解耦，懒得写了
        Post oldPost = getById(id);
        oldPost.setViewCount(oldPost.getViewCount() + 1);
        updateById(oldPost);
        return redisUtil.safeGet("post:detail:" + id, PostInfoDTO.class,
                () -> {
                    Post post = getById(id);
                    if(post == null){
                        throw new PostException("帖子不存在或被删除");
                    }
                    postSelector.calculatePostScore(post);
                    PostInfoDTO postInfoDTO = BeanUtil.copyProperties(post, PostInfoDTO.class);
                    User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                            .eq(User::getId, post.getUserId()));
                    postInfoDTO.setUserAvatar(user.getAvatar());
                    postInfoDTO.setUserName(user.getUsername());
                    if (post.getStatus() == 1) { // 匿名帖子
                        postInfoDTO.setUserName(ANONYMOUS_NAME);
                        postInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
                    }
                    postInfoDTO.setIsLike(isLikedPost(post.getId()));
                    return postInfoDTO;
                },
                1, TimeUnit.MINUTES);
    }

    @Override
    public ScrollPageResponse<PostBasicInfoDTO> pagePost(PostPageQueryDTO postPageQueryDTO) {
        // TODO 未来考虑加入多种规则
        /* 旧版分页逻辑
        LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class)
                .orderByDesc(Post::getCreateTime);
        IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
        List<Long> userIds = new ArrayList<>();
        postPage.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(postPage, post -> {
            PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
            User user = userMap.getOrDefault(post.getUserId(), new User());
            postBasicInfoDTO.setUserAvatar(user.getAvatar());
            postBasicInfoDTO.setUserName(user.getUsername());
            if (post.getStatus() == 1) { // 匿名帖子
                postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
                postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
            }
            return postBasicInfoDTO;
        });
         */
        String key;
        if(postPageQueryDTO.getCategoryId() == null || postPageQueryDTO.getCategoryId().equals(0L)){
            key = (postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:") + "all";
        }else {
            key = (postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:") + postPageQueryDTO.getCategoryId();
        }
        return redisUtil.scrollPageQuery(key, PostBasicInfoDTO.class, postPageQueryDTO,
                (postIds) -> {
                    List<Long> userIds = new ArrayList<>();
                    List<Post> posts = postMapper.selectByIdsOrderByField(postIds);
                    posts.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<PostBasicInfoDTO> postBasicInfoDTOS = new ArrayList<>();
                    posts.forEach(post -> {
                        PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
                        User user = userMap.getOrDefault(post.getUserId(), new User());
                        postBasicInfoDTO.setUserAvatar(user.getAvatar());
                        postBasicInfoDTO.setUserName(user.getUsername());
                        postBasicInfoDTO.setIsLike(isLikedPost(post.getId()));

                        String content = post.getContent();
                        if (content != null) {
                            content = content.trim();
                            String summary = content.substring(0, Math.min(content.length(), 15));
                            postBasicInfoDTO.setSummary(summary);
                        } else {
                            postBasicInfoDTO.setSummary("该帖子内容为空哦");
                        }

                        if (post.getStatus() == 1) { // 匿名帖子
                            postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
                            postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
                        }
                        postBasicInfoDTOS.add(postBasicInfoDTO);
                    });
                    return postBasicInfoDTOS;
                });
    }

    @Override
    public ScrollPageResponse<PostBasicInfoDTO> pagePostOfUser(PostPageQueryDTO postPageQueryDTO) {
        Long myUserId = UserContext.getUserId();
        if(myUserId == null){
            throw new UserException("用户未登录");
        }
        Long userId = postPageQueryDTO.getUserId()==null ? myUserId : postPageQueryDTO.getUserId();
        /*
        // 构建包含分页信息的缓存键
        String cacheKey = "post:of:user:" + userId + ":page:" + postPageQueryDTO.getCurrent() + ":size:" + postPageQueryDTO.getSize();
        return redisUtil.safeGet(cacheKey, PageResponse.class,
                () -> {
                    LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class)
                            .eq(Post::getUserId, userId);
                    // 当查询别人时，不显示匿名帖子
                    if(!myUserId.equals(userId)){
                        queryWrapper.eq(Post::getStatus, 0);
                    }
                    queryWrapper.orderByDesc(Post::getCreateTime);
                    IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
                    return PageUtil.convert(postPage, post -> {
                        PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
                        postBasicInfoDTO.setUserAvatar(UserContext.getAvatar());
                        postBasicInfoDTO.setUserName(UserContext.getUsername());
                        if (post.getStatus() == 1) { // 自己的匿名帖子
                            postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
                            postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
                        }
                        return postBasicInfoDTO;
                    });
                },
                60, TimeUnit.MINUTES);
         */
        String key = "post:user:" + userId;
        return redisUtil.scrollPageQuery(key, PostBasicInfoDTO.class, postPageQueryDTO,
                (postIds) -> {
                    List<Long> userIds = new ArrayList<>();
                    List<Post> posts = postMapper.selectByIdsOrderByField(postIds);
                    posts.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<PostBasicInfoDTO> postBasicInfoDTOS = new ArrayList<>();
                    for(Post post : posts){
                        PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
                        User user = userMap.getOrDefault(post.getUserId(), new User());
                        postBasicInfoDTO.setUserAvatar(user.getAvatar());
                        postBasicInfoDTO.setUserName(user.getUsername());
                        postBasicInfoDTO.setIsLike(isLikedPost(post.getId()));

                        String content = post.getContent();
                        if (content != null) {
                            content = content.trim();
                            String summary = content.substring(0, Math.min(content.length(), 15));
                            postBasicInfoDTO.setSummary(summary);
                        } else {
                            postBasicInfoDTO.setSummary("该帖子内容为空哦");
                        }

                        if (post.getStatus() == 1) { // 自己的匿名帖子
                            if(!userId.equals(user.getId())){
                                continue;
                            }
                            postBasicInfoDTO.setUserName(ANONYMOUS_NAME);
                            postBasicInfoDTO.setUserAvatar(ANONYMOUS_AVATAR);
                        }
                        postBasicInfoDTOS.add(postBasicInfoDTO);
                    }
                    return postBasicInfoDTOS;
                });
    }

    private boolean isLikedPost(Long postId){
        Long userId = UserContext.getUserId();
        if(userId == null){
            return false;
        }
        String likeBlogKey = "post:like:" + postId;
        RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeBlogKey, userId));
    }

    @Override
    public void likePost(Long id) {
        Long userId = UserContext.getUserId();
        if(userId == null){
            throw new UserException("用户未登录");
        }
        String likeBlogKey = "post:like:" + id;
        RedisTemplate<String, Object> redisTemplate = redisUtil.getInstance();
        //没点赞
        if(!isLikedPost(id)){
            //数据库点赞记录加一
            boolean success = update().setSql("like_count = like_count + 1").eq("id",id).update();
            if(success){
                redisTemplate.opsForSet().add(likeBlogKey, userId, System.currentTimeMillis());
            }
        }else{
            //数据库点赞记录减一
            boolean success = update().setSql("like_count = like_count - 1").eq("id",id).update();
            if(success){
                redisTemplate.opsForSet().remove(likeBlogKey, userId);
            }
        }
        redisUtil.delete("post:detail:" + id);
    }

}
