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

import com.apobates.forum.core.security.exception.StrategyException;
import com.apobates.forum.core.security.exception.VerificaFailException;
import com.apobates.forum.member.entity.MemberGroupEnum;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.entity.MemberStatusEnum;
import com.apobates.forum.member.impl.event.MemberChangeEvent;
import com.apobates.forum.member.storage.OnlineMemberStorage;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.member.storage.core.MemberSessionBeanBuilder;
import com.apobates.forum.thrones.event.MemberChangeEventListener;
import com.apobates.forum.thrones.exception.BorbidMemberRegisterException;
import com.apobates.forum.thrones.exception.ForumValidateException;
import com.apobates.forum.thrones.exception.MemberFreezeException;
import com.apobates.forum.thrones.exception.PermissionLostException;
import com.apobates.forum.thrones.exception.ResourceNotFoundException;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.TipMessage;
import com.apobates.forum.utils.TipMessageLevelEnum;
import com.apobates.forum.utils.lang.ReplicableException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 控制器共享
 *
 * @author xiaofanku
 * @since 20200511
 */
@ControllerAdvice
public class CommonControllerAdvice {
    @Autowired
    private OnlineMemberStorage onlineMemberStorage;
    @Autowired
    private RedisTemplate<String, Object> template;
    @Value("${site.domain}")
    private String siteDomain;
    private final static Logger logger = LoggerFactory.getLogger(CommonControllerAdvice.class);
    
    //有什么显示什么
    //不作还原
    @ModelAttribute
    public MemberSessionBean getSessionBean(HttpServletRequest request, HttpServletResponse response) {
        logger.info("[CCA]EXE Sequence: 2:"+request.getRequestURI());
        // Cookie
        MemberSessionBean mbean = onlineMemberStorage.getInstance(request, "CommonControllerAdvice").orElse(MemberSessionBeanBuilder.empty().build(Commons.getRequestIp(request), "CommonControllerAdvice"));
        if (mbean.getMid()>0) {
            Optional<MemberSessionBean> data = refactMemberSessionBean(mbean);
            data.ifPresent(newMSB -> {
                onlineMemberStorage.refresh(request, response, newMSB.getStatus(), newMSB.getGroup(), newMSB.getRole());
            });
            if(data.isPresent()){
                mbean = data.get();
            }
        }
        return mbean;
    }
    private Optional<MemberSessionBean> refactMemberSessionBean(MemberSessionBean oldBean) {
        Map<String, Integer> cachedata = getEventPushDate(oldBean.getMid());
        if (!cachedata.isEmpty()) {
            Optional<MemberGroupEnum> newGroup = MemberChangeEvent.get(MemberGroupEnum.class, cachedata);
            Optional<MemberStatusEnum> newStatus = MemberChangeEvent.get(MemberStatusEnum.class, cachedata);
            Optional<MemberRoleEnum> newRole = MemberChangeEvent.get(MemberRoleEnum.class, cachedata);
            //
            MemberSessionBean newBean = oldBean.refact(newGroup.orElse(oldBean.getGroup()), newRole.orElse(oldBean.getRole()), newStatus.orElse(oldBean.getStatus()));
            return Optional.of(newBean);
        }
        return Optional.empty();
    }

    private Map<String,Integer> getEventPushDate(long memberId){
        String redisKey = MemberChangeEventListener.getEventRedisKey(memberId);
        HashOperations<String, String, Integer> operation = template.opsForHash();
        Map<String, Integer> cachedata = operation.entries(redisKey);
        if (null != cachedata && !cachedata.isEmpty()) { //总有一个变了
            template.delete(redisKey);
            return cachedata;
        }
        return Collections.emptyMap();
    }
    //https://stackoverflow.com/questions/24498662/howto-handle-404-exceptions-globally-using-spring-mvc-configured-using-java-base
    //https://stackoverflow.com/questions/2066946/trigger-404-in-spring-mvc-controller
    @ExceptionHandler(value={ResourceNotFoundException.class, NoHandlerFoundException.class, ReplicableException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ModelAndView getModuleVisitException(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Exception e){
        if(logger.isDebugEnabled()){
            logger.debug(e.getMessage());
        }
        //e.printStackTrace();
        return normal(e, e.getMessage(), request, response, "404");
    }
    
    @ExceptionHandler(value={StrategyException.class, VerificaFailException.class, MemberFreezeException.class, PermissionLostException.class, BorbidMemberRegisterException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ModelAndView getStrategyExecuteException(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Exception e){
        if(logger.isDebugEnabled()){
            logger.debug(e.getMessage());
        }
        //e.printStackTrace();
        return normal(e, e.getMessage(), request, response, "401");
    }
    
    @ExceptionHandler(value={
            ForumValidateException.class, 
            MissingServletRequestPartException.class, 
            MethodArgumentNotValidException.class, 
            HttpMessageNotWritableException.class, 
            HttpMessageNotReadableException.class, 
            TypeMismatchException.class, 
            ServletRequestBindingException.class, 
            MissingServletRequestParameterException.class, 
            IllegalAccessException.class, IllegalStateException.class, IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ModelAndView getExecteException(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Exception e){
        if(logger.isDebugEnabled()){
            logger.debug(e.getMessage());
        }
        //e.printStackTrace();
        return normal(e, e.getMessage(), request, response, "400");
    }
    
    @ExceptionHandler(value={
            java.lang.NoClassDefFoundError.class,
            org.eclipse.persistence.exceptions.DatabaseException.class, 
            javax.persistence.RollbackException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ModelAndView getJpaDBExecteException(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Exception e){
        if(logger.isDebugEnabled()){
            logger.debug(e.getMessage());
        }
        //e.printStackTrace();
        return normal(e, "服务器组件发生错误", request, response, "500");
    }
    private ModelAndView normal(
            Exception e, 
            String message, 
            HttpServletRequest request, 
            HttpServletResponse response, 
            String viewName){
        if(isAjaxRequest(request)){
            TipMessage tipMsg = new TipMessage(message, TipMessageLevelEnum.ERR.getLevel(), request.getRequestURL().toString());
            try {
                //可能引起FF的XML Parsing Error: not well-formed
                response.setContentType("application/json");
                response.setStatus(Integer.valueOf(viewName));
                response.setCharacterEncoding("UTF-8");
                PrintWriter writer = response.getWriter();
                writer.write(tipMsg.toJsonString());
                writer.flush();
            } catch (IOException ex) {
                if(logger.isDebugEnabled()){
                    logger.debug("["+viewName+"]输出JSON格式消息", ex);
                }
            }
            return null;
        }
        ModelAndView model=new ModelAndView();
        model.addObject("errors", message);
        model.addObject("url", request.getRequestURL());
        model.addObject("referer", request.getHeader("referer"));
        model.addObject("paramer", request.getParameterMap());
        model.addObject("exception", e);
        model.addObject("debug", logger.isDebugEnabled());
        model.addObject("BASE", siteDomain); //request.getContextPath()
        model.setViewName(viewName);
        return model;
    }
    private boolean isAjaxRequest(HttpServletRequest request){
          return ((request.getHeader("accept").contains("application/json") || (request.getHeader("X-Requested-With") != null 
                  && 
               request.getHeader("X-Requested-With").contains("XMLHttpRequest"))));
    }
}