package com.apobates.forum.trident.controller.helper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.apobates.forum.core.ad.ActiveDirectoryConnectorFactory;
import com.apobates.forum.core.api.service.BoardConfigService;
import com.apobates.forum.core.api.service.BoardModeratorService;
import com.apobates.forum.core.api.service.BoardService;
import com.apobates.forum.core.api.service.PostsService;
import com.apobates.forum.core.api.service.TopicActionCollectionService;
import com.apobates.forum.core.api.service.TopicConfigService;
import com.apobates.forum.core.api.service.TopicService;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardConfig;
import com.apobates.forum.core.entity.BoardModerator;
import com.apobates.forum.core.entity.Posts;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.core.entity.TopicConfig;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.member.MemberProfileBean;
import com.apobates.forum.member.api.service.MemberService;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.storage.OnlineMemberStorage;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.strategy.Strategy;
import com.apobates.forum.strategy.StrategyEntityParam;
import com.apobates.forum.strategy.StrategyMode;
import com.apobates.forum.strategy.exception.StrategyException;
import com.apobates.forum.strategy.exception.VerificaFailException;
import com.apobates.forum.strategy.exposure.ComplexDetectionStrategy;
import com.apobates.forum.strategy.exposure.config.impl.BoardConfigMemberProfileStrategy;
import com.apobates.forum.strategy.exposure.config.impl.BoardConfigStrategy;
import com.apobates.forum.strategy.exposure.config.impl.TopicConfigMemberProfileStrategy;
import com.apobates.forum.strategy.exposure.config.impl.TopicConfigStrategy;
import com.apobates.forum.strategy.exposure.config.supply.BoardConfigInterruptStrategy;
import com.apobates.forum.strategy.exposure.config.supply.TopicConfigAtomPosterStrategy;
import com.apobates.forum.strategy.exposure.config.supply.TopicConfigInterruptStrategy;
import com.apobates.forum.strategy.exposure.config.supply.TopicConfigPrivacyProtectedStrategry;
import com.apobates.forum.strategy.exposure.impl.BoardDetectionStrategy;
import com.apobates.forum.strategy.exposure.impl.PostsDetectionStrategy;
import com.apobates.forum.strategy.exposure.impl.TopicDetectionStrategy;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.lang.TriFunction;

/**
 * 使用策略注解时的拦载器
 * 
 * @author xiaofanku
 * @since 20200808
 */
public class StrategyInterceptorAdapter extends HandlerInterceptorAdapter{
    private final static Logger logger = LoggerFactory.getLogger(StrategyInterceptorAdapter.class);
    @Value("${site.domain}")
    private String siteDomain;
    @Value("${site.member.freeze}")
    private int newMemberFreezeMinute;
    @Autowired
    private OnlineMemberStorage onlineMemberStorage;
    @Autowired
    private PostsService postsService;
    @Autowired
    private TopicService topicService;
    @Autowired
    private BoardService boardService;
    @Autowired
    private BoardModeratorService boardModeratorService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private TopicActionCollectionService topicActionCollectionService;
    @Autowired
    private TopicConfigService topicConfigService;
    @Autowired
    private BoardConfigService boardConfigService;
    private final BiFunction<Integer, Long, Supplier<Stream<BoardModerator>>> moderatorMapping = (boardGroupId, boardId)->()->boardModeratorService.getAllUsedByBoardId(boardGroupId, boardId);
    private final static String NEWLINE="\r\n";
    private final TriFunction<Integer, Long, Long, Supplier<Optional<BoardModerator>>> managerModeratorFun = (Integer boardGroupId, Long boardId, Long memberId)->()->boardModeratorService.get(boardGroupId, boardId, memberId);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod methodHandler=(HandlerMethod) handler;
        if(!methodHandler.hasMethodAnnotation(Strategy.class)){
            logger.info("[SIA]no strategy anno");
            return true;
        }
        logger.info("[SIA]start strategy process");
        Map<String,String> param = new HashMap<>();
        param.put("class", methodHandler.getClass().getName());
        param.put("controllerMethod", methodHandler.getMethod().getName());
        Strategy s = methodHandler.getMethodAnnotation(Strategy.class);
        param.put("action", s.action().name());
        param.put("allowRoles", Stream.of(s.allowRoles()).map(MemberRoleEnum::name).collect(Collectors.joining(",")));
        param.put("mode", s.mode().name());
        Class<?> handerClass;
        if(s.handler() == Void.class){
            handerClass = getHanderClassName(s.action());
        }else{
            handerClass = s.handler();
        }
        //
        Optional<Class<?>> ecp = getEntityClassName(handerClass.getName());
        if(!ecp.isPresent()){
            logger.info("[SIA]entity class not exists");
            return true;
        }
        Class<?> entityClass=ecp.get(); 
        String idVal = getEntityId(s.param(), s.paramId(), request, entityClass.getName());
        param.put("handler", handerClass.getName());
        param.put("entityClassName", entityClass.getName());
        param.put("entityId", idVal);
        param.put("configStrategy", (StrategyMode.ETC!=s.mode())+"");
        param.put("requestURI", request.getRequestURI());
        param.put("requestURL", request.getRequestURL().toString());
        param.put("referer", request.getHeader("referer"));
        //
        Member member = parseRequestMember(request);
        param.put("memberId", member.getId()+"");
        param.put("memberNames", member.getNames());
        param.put("memberRole", member.getMrole().name());
        param.put("memberGroup", member.getMgroup().name());
        logger.info(printStructMap(param));
        try{
            Method detectMethod = handerClass.getMethod("detect", Object.class, Member.class, ForumActionEnum.class, Set.class);
            Set<MemberRoleEnum> allowRoles = Collections.emptySet();
            try{
                allowRoles = Collections.unmodifiableSet(EnumSet.copyOf(Arrays.asList(s.allowRoles())));
            }catch(IllegalArgumentException|NullPointerException e){}
            Object entityIns = getDetectObject(idVal, entityClass).orElseThrow(()->new VerificaFailException("实体获取失败"));
            Object t = detectMethod.invoke(handerClass.getDeclaredConstructor().newInstance(), entityIns, member, s.action(), allowRoles);
            if(allowRoles.containsAll(Arrays.asList(MemberRoleEnum.MASTER, MemberRoleEnum.BM))){
                moderatorPlug(t, entityIns);
            }
            if(EnumSet.of(ForumActionEnum.TOPIC_PUBLISH, ForumActionEnum.POSTS_REPLY, ForumActionEnum.POSTS_REPLY_FORM, ForumActionEnum.POSTS_QUOTE).contains(s.action())){
                freezePlug(t);
            }
            if(StrategyMode.ETC != s.mode()){
                configStrategyPlug(t, entityIns, s.mode(), member);
            }
        }catch(InstantiationException|NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException e){
            e.printStackTrace();
            logger.info("[SIA]has Exception: "+e.getMessage());
        }catch(InvocationTargetException e){
            e.getTargetException().printStackTrace();
            logger.info("[SIA]has Exception: "+e.getTargetException().getMessage());
            if(e.getTargetException() instanceof VerificaFailException){
                throw (VerificaFailException)e.getTargetException();
            }
            if(e.getTargetException() instanceof StrategyException){
                throw (StrategyException)e.getTargetException();
            }
        }
        //
        logger.info("[SIA]strategy process finish");
        
        return true;
    }
    @SuppressWarnings("unchecked")
    private void configStrategyPlug(Object complexDetectionStrategy, Object entityIns, StrategyMode mode, Member member){
        ComplexDetectionStrategy<?> cds = null;
        try{
            cds = ComplexDetectionStrategy.class.cast(complexDetectionStrategy);
        }catch(ClassCastException e){
            e.printStackTrace();
            logger.info("[SIA][Freeze]ComplexDetectionStrategy cast Exception: "+e.getMessage());
        }
        Supplier<MemberProfileBean> mpb = ()->queryMemberProfileBean(member.getId()).orElseThrow(()->new StrategyException("会员个人信息计算失败"));
        if(null != entityIns && entityIns instanceof Topic){
            Topic t = (Topic)entityIns; TopicConfig tc = topicConfigService.getByTopicId(t.getId()).orElseThrow(()->new StrategyException("话题配置文件获取失败"));
            ((ComplexDetectionStrategy<Topic>)cds).config(TopicConfigStrategy.BUILDER)
                    .initial(tc, mode)
                    .profile(mpb, new TopicConfigMemberProfileStrategy(true, managerModeratorFun.apply(t.getVolumesId(), t.getBoardId(), member.getId())))
                    .plug(new TopicConfigPrivacyProtectedStrategry())
                    .plug(getB(tc.getWriteMinInterrupt(), t.getId()))
                    .plug(getC(t.getId(), member.getId()));
        }
        if(null != entityIns && entityIns instanceof Board){
            Board b = (Board)entityIns; BoardConfig bc = boardConfigService.getByBoardId(b.getId()).orElseThrow(()->new StrategyException("版块配置文件获取失败"));
            ((ComplexDetectionStrategy<Board>)cds).config(BoardConfigStrategy.BUILDER)
                    .initial(bc, mode)
                    .profile(mpb, new BoardConfigMemberProfileStrategy(true, managerModeratorFun.apply(b.getVolumesId(), b.getId(), member.getId())))
                    .plug(getA(bc.getWriteMinInterrupt(), b.getId()));
        }
    }
    //
    private TopicConfigAtomPosterStrategy getC(long topicId, long memberId){
        return new TopicConfigAtomPosterStrategy(()->postsService.countRepliesSize(topicId, memberId));
    }
    private TopicConfigInterruptStrategy getB(int minInterruptSize, long topicId){
        Supplier<Stream<Posts>> recentReplies = (minInterruptSize > 0) ? ()->postsService.getRecentForTopicIgnoreStatus(topicId, minInterruptSize) : ()->Stream.empty();
        return new TopicConfigInterruptStrategy(recentReplies);
    }
    private BoardConfigInterruptStrategy getA(int minInterruptSize, long boardId){
        Supplier<Stream<Topic>> recentTopices = (minInterruptSize > 0) ? ()->topicService.getRecentForBoardIgnoreStatus(boardId, minInterruptSize) : ()->Stream.empty();
        return new BoardConfigInterruptStrategy(recentTopices);
    }
    private Optional<MemberProfileBean> queryMemberProfileBean(long memberId){
        EnumMap<ForumActionEnum, Long> statsdata = topicActionCollectionService.groupMemberTopicAction(memberId);
        return memberService.getMemberProfileBean(memberId, statsdata);
    }
    //
    private void freezePlug(Object complexDetectionStrategy){
        ComplexDetectionStrategy<?> cds = null;
        try{
            cds = ComplexDetectionStrategy.class.cast(complexDetectionStrategy);
        }catch(ClassCastException e){
            e.printStackTrace();
            logger.info("[SIA][Freeze]ComplexDetectionStrategy cast Exception: "+e.getMessage());
        }
        if(null != cds){
            cds.freezePlug(newMemberFreezeMinute);
        }
    }
    private void moderatorPlug(Object complexDetectionStrategy, Object entityIns){
        ComplexDetectionStrategy<?> cds = null;
        try{
            cds = ComplexDetectionStrategy.class.cast(complexDetectionStrategy);
        }catch(ClassCastException e){
            e.printStackTrace();
            logger.info("[SIA][Moderator]ComplexDetectionStrategy cast Exception: "+e.getMessage());
        }
        long boardId=-1L;int boardGroupId=-1;
        if(null != entityIns && entityIns instanceof Posts){
            Posts p = ((Posts)entityIns);
            boardId=p.getBoardId();boardGroupId=p.getVolumesId();
        }
        if(null != entityIns && entityIns instanceof Topic){
            Topic t = ((Topic)entityIns);
            boardId=t.getBoardId();boardGroupId=t.getVolumesId();
        }
        if(null != entityIns && entityIns instanceof Board){
            Board b = ((Board)entityIns);
            boardId=b.getId();boardGroupId=b.getVolumesId();
        }
        //标签管理时非管理辖区的版主也能进行@bug:需要话题实体,但配置中即是话题标签
        if(null != cds && boardId >0 && boardGroupId >0){
            cds.moderatorPlug(moderatorMapping.apply(boardGroupId, boardId));
        }
    }
    private Optional<?> getDetectObject(String id, Class<?> entityClass){
        long primaryKey = Long.valueOf(id);
        if(entityClass == Posts.class){
            return postsService.get(primaryKey);
        }
        if(entityClass == Topic.class){
            return topicService.get(primaryKey);
        }
        if(entityClass == Board.class){ 
            return boardService.get(primaryKey);
        }
        return Optional.empty();
    }
    /**
     * 返回实体检测策略的类名
     * 
     * @param action
     * @return 
     */
    private Class<?> getHanderClassName(ForumActionEnum action){
        if(action.getSection().equals("topic")){
            return TopicDetectionStrategy.class;
        }
        if(action.getSection().equals("board")){
            return BoardDetectionStrategy.class;
        }
        if(action.getSection().equals("posts")){
            return PostsDetectionStrategy.class;
        }
        return Void.class;
    }
    /**
     * 返回实体检测策略使用的实体类名
     * 
     * @param handerClassName
     * @return 
     */
    private Optional<Class<?>> getEntityClassName(String handerClassName){
        if(handerClassName.endsWith("TopicDetectionStrategy")){
            return Optional.of(Topic.class);
        }
        if(handerClassName.endsWith("BoardDetectionStrategy")){
            return Optional.of(Board.class);
        }
        if(handerClassName.endsWith("PostsDetectionStrategy")){
            return Optional.of(Posts.class);
        }
        return Optional.empty();
    }
    /**
     * 返回策略使用的实体主键值
     * 
     * @param param
     * @param paramId
     * @param request
     * @param entityClassName
     * @return 
     */
    private String getEntityId(StrategyEntityParam param, String paramId, HttpServletRequest request, String entityClassName){
        if(StrategyEntityParam.QUERY_STR == param){
            return Commons.isNotBlank(paramId)?request.getParameter(paramId):"-1";//有的不需要实体
        }else{
            String urlString="";
            if(StrategyEntityParam.URI==param){
                urlString = request.getRequestURL().toString();//getRequestURI:不带域名的
            }
            if(StrategyEntityParam.REFERER == param){
                urlString = request.getHeader("referer");//带域名的
            }
            
            long id = parseEntityId(entityClassName, urlString); 
            return id+"";
        }
    }
    //只有在StrategyEntityParam.URI, StrategyEntityParam.REFERER,
    //只有在handler=Void.class
    private long parseEntityId(String entityClassName, String urlString){
        Objects.requireNonNull(entityClassName, "未知的实体类型"); Objects.requireNonNull(urlString, "URL地址不可用");
        if(entityClassName.endsWith("Topic")){
            return ActiveDirectoryConnectorFactory.parseRefererToTopic(urlString, siteDomain).orElseGet(Topic::new).getId();
        }
        if(entityClassName.endsWith("Board")){
            Optional<Board> b = ActiveDirectoryConnectorFactory.parseRefererToBoard(urlString, siteDomain);
            if(b.isPresent()){
                return b.get().getId();
            }
            //侧边栏的发布话题:需要Board,
            if(urlString.startsWith(siteDomain+"/topic/")){
                return ActiveDirectoryConnectorFactory.parseRefererToTopic(urlString, siteDomain).orElseGet(Topic::new).getBoardId();
            }
        }
        return 0L;
    }
    /**
     * 返回策略使用的会员实例
     * 
     * @param request
     * @return 
     */
    private Member parseRequestMember(HttpServletRequest request){
        // Cookie
        MemberSessionBean mbean = onlineMemberStorage.getInstance(request, "StrategyInterceptorAdapter").orElse(null);
        if (null == mbean) {
            return Member.guestMember();
        }
        return mbean.toMember();
    }
    /**
     * 打印策略使用的相关参数值
     * 
     * @param struct
     * @return 
     */
    private String printStructMap(Map<String,String> struct){
        String descrip = "[StrategyInterceptorAdapter]" + NEWLINE;
            descrip += "/*----------------------------------------------------------------------*/" + NEWLINE;
        for(Entry<String,String> entry : struct.entrySet()){
            descrip += "* "+entry.getKey()+": " +entry.getValue()+ NEWLINE;
        }
            descrip += "/*----------------------------------------------------------------------*/" + NEWLINE;
        return descrip;
    }
}
