package com.apobates.forum.thrones.controller;

import com.apobates.forum.attention.core.decorater.ForumEntityIcoDecorder;
import com.apobates.forum.core.ImageIOMeta;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardConfig;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.BoardStats;
import com.apobates.forum.core.entity.proxy.BoardReplica;
import com.apobates.forum.core.entity.proxy.TopicReplica;
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.BoardConfigService;
import com.apobates.forum.core.service.BoardGroupService;
import com.apobates.forum.core.service.BoardModeratorService;
import com.apobates.forum.core.service.BoardService;
import com.apobates.forum.core.service.BoardStatsService;
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.entity.MemberGroupEnum;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.thrones.controller.form.BoardConfigForm;
import com.apobates.forum.thrones.controller.helper.OnlineDescriptor;
import com.apobates.forum.thrones.exception.ResourceNotFoundException;
import com.apobates.forum.thrones.vo.CommonLink;
import com.apobates.forum.thrones.vo.CommonLink.CommonLinkBuilder;
import com.apobates.forum.thrones.vo.ForumThreads;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.TipMessage;
import com.apobates.forum.utils.lang.EnumArchitecture;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.PageRequest;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
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 20200521
 */
@Controller
@RequestMapping(value = "/board")
public class BoardController {
    @Autowired
    private BoardGroupService boardGroupService;
    @Autowired
    private BoardService boardService;
    @Autowired
    private BoardModeratorService boardModeratorService;
    @Autowired
    private BoardConfigService boardConfigService;
    @Autowired
    private TopicService topicService;
    @Autowired
    private BoardStatsService boardStatsService;
    @Autowired
    private ImageIOMeta imageIOMeta;
    @Value("${site.domain}")
    private String siteDomain;

    @ModelAttribute("boardArg")
    public BoardReplica getTopic(HttpServletRequest request) {
        return BoardReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
    }
    
    // 版块主页(所有话题)
    @GetMapping(path="/{path}.xhtml")
    @OnlineDescriptor(action=ForumActionEnum.BOARD_BROWSE)
    @Strategy(action=ForumActionEnum.BOARD_BROWSE, param=StrategyEntityParam.URI, mode=StrategyMode.READ)
    public String boardHome(
            @PathVariable("path") String connectValue,
            @RequestParam(value = "category", required = false, defaultValue = "") String categoryValue,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        BoardReplica adObj = BoardReplica.of(connectValue).orElseThrow(()->new ResourceNotFoundException("版块路径参数解析失败"));
        // 版块的统计怎么加载: A是要推(websocket)?用户真的关心这个数据的实时性吗!B是要拉(ajax)?
        BoardReplica board = boardService.getBoardStats(adObj.getId()).orElseThrow(()->new ResourceNotFoundException("版块不存在或暂时无法访问")); //有统计数据
        //----------------------------------策略检查开始,不限角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        // 版块下的话题异步加载
        //----------------------------------
        BoardGroup volumes = boardGroupService.get(board.getVolumesId()).orElse(BoardGroup.empty(board.getVolumesId())); //optional:class java.util.LinkedHashMap cannot be cast to class
        board.copyVolumes(volumes);
        model.addAttribute("board", board);
        model.addAttribute("category", categoryValue);
        return "default/board/view";
    }
    
    // 会员星标版块
    @PostMapping(path = "/star", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.BOARD_FAVORITE)
    @Strategy(action=ForumActionEnum.BOARD_FAVORITE)
    public String starBoardAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("boardArg") BoardReplica adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败").toJsonString();
        }
        long boardId = adObj.getId();
        //----------------------------------
        Board board = boardService.get(boardId).orElse(Board.empty(0));
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        boolean symbol; 
        try{
            symbol = boardService.favorite(boardId, aec).orElse(false);
        }catch(IllegalStateException e){
            return TipMessage.ofError(e.getMessage(), BoardReplica.generateConnectString(boardId, board.getVolumesId())).toJsonString();
        }
        if (symbol) {
            return TipMessage.ofSuccess("版块收藏成功").toJsonString();
        }
        return TipMessage.ofError("收藏失败").toJsonString();
    }
    
    // 删除星标的版块
    @PostMapping(path = "/star/cancel", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.BOARD_FAVORITE_CANCEL)
    @Strategy(action=ForumActionEnum.BOARD_FAVORITE_CANCEL, param=StrategyEntityParam.QUERY_STR, paramId="id")
    public String undoStarBoardAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @RequestParam("id") long boardId,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        if (boardId < 1 || !mbean.isOnline()) {
            return TipMessage.ofError("参数丢失或不合法").toJsonString();
        }
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限角色.
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->boardService.removeFavorite(boardId, aec)).success("已成功取消收藏").error("取消收藏失败").toJsonString();
    }
    
    // 锁定
    @PostMapping(path = "/lock", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.BOARD_LOCK)
    @Strategy(action=ForumActionEnum.BOARD_LOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage lockBoardAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("boardArg") BoardReplica adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        long boardId = adObj.getId();
        int boardGroupId = adObj.getVolumesId();
        //----------------------------------
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始:只有管理员可以锁定
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->boardService.lock(boardId, boardGroupId, aec)).success("版块成功锁定").error("操作失败");
    }
    
    // 解锁
    @PostMapping(path = "/unlock", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.BOARD_UNLOCK)
    @Strategy(action=ForumActionEnum.BOARD_UNLOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage unlockBoardAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("boardArg") BoardReplica adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        long boardId = adObj.getId();
        int boardGroupId = adObj.getVolumesId();
        //----------------------------------
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始:只有管理员可以解锁
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->boardService.releaseLock(boardId, boardGroupId, aec)).success("版块解锁成功").error("操作失败");
    }
    
    // 编辑版块的配置
    @GetMapping(path = "/config/edit")
    @OnlineDescriptor(action=ForumActionEnum.BOARD_CONFIG_EDIT)
    @Strategy(action=ForumActionEnum.BOARD_CONFIG_EDIT, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String boardConfigForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("boardArg") BoardReplica adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            throw new ResourceNotFoundException("操作参数解析失败");
        }
        long boardId = adObj.getId();
        int boardGroupId = adObj.getVolumesId();
        //----------------------------------
        BoardReplica board = boardService.get(boardId).map(BoardReplica.copyBoard).orElseThrow(()->new ResourceNotFoundException("版块不存在或暂时无法访问"));
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以编辑
        //  移至StrategyInterceptorAdapter
        /*----------------------------------策略检查结束*/
        BoardConfigForm form = boardConfigService.getByBoardId(boardId).map(BoardConfigForm::init).orElseGet(BoardConfigForm::new);
        //因BoardConfigForm.init中未设置boardId,boardgroupId
        form.setBoardId(boardId + "");
        form.setVolumesId(boardGroupId + "");
        form.setToken(token);
        model.addAttribute("form", form);
        model.addAttribute("board", board);
        return "default/board/config";
    }
    @PostMapping(path = "/config/edit")
    @OnlineDescriptor(action=ForumActionEnum.BOARD_CONFIG_EDIT)
    @Strategy(action=ForumActionEnum.BOARD_CONFIG_EDIT, param=StrategyEntityParam.QUERY_STR, paramId="boardId", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String boardConfigAction(@ModelAttribute("form") BoardConfigForm form, MemberSessionBean mbean, HttpServletRequest request, Model model) {
        BoardReplica br = boardService.get(form.getLongBoardId()).map(BoardReplica.copyBoard).orElseThrow(()->new ResourceNotFoundException("版块不存在或暂时无法访问"));
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
        //----------------------------------策略检查开始, 限制角色:管理员,版主,大版主都可以编辑
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        BoardConfig config = new BoardConfig();
        //boolean
        config.setReadWrite(form.getBooleanReadWrite());
        config.setIpFilter(form.getBooleanIpFileter());
        //读
        config.setReadMinScore(form.getIntegerReadMinScore());
        MemberGroupEnum rmg = EnumArchitecture.getInstance(form.getIntegerReadLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.GUEST);
        config.setReadLowMemberGroup(rmg);
        MemberRoleEnum rmr = EnumArchitecture.getInstance(form.getIntegerReadLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
        config.setReadLowMemberRole(rmr);
        config.setReadLowMemberLevel(form.getIntegerReadLowMemberLevel());
        //写
        config.setEditMinute(form.getIntegerEditMinute());
        config.setWriteMinInterrupt(form.getIntegerWriteMinInterrupt());
        config.setWriteMinScore(form.getIntegerWriteMinScore());
        MemberGroupEnum wmg = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.CARD);
        config.setWriteLowMemberGroup(wmg);
        MemberRoleEnum wmr = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
        config.setWriteLowMemberRole(wmr);
        config.setWriteLowMemberLevel(form.getIntegerWriteLowMemberLevel());
        //
        boolean symbol = false;
        try{
            symbol = boardService.editBoardConfig(
                form.getLongBoardId(),
                form.getIntegerVolumesId(),
                form.getLongRecord(),
                config,
                aec).orElse(false);
        }catch(IllegalArgumentException e){}
        if (symbol) {
            return String.format("redirect:/board/%s.xhtml", br.getConnect());
        }
        model.addAttribute("form", form);
        model.addAttribute("board", br);
        model.addAttribute("errors", "更新版块下的话题策略配置失败");
        return "default/board/config";
    }
    
    // 查看指定版块的图标
    @GetMapping(path = "/ico/{id}.png", produces = MediaType.IMAGE_PNG_VALUE)
    public ResponseEntity<byte[]> getBoardIco(
            @PathVariable("id") long id, 
            @RequestParam(value="scale", required = false, defaultValue = "auto")String scale, 
            HttpServletResponse response, 
            Model model) {
        Board board = boardService.get(id).orElse(null);
        if (null == board) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return null;
        }
        try {
            URL boardIcoURL = new ForumEntityIcoDecorder(imageIOMeta.getImageBucketDomain(), imageIOMeta.getUploadImageDirectName())
                    .decode(board.getImageAddr(), scale)
                    .orElse(null);
            if(null == boardIcoURL){
                boardIcoURL = new URL(String.format("%s/%s/ico/default_icon.png", imageIOMeta.getImageBucketDomain(), imageIOMeta.getUploadImageDirectName()));
            }
            InputStream in = boardIcoURL.openStream();
            byte[] avtarBytes = IOUtils.toByteArray(in);
            
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.IMAGE_PNG);
            headers.setCacheControl(CacheControl.noCache().getHeaderValue());
            return new ResponseEntity<>(avtarBytes, headers, HttpStatus.CREATED);
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return null;
        }
    }
    
    //异步加载指定版块下的话题
    @GetMapping(path = "/threads.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String getBoardTopics(
            @RequestParam("pageNumber") int page,
            @RequestParam("pageSize") int pageSize,
            @RequestParam(value = "category", required = false, defaultValue = "") String categoryValue,
            @ModelAttribute("boardArg") BoardReplica adObj,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return "{}";
        }
        //-------------------------------------------------
        Page<TopicReplica> rs = topicService.getAll(adObj.getId(), categoryValue, new PageRequest(page, pageSize));
        List<ForumThreads> data = rs.getResult().map(ForumThreads::new).collect(Collectors.toList());
        Map<String, Object> result = new HashMap<>();
        result.put("result", data);
        result.put("total", rs.getTotalElements());
        result.put("board", adObj.getId());
        result.put("volumes", adObj.getVolumesId());
        result.put("page", page);
        result.put("size", pageSize);
        if (Commons.isNotBlank(categoryValue)) {
            result.put("category", categoryValue);
        }
        return new Gson().toJson(result);
    }
    
    //异步加载话题的统计
    @GetMapping(path = "/stats.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String getBoardStats(
            @ModelAttribute("boardArg") Board adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return "{}";
        }
        //-------------------------------------------------
        BoardStats bs = boardStatsService.getByBoard(adObj.getId()).orElseGet(() -> new BoardStats(adObj.getId(), adObj.getVolumesId()));
        Map<String, Long> statsData = bs.statsMap();
        
        if (mbean.isOnline()) {
            statsData.put("favorited", boardService.isFavorited(adObj.getId(), mbean.getMid()) ? 1L : 0L); //是否可以收藏
            statsData.put("victim", mbean.getMid());
        } else { //匿名不可以
            statsData.put("favorited", 0L);
            statsData.put("victim", 0L);
        }
        return Commons.toStatsJson(statsData);
    }
    // 工具菜单
    @GetMapping(path = "/tool.jsonp", produces = "application/javascript;charset=UTF-8")
    @ResponseBody
    public String getBoardToolMenus(
            @RequestParam("callback") String callBackFun,
            @RequestParam(value = "box", required = false, defaultValue = "null") String boxEle,
            @ModelAttribute("boardArg") Board adObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == adObj || adObj.getId() < 1) {
            return callBackFun + "({});";
        }
        long boardId = adObj.getId();
        int boardGroupId = adObj.getVolumesId();
        //-------------------------------------------------
        Map<String, List<CommonLink>> menues = new HashMap<>();
        List<CommonLink> publicLink = Arrays.asList(
                CommonLinkBuilder.setAnchor("收藏").setLink("/board/star").isAjax(true).build(),
                CommonLinkBuilder.setAnchor("订阅").setLink("/rss/board.xml?id=" + boardId + "&volumes=" + boardGroupId).build(),
                CommonLinkBuilder.setAnchor("文本").build(),
                CommonLinkBuilder.setAnchor("打印").build());
        menues.put("public", publicLink);
        if (MemberRoleEnum.ADMIN == mbean.getRole() || boardModeratorService.get(boardGroupId, boardId, mbean.getMid()).isPresent()) {
            List<CommonLink> manageLink = Arrays.asList(
                    CommonLinkBuilder.setAnchor("锁定").setLink("/board/lock").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("解锁").setLink("/board/unlock").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("配置").setLink("/board/config/edit").build());
            menues.put("manager", manageLink);
        }
        Map<String, Object> result = Map.ofEntries(Map.entry("element", boxEle), Map.entry("result", menues));
        return callBackFun + "(" + new Gson().toJson(result) + ");";
    }
    public static ActionEventCulpritor getInstance(final long memberId, final String memberNickname, final HttpServletRequest request, final String token) {
        return new ActionEventCulpritor() {
            @Override
            public long getMemberId() {
                return memberId;
            }
            
            @Override
            public String getMemberNickname() {
                return memberNickname;
            }
            
            @Override
            public String getIpAddr() {
                return Commons.getRemoteAddr(request);
            }
            
            @Override
            public String getToken() {
                return token;
            }
            
            @Override
            public String getUserAgent() {
                return request.getHeader("User-Agent");
            }
        };
    }
}