package com.apobates.forum.thrones.controller;

import com.apobates.forum.core.ImageIOMeta;
import com.apobates.forum.core.entity.BoardTopicCategoryIndex;
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.entity.proxy.TopicReplica;
import com.apobates.forum.core.security.exposure.impl.TopicDetectionStrategy;
import com.apobates.forum.core.security.strategy.Strategy;
import com.apobates.forum.core.security.strategy.StrategyEntityParam;
import com.apobates.forum.core.security.strategy.StrategyMode;
import com.apobates.forum.core.service.BoardService;
import com.apobates.forum.core.service.BoardTopicCategoryIndexService;
import com.apobates.forum.core.service.PostsService;
import com.apobates.forum.core.service.TopicService;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.member.MemberBaseProfile;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.thrones.controller.form.PostsForm;
import com.apobates.forum.thrones.controller.helper.OnlineDescriptor;
import com.apobates.forum.thrones.controller.helper.ReportPlugTopic;
import com.apobates.forum.thrones.exception.ForumValidateException;
import com.apobates.forum.thrones.exception.ResourceNotFoundException;
import com.apobates.forum.thrones.vo.ForumReplierDigest;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.TipMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 回复控制器
 * 
 * @author xiaofanku
 * @since 20200522
 */
@Controller
@RequestMapping(value = "/posts")
public class PostsController {
    @Autowired
    private BoardService boardService;
    @Autowired
    private TopicService topicService;
    @Autowired
    private PostsService postsService;
    @Autowired
    private BoardTopicCategoryIndexService boardTopicCategoryIndexService;
    @Autowired
    private ImageIOMeta imageIOMeta;
    @Value("${site.domain}")
    private String siteDomain;
    @Value("${site.pageSize}")
    private int pageSize;
    
    @ModelAttribute("topicArg")
    public TopicReplica getTopic(HttpServletRequest request) {
        return TopicReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
    }
    
    // 回复话题
    @GetMapping(path = "/create")
    @OnlineDescriptor(action=ForumActionEnum.POSTS_REPLY)
    @Strategy(action=ForumActionEnum.POSTS_REPLY, mode=StrategyMode.WRITE, handler=TopicDetectionStrategy.class)
    public String createPostsForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @RequestParam(value = "qfloor", required = false, defaultValue = "0") long quoteFloorId,
            @RequestParam(value = "qreplier", required = false, defaultValue = "0") long quotePostsId,
            @RequestParam(value = "scale", required = false, defaultValue = "auto") String quoteImageScale,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //------------------------------------------Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            throw new ResourceNotFoundException("回复话题需要的参数解析失败");
        }
        //------------------------------------------对回复话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //------------------------------------------
        PostsForm form = new PostsForm();
        form.setBoard(tpObj.getBoardId() + "");
        form.setVolumes(tpObj.getVolumesId() + "");
        form.setTopic(tpObj.getId() + "");
        form.setToken(token);
        if (quotePostsId > 0 && quoteFloorId > 0) {
            //----------------------------------引用%d楼%s的回复
            Optional<Posts> data = postsService.get(quotePostsId);
            if (data.isPresent()) {
                form.setContent("");
                //只在移动设备中使用
                form.setQuoteFloor(quoteFloorId);
                form.setQuoteId(quotePostsId);
                form.setQuoteScale(quoteImageScale);
                model.addAttribute("infoTip", String.format("引用%d楼%s的回复", quoteFloorId, data.get().getMemberNickname()));
            }
        }
        model.addAttribute("form", form);
        //
        TopicReplica tr = topicService.get(tpObj.getId()).map(TopicReplica.copyTopic).orElseGet(TopicReplica::new);
        tr.setBoard(boardService.get(tpObj.getBoardId(), tpObj.getVolumesId()).orElse(null));
        model.addAttribute("topic", tr);
        return "default/posts/create";
    }
    
    @PostMapping(path = "/create")
    @OnlineDescriptor(action=ForumActionEnum.POSTS_REPLY)
    @Strategy(action=ForumActionEnum.POSTS_REPLY, param=StrategyEntityParam.QUERY_STR, paramId="topic", mode=StrategyMode.WRITE, handler=TopicDetectionStrategy.class)
    public String createPostsAction(
            HttpServletRequest request,
            MemberSessionBean mbean,
            @Valid @ModelAttribute("form") PostsForm form,
            BindingResult bindingResult,
            Model model) {
        if (bindingResult.hasErrors()) {
            throw new ForumValidateException(bindingResult.getAllErrors().get(0).getDefaultMessage());
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
        //----------------------------------对回复话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //----------------------------------引用%d楼%s的回复
        String postsContent = getRealPostsContent(form.getQuoteId(), form.getContent(), form.getQuoteScale(), request);
        //----------------------------------
        long boardId = form.getLongBoard();
        int volumesId = form.getIntegerVolumes();
        long topicId = form.getLongTopic();
        //
        if (boardId > 0 && volumesId >= 0 && topicId > 0) {
            long postsId = postsService.create(volumesId, boardId, topicId, postsContent, imageIOMeta, aec);
            if (postsId>0) {
                //----------------------------------
                // 回复通知(tc.notify)
                //----------------------------------
                long lastPostsPageNumber = calcPostsPageNumber(postsService.get(postsId), pageSize);
                return String.format("redirect:/topic/%s.xhtml?p=%d", TopicReplica.generateConnectString(topicId, boardId, volumesId), lastPostsPageNumber);
            }
        }
        model.addAttribute("form", form);
        model.addAttribute("errors", "话题回复操作失败");
        //
        TopicReplica topic = topicService.get(topicId).map(TopicReplica.copyTopic).orElseGet(TopicReplica::new);
        topic.setBoard(boardService.get(boardId, volumesId).orElse(null));
        model.addAttribute("topic", topic);
        return "default/posts/create";
    }
    //计算指定回复所在页码值
    private long calcPostsPageNumber(Optional<Posts> posts, int pageSize){
        if(!posts.isPresent()){
            return 1L;
        }
        long currentFloor = posts.get().getFloorNumber();
        if (currentFloor <= pageSize) {
            return 1;
        }
        // 整除
        long t = currentFloor / pageSize;
        // 不整除
        if (currentFloor % pageSize != 0) {
            t += 1;
        }
        return t;
    }
    // 编辑回复,只允许针对Posts.reply(回复的内容/非话题的内容/)的记录
    @GetMapping(path = "/edit")
    @OnlineDescriptor(action=ForumActionEnum.POSTS_EDIT)
    @Strategy(action=ForumActionEnum.POSTS_EDIT, param=StrategyEntityParam.QUERY_STR, paramId="id", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String editPostsForm(
            @RequestParam("id") long postsId,
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        Posts posts = postsService.getPostsContentForEdit(postsId, imageIOMeta).orElse(Posts.empty(""));
        //----------------------------------策略检查开始,不作具体限制让策略去作
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        PostsForm form = new PostsForm();
        form.setRecord(postsId);
        form.setBoard(posts.getBoardId() + "");
        form.setTopic(posts.getTopicId() + "");
        form.setContent(posts.getContent());
        form.setVolumes(posts.getVolumesId() + "");
        form.setToken(token);
        model.addAttribute("form", form);
        //
        TopicReplica topic = topicService.get(posts.getTopicId()).map(TopicReplica.copyTopic).orElseGet(TopicReplica::new);
        topic.setBoard(boardService.get(posts.getBoardId(), posts.getVolumesId()).orElse(null));
        model.addAttribute("topic", topic);
        return "default/posts/modify";
    }
    @PostMapping(path = "/edit")
    @OnlineDescriptor(action=ForumActionEnum.POSTS_EDIT)
    @Strategy(action=ForumActionEnum.POSTS_EDIT, param=StrategyEntityParam.QUERY_STR, paramId="record", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String editPostsAction(
            HttpServletRequest request, 
            MemberSessionBean mbean, 
            @Valid @ModelAttribute("form") PostsForm form, 
            BindingResult bindingResult,
            Model model) {
        if (bindingResult.hasErrors()) {
            model.addAttribute("errors", bindingResult.getAllErrors().get(0).getDefaultMessage());
            model.addAttribute("form", form);
            //
            TopicReplica topic = topicService.get(form.getLongTopic()).map(TopicReplica.copyTopic).orElseGet(TopicReplica::new);
            topic.setBoard(boardService.get(form.getLongBoard(), form.getIntegerVolumes()).orElse(null));
            model.addAttribute("topic", topic);
            return "default/posts/modify";
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
        //----------------------------------策略检查开始,不作具体限制让策略去作
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        boolean status = postsService.edit(
                form.getLongRecord(),
                form.getContent(),
                imageIOMeta,
                aec).orElse(false);
        if (status) {
            return String.format("redirect:/topic/%s.xhtml", TopicReplica.generateConnectString(form.getLongTopic(), form.getLongBoard(), form.getIntegerVolumes())); //?20191104
        }
        model.addAttribute("errors", "编辑话题回复操作失败");
        model.addAttribute("form", form);
        //
        TopicReplica topic = topicService.get(form.getLongTopic()).map(TopicReplica.copyTopic).orElseGet(TopicReplica::new);
        topic.setBoard(boardService.get(form.getLongBoard(), form.getIntegerVolumes()).orElse(null));
        model.addAttribute("topic", topic);
        return "default/posts/modify";
    }
    //话题内容页中关于回复的功能
    // 快速回复
    @PostMapping(path = "/reply", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.POSTS_REPLY)
    @Strategy(action=ForumActionEnum.POSTS_REPLY, mode=StrategyMode.WRITE, handler=TopicDetectionStrategy.class)
    public String quickReplyAction(
            @RequestParam("content") String content,
            @RequestParam(value = "quote", required = false, defaultValue = "0") long quotePostsId,
            @RequestParam(value = "scale", required = false, defaultValue = "auto") String imageScale,
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        TopicReplica tpObj = TopicReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败").toJsonString();
        }
        Topic topic = topicService.get(tpObj.getId()).orElseThrow(()->new ResourceNotFoundException("话题不存在或暂时无法访问"));
        long topicId = topic.getId();
        long boardId = topic.getBoardId();
        int boardGroupId = topic.getVolumesId();
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------对回复话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //----------------------------------
        if (!Commons.isNotBlank(content)) {
            return TipMessage.ofError("话题回复内容不可以为空").toJsonString();
        }
        //----------------------------------引用%d楼%s的回复
        String postsContent = getRealPostsContent(quotePostsId, content, imageScale, request);
        //----------------------------------
        if (boardId > 0 && boardGroupId >= 0 && topicId > 0) {
            long postsId = postsService.create(boardGroupId, boardId, topicId, postsContent, imageIOMeta, aec); //这里有图片会出问题吗?@20200506
            if (postsId > 0) {
                //异步
                Posts posts = postsService.get(postsId).orElse(null);
                if (null == posts) {
                    return TipMessage.ofError("回复失败").toJsonString();
                }
                
                return new ForumReplierDigest(
                        postsId,
                        posts.getMemberId(),
                        posts.getMemberNickname(),
                        mbean.getGroup().getTitle(),
                        MemberBaseProfile.getStyle(mbean.getGroup(), mbean.getRole()),
                        mbean.getSignature(),
                        posts.getEntryDateTime(),
                        postsContent,
                        posts.getFloorNumber(),
                        (mbean.getMid()==topic.getMemberId()), 
                        false,
                        "-1",
                        null).toJsonString();
            }
        }
        return TipMessage.ofError("回复失败").toJsonString();
    }
    // 删除回复|屏弊回复
    @PostMapping(path = "/delete", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.POSTS_DEL)
    @Strategy(action=ForumActionEnum.POSTS_DEL, param=StrategyEntityParam.QUERY_STR, paramId="id", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage removePostsAction(
            @RequestParam("id") long postsId,
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->postsService.remove(postsId, aec)).success("话题回复成功删除").error("操作失败");
    }
    
    // 返回引用的回复内容
    @GetMapping(path = "/quote/data.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @Strategy(action=ForumActionEnum.POSTS_QUOTE, param=StrategyEntityParam.REFERER, mode=StrategyMode.WRITE, handler=TopicDetectionStrategy.class)
    public Map<String, String> quotePostsContent(
            @RequestParam("id") long postsId,
            @RequestParam(value = "scale", required = false, defaultValue = "auto") String imageScale,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败").toMap();
        }
        //----------------------------------
        Optional<Posts> data = postsService.get(postsId);
        if (!data.isPresent()) {
            return TipMessage.ofError("引用的回复对象不存在或暂时无法访问").toMap();
        }
        Posts p = data.get();
        if(!p.isNormal()){
            return TipMessage.ofError("引用的回复对象已不可用").toMap();
        }
        //----------------------------------对回复话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //----------------------------------引用%d楼%s的回复
        Map<String, String> rs = new HashMap<>();
        rs.put("quote", postsId + "");
        rs.put("scale", imageScale);
        rs.put("infoTip", String.format("引用%d楼%s的回复", p.getFloorNumber(), p.getMemberNickname()));
        //----------------------------------
        return rs;
    }
    
    // 快速回复的表单
    @GetMapping(path = "/reply/form")
    @Strategy(action=ForumActionEnum.POSTS_REPLY_FORM, param=StrategyEntityParam.REFERER, mode=StrategyMode.WRITE, handler=TopicDetectionStrategy.class)
    public String getQuickReplyForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------对回复话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //-------------------------------------------------
        model.addAttribute("token", token);
        if(mbean.isOnline()){
            model.addAttribute("member", MemberBaseProfile.init(mbean.toMember()).toMap());
            return "default/posts/reply_embedded";
        }
        model.addAttribute("member", MemberBaseProfile.init(Member.guestMember()).toMap());
        return "default/posts/block_reply_embedded";
    }
    // 举报
    @PostMapping(path = "/report", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.POSTS_REPORT)
    @Strategy(action=ForumActionEnum.POSTS_REPORT, param=StrategyEntityParam.QUERY_STR, paramId="id")
    public TipMessage reportPostsAction(
            @RequestParam("id") long postsId,
            @RequestParam("type") int reportTypeSymbol,
            @RequestParam("reason") String reportContent,
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        long topicId = tpObj.getId();
        //----------------------------------
        PostsReplica posts = postsService.get(postsId, topicId).orElse(null);
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        Optional<BoardTopicCategoryIndex> btc = boardTopicCategoryIndexService.getReportRelativeRecord();
        if(!btc.isPresent()){
            return TipMessage.ofError("未知的存储目标");
        }
        final Optional<TopicReplica> result = topicService.plug(new ReportPlugTopic(
                ForumActionEnum.POSTS_REPORT,
                aec,
                posts,
                reportTypeSymbol,
                reportContent,
                btc.get()));
        return TipMessage.Builder.of(()->result.isPresent()).success("回复举报成功").error("操作失败");
    }
    //引用回复时,引用的内容
    private String quoteContent(Optional<Posts> quotePosts) {
        if (!quotePosts.isPresent()) {
            return "";
        }
        Posts p = quotePosts.get();
        String t = "<blockquote><strong>引用<a href=\"%s\">%s</a> %s 的回复</strong><br/>%s</blockquote>";
        String quoteUrl = String.format("%s/topic/%s.xhtml#posts-%d", siteDomain, TopicReplica.generateConnectString(p.getTopicId(), p.getBoardId(), p.getVolumesId()), p.getId());
        String quoteContent = (p.isNormal()) ? p.getContent() : "<p>回复内容已被屏蔽</p>";
        return String.format(t,
                quoteUrl,
                p.getFloorNumber() + "楼",
                p.getMemberNickname(),
                quoteContent);
    }
    //前补引用的内容
    private String getRealPostsContent(long quotePostsId, String formPostsContent, String imageScale, HttpServletRequest request) {
        String postsContent = formPostsContent;
        //
        if (quotePostsId > 0) {
            String quotePostsContent = quoteContent(postsService.getPostsContentForQuote(quotePostsId, imageIOMeta, false, imageScale));
            postsContent = quotePostsContent + postsContent;
        }
        return postsContent;
    }
}
