package com.apobates.forum.core.impl.service;

import com.apobates.forum.attention.core.decorater.ForumEncoder;
import com.apobates.forum.attention.core.decorater.posts.ForumPostsDecorator;
import com.apobates.forum.core.ImageIOMeta;
import com.apobates.forum.core.dao.PostsDao;
import com.apobates.forum.core.dao.TopicDao;
import com.apobates.forum.core.entity.Posts;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.core.entity.proxy.PostsReplica;
import com.apobates.forum.core.impl.ActionDescriptor;
import com.apobates.forum.core.impl.event.PostsPublishEvent;
import com.apobates.forum.core.service.PostsService;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.member.service.MemberService;
import com.apobates.forum.utils.DateTimeUtils;
import com.apobates.forum.utils.lang.ReplicableException;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.github.davidmarquis.redisq.producer.MessageProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 *
 * @author xiaofanku
 * @since 20200512
 */
@Service
public class PostsServiceImpl implements PostsService{
    @Autowired
    private PostsDao postsDao;
    @Autowired
    private TopicDao topicDao;
    @Autowired
    private MemberService memberService;
    @Autowired @Qualifier("postsPublishProducer")
    private MessageProducer<PostsPublishEvent> postsPublishProducer;
    private final static Logger logger = LoggerFactory.getLogger(PostsServiceImpl.class);
    
    @ActionDescriptor(action=ForumActionEnum.POSTS_REPLY, isBool=false)
    @Override
    public long create(int boardGroupId, long boardId, long topicId, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) { //ASP-P1
        //编码回复内容
        String encodePostsContent = new ForumEncoder(content)
                .encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                .relaxedHtmlTag()
                .parseEmoji()
                .getContent();
        //-------------------------------------------------------------------------
        //楼层交由事务去填充
        try{
            Posts posts = new Posts(encodePostsContent, culpritor.getMemberId(), culpritor.getMemberNickname(), topicId, boardGroupId, boardId, -1, culpritor.getIpAddr());
            postsDao.save(posts);
            if (posts.getId() > 0) {
                postsPublishProducer.create(new PostsPublishEvent(posts, culpritor.getUserAgent())).submit();
                return posts.getId();
            }
        }catch(Exception e){
            if(logger.isDebugEnabled()){
                logger.debug("话题回复创建失败", e);
            }
        }
        return 0L;
    }
    
    @ActionDescriptor(action=ForumActionEnum.POSTS_EDIT)
    @Override
    public Optional<Boolean> edit(long id, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) { //ASP-P2
        //编码回复内容
        String encodePostsContent = new ForumEncoder(content)
                .encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                .relaxedHtmlTag()
                .parseEmoji()
                .getContent();
        //-------------------------------------------------------------------------
        try{
            //加工内容
            return postsDao.edit(id, encodePostsContent, culpritor.getMemberId(), culpritor.getMemberNickname());
        }catch(Exception e){
            if(logger.isDebugEnabled()){
                logger.debug("回复编辑失败", e);
            }
        }
        return Optional.empty();
    }
    
    @Override
    public Page<PostsReplica> getAll(long topicId, Pageable pageable) {
        Page<Posts> rs = postsDao.findAllByTopic(topicId, pageable);
        return copyPosts(rs, null);
    }
    //20200529
    @Override
    public Page<PostsReplica> getAllReply(long topicId, Pageable pageable, ImageIOMeta imageIO, boolean lazyLoad, String imageScale) {
        Page<Posts> rs = postsDao.findAllReplyByTopic(topicId, pageable);
        Consumer<PostsReplica> pc = (PostsReplica pr)->{
            //解码图片连接编码
            String postsBody = ForumPostsDecorator.init(pr)
                                .block("*")
                                .decorateUploadImageUseScale(lazyLoad, imageScale)
                                .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                                .getContent();
            pr.setContent(postsBody);
        };
        return copyPosts(rs, pc);
    }
    //20200529
    @Override
    public Page<PostsReplica> getAllReplyFilterAuthor(long topicId, long filterMemeberId, Pageable pageable, ImageIOMeta imageIO, boolean lazyLoad, String imageScale) {
        if (filterMemeberId < 1) {
            return getAllReply(topicId, pageable, imageIO, lazyLoad, imageScale);
        }
        Page<Posts> rs = postsDao.findAllReplyByTopic(topicId, filterMemeberId, pageable);
        Consumer<PostsReplica> pc = (PostsReplica pr)->{
            //解码图片连接编码
            String postsBody = ForumPostsDecorator.init(pr)
                                .block("*")
                                .decorateUploadImageUseScale(lazyLoad, imageScale)
                                .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                                .getContent();
            pr.setContent(postsBody);
        };
        return copyPosts(rs, pc);
    }
    
    @Override
    public Optional<Posts> getPostsContentForQuote(long id, ImageIOMeta imageIO, boolean lazyLoad, String imageScale)throws IllegalArgumentException {
        if (id <= 0) {
            return Optional.empty();//("参数不合法或不被接受");
        }
        Posts posts = postsDao.findOne(id).orElseThrow(()->new IllegalArgumentException("话题回复不存在"));
        String postsBody = ForumPostsDecorator.init(posts)
                            .block("作者被禁止发言或内容自动屏蔽")
                            .decorateUploadImageUseScale(lazyLoad, imageScale)
                            .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                            .modify("回复最近由管理人员进行过编辑")
                            .getContent();
        posts.setContent(postsBody);
        return Optional.of(posts);
    }
    
    @Override
    public Optional<Posts> getPostsContentForEdit(long id, ImageIOMeta imageIO)throws IllegalArgumentException {
        if (id <= 0) {
            return Optional.empty();//("参数不合法或不被接受");
        }
        Posts posts = postsDao.findOne(id).orElseThrow(()->new IllegalArgumentException("话题回复不存在"));
        String postsBody = ForumPostsDecorator.init(posts)
                            .decorateUploadImageEmptyScale(false)
                            .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                            .getContent();
        posts.setContent(postsBody);
        return Optional.of(posts);
    }
    
    @Override
    public Optional<PostsReplica> getPostsContentForEditByTopic(long topicId, ImageIOMeta imageIO)throws IllegalArgumentException,ReplicableException {
        if (topicId <= 0) {
            return Optional.empty();//("参数不合法或不被接受");
        }
        PostsReplica posts = getTopicContent(topicId).orElseThrow(()->new IllegalArgumentException("话题内容不存在"));
        String postsBody = ForumPostsDecorator.init(posts)
                            .decorateUploadImageEmptyScale(false)
                            .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                            .getContent();
        posts.setContent(postsBody);
        return Optional.of(posts);
    }
    
    @Override
    public Stream<Posts> getRecentForRSS(long topicId, int size, ImageIOMeta imageIO, boolean lazyLoad) {
        Consumer<Posts> action = posts->{
            //解码图片连接编码
            String postsBody = ForumPostsDecorator.init(posts)
                                .block("作者被禁止发言或内容自动屏蔽")
                                .decorateUploadImageEmptyScale(lazyLoad)
                                .apply(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName())
                                .modify("回复最近由管理人员进行过编辑")
                                .getContent();
            posts.setContent(postsBody);
        };
        return postsDao.findAllByTopic(topicId, size).peek(action);
    }
    
    @ActionDescriptor(action=ForumActionEnum.POSTS_DEL)
    @Override
    public Optional<Boolean> remove(long id, ActionEventCulpritor culpritor) { //ASP-P3
        return postsDao.editStatus(id, false);
    }
    
    @Override
    public Page<Posts> getAllReply(long topicId, Pageable pageable) {
        return postsDao.findAllReplyByTopic(topicId, pageable);
    }
    
    @Override
    public Stream<Posts> getTopicLordContent(List<Long> topicIdList) {
        if (null == topicIdList || topicIdList.isEmpty()) {
            return Stream.empty();
        }
        return postsDao.findOneByOneFloor(topicIdList);
    }
    
    @Override
    public Stream<Posts> getRecentByUnixStamp(long topicId, int prevUnixStamp) {
        LocalDateTime prevDate = DateTimeUtils.getDateTimeByUnixTimestamp(prevUnixStamp);
        return postsDao.findAllRecentByTopic(topicId, prevDate);
    }
    
    @Override
    public Stream<Posts> getRecentForTopicIgnoreStatus(long topicId, int size) {
        return postsDao.findAllRecentByTopicIgnoreStatus(topicId, size);
    }
    
    @Override
    public Optional<Posts> get(long id) {
        if (id <= 0) {
            return Optional.empty(); //("参数不合法或不被接受");
        }
        return postsDao.findOne(id);
    }
    
    @Override
    public Optional<PostsReplica> getTopicContent(final long topicId){ //DEP03
        CompletableFuture<Optional<Posts>> r = CompletableFuture.supplyAsync(()->postsDao.findOneByTopicId(topicId)).completeOnTimeout(Optional.empty(), 1, TimeUnit.SECONDS);
        CompletableFuture<Topic> t = CompletableFuture.supplyAsync(()->topicDao.findOneById(topicId)).completeOnTimeout(null, 1, TimeUnit.SECONDS);
        return r.thenCombine(t, (opPosts, topic)->{
            Optional<PostsReplica> oppo = opPosts.map(PostsReplica.copyPosts);
            oppo.ifPresent(prObj->prObj.setTopic(topic));
            return oppo;
        }).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public Optional<PostsReplica> get(final long id, final long topicId){ //DEP01
        return getAsync(id, topicId).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public long maxFloor(long topicId) {
        return postsDao.maxFloor(topicId);
    }
    
    @Override
    public long getTopicPageSize(long topicId, int pageSize) {
        long mf = maxFloor(topicId);
        if (mf <= pageSize) {
            return 1;
        }
        long page = mf / pageSize;
        if (mf % pageSize != 0) {
            page += 1;
        }
        return page;
    }
    
    @Override
    public long countRepliesSize(long topicId, long replyer) {
        return postsDao.countRepliesSize(topicId, replyer);
    }
    
    @Override
    public long countRepliesSize(long topicId) {
        return postsDao.countTopicReply(topicId);
    }
    
    private Page<PostsReplica> copyPosts(Page<Posts> rs, final Consumer<PostsReplica> postsConsumer){
        List<Posts> posts = rs.getResult().collect(Collectors.toList());
        if (null == posts || posts.isEmpty()) {
            return Page.empty();
        }
        //AVOID require memberService
        List<Long> memberIdList = posts.stream().map(Posts::getMemberId).collect(Collectors.toList());
        Map<Long, Member> memberSet = memberService.queryCollection(memberIdList).collect(Collectors.toMap(Member::getId, Function.identity()));
        BiFunction<Posts, Member, PostsReplica> bi = (p, m)->{
            Optional<PostsReplica> pr = Optional.ofNullable(p).map(PostsReplica.copyPosts);
            pr.ifPresent(pc->{
                Member safeIns = new Member();
                safeIns.setId(m.getId());
                safeIns.setNickname(m.getNickname());
                safeIns.setSignature(m.getSignature());
                safeIns.setAvatarURI(m.getAvatarURI());
                safeIns.setRegisteDateTime(m.getRegisteDateTime());
                safeIns.setMgroup(m.getMgroup());
                safeIns.setMrole(m.getMrole());
                safeIns.setStatus(m.getStatus());
                //
                pc.setMember(safeIns);
                if(null != postsConsumer){
                    postsConsumer.accept(pc);
                }
            });
            return pr.orElse(null);
        };
        Stream<PostsReplica> result = posts.parallelStream().map(pobj->bi.apply(pobj, memberSet.getOrDefault(pobj.getMemberId(), Member.guestMember()))).filter(Objects::nonNull);
        return new Page<PostsReplica>() {
            @Override
            public long getTotalElements() {
                return rs.getTotalElements();
            }
            
            @Override
            public Stream<PostsReplica> getResult() {
                return result;
            }
        };
    }
    private CompletableFuture<Posts> getAsync(final long id){
        return CompletableFuture.supplyAsync(()->postsDao.findOneById(id));
    }
    private CompletableFuture<Optional<PostsReplica>> getAsync(final long id, final long topicId){
        CompletableFuture<Topic> tc = CompletableFuture.supplyAsync(()->topicDao.findOneById(topicId)).completeOnTimeout(null, 1, TimeUnit.SECONDS);
        return getAsync(id).thenCombine(tc, (posts, topic)->{
            Optional<PostsReplica> pr = Optional.ofNullable(posts).map(PostsReplica.copyPosts);
            pr.ifPresent(prObj->prObj.setTopic(topic));
            return pr;
        });
    }
}
