package cn.com.poc.common.handler;

import cn.com.yict.framemax.core.exception.BusinessException;
import cn.com.yict.framemax.core.i18n.I18nMessageException;
import cn.com.yict.framemax.core.exception.ErrorCoded;
import cn.com.yict.framemax.core.spring.rest.RestServiceRegistration;
import cn.com.yict.framemax.core.utils.BeanUtils;
import cn.com.yict.framemax.data.exception.ValidationException;
import cn.com.yict.framemax.security.oauth.OauthAccesstokenFilter;
import cn.com.yict.framemax.security.oauth.exception.TokenExpiredException;
import cn.com.yict.framemax.web.utils.HttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.PriorityOrdered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 统一异常处理器
 *
 * @author Focan Zhong
 * @create 2021/8/7
 * <p>
 * 框架统一异常处理器
 * @see cn.com.yict.framemax.core.spring.rest.RestHandlerExceptionResolver
 */
public class RestHandlerExceptionResolver implements HandlerExceptionResolver, PriorityOrdered {

    private int order = -10;
    private Map<Integer, String> statusCodeMap = new HashMap<Integer, String>() {
        private static final long serialVersionUID = 1L;

        {
            this.put(HttpServletResponse.SC_UNAUTHORIZED,
                    "NoneLoginException,AuthenticationException");
        }
    };
    private int statusCode = HttpServletResponse.SC_OK;

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (!RestServiceRegistration.isRestHandler(handler)) {
            return null;
        }
        Throwable throwable = ex;
        if (ex.getCause() != null) {
            throwable = ex.getCause();
        }
        String simpleName = ',' + throwable.getClass().getSimpleName();
        String name = ',' + throwable.getClass().getName();
        int sc = statusCode;
        for (Map.Entry<Integer, String> entry : statusCodeMap.entrySet()) {
            String source = ',' + entry.getValue();
            if (source.contains(simpleName) || source.contains(name)) {
                sc = entry.getKey();
                break;
            }
        }
        request.setAttribute(cn.com.yict.framemax.core.spring.rest.RestHandlerExceptionResolver.class.getName(), throwable.getMessage());
        return new ModelAndView(new RestExceptionView(throwable, sc));
    }

    public static class RestExceptionView implements View {

        /**
         * Log variable for all child classes.
         */
        protected final Logger log = LoggerFactory.getLogger(this.getClass());

        private Throwable ex;
        private int statusCode;

        public RestExceptionView(Throwable ex) {
            this(ex, HttpServletResponse.SC_OK);
        }

        public RestExceptionView(Throwable ex, int statusCode) {
            this.ex = ex;
            this.statusCode = statusCode;
        }

        @Override
        public String getContentType() {
            return "application/json;charset=utf-8";
        }

        @Override
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("code", -1);
            map.put("message", BeanUtils.ifnull(ex.getMessage(), ex.toString()));
            if (ex instanceof ValidationException) {
                ValidationException ve = (ValidationException) ex;
                map.put("errors", ve.getErrors());
            }
            if (ex instanceof ErrorCoded) {
                ErrorCoded eec = (ErrorCoded) ex;
                map.put("code", eec.getErrorCode());
            }
            if (ex instanceof TokenExpiredException) {
                map.put("code", OauthAccesstokenFilter.USERNAME_PWD_ERROR_CODE);
            }
            if (ex instanceof BusinessException) {
                map.put("message", "业务异常：[" + ex.getMessage() + "]");
            }
//            if (ex instanceof IllegalArgumentException) {
//                map.put("message", "参数异常：[" + ex.getMessage() + "]");
//            }
            if (ex instanceof IllegalArgumentException) {
                map.put("message", "Parameter exception：[" + ex.getMessage() + "]");
            }
            if (ex instanceof I18nMessageException) {
                map.put("message", ex.getMessage());
            }

            /*
             * if (ex instanceof NoneLoginException || ex instanceof
             * AuthenticationException) {
             * response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); }else{
             * response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
             * }
             */
            response.setStatus(statusCode);
            HttpUtils.outJson(map, request, response);

            if (ex instanceof I18nMessageException) {
                log.error("业务异常：", ex);
                return;
            }
            if (ex instanceof IllegalArgumentException) {
                //若出现：Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
                //请检查url带参时 @RequestParam注解是否给name值
                log.error("参数异常：", ex);
                return;
            }
            if (ex instanceof TokenExpiredException) {
                log.error("Token异常：", ex);
                return;
            }
            log.error("系统异常：", ex);
        }

    }

    @Override
    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public void setStatusCodeMap(Map<Integer, String> statusCodeMap) {
        if (statusCodeMap != null) {
            this.statusCodeMap.putAll(statusCodeMap);
        }
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

}
