package com.copy.trade.handler;


import com.copy.trade.core.ErrorResult;
import com.copy.trade.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * 统一异常处理类
 *
 * @author
 * @date 2018/9/7 01:41
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final MessageSource ms;

    public GlobalExceptionHandler(MessageSource ms) {
        this.ms = ms;
    }

    /**
     * 错误信息展示顺序，越排在后面的错误类型，越优先展示
     */
    private static final List<String> ERROR_ORDER = new ArrayList<>(Arrays.asList("Email", "Pattern", "Past",
            "PastOrPresent", "FutureOrPresent", "Future", "Positive", "PositiveOrZero", "NegativeOrZero", "Negative",
            "Digits", "Size", "DecimalMin", "DecimalMax", "Min", "Max", "AssertFalse", "AssertTrue", "NotBlank",
            "NotEmpty", "Null", "NotNull"));

    /**
     * 基本的请求错误，用于处理无法处理的请求
     * 请求Method不匹配(eg: GET POST)
     * 请求头的Accept不匹配
     * 不存在的请求
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {
            // 不支持的请求类型
            HttpRequestMethodNotSupportedException.class,
            //基本的请求错误，用于处理无法处理的请求
            HttpMediaTypeNotSupportedException.class,
            // 不能处理的请求类型
            HttpMediaTypeNotAcceptableException.class,
            // 没有该请求
            NoHandlerFoundException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public final ErrorResult handleException(ServletException ex) {
        return return404(ex);
    }


    /**
     * 参数有误，http解析失败
     * requestBody请求时，格式为非有效的json字符串时，报出的异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public final ErrorResult handleException(HttpMessageNotReadableException ex) {
        String err = "sys.common.400";
        String msg = getMsg(err);
        log.error(msg, ex);
        return new ErrorResult(HttpStatus.BAD_REQUEST.value(), err, msg);
    }


    /**
     * requireparam 字段缺失
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {
            MissingServletRequestParameterException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public final ErrorResult handleException(MissingServletRequestParameterException ex){
        String code = "chk.common.format";
        String msg = getMsg(code, ex.getParameterName());
        log.error(ex.getMessage(), ex);
        return new ErrorResult(HttpStatus.BAD_REQUEST.value(), code, msg);
    }

    /**
     * requireparam 格式不匹配
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {
            MethodArgumentTypeMismatchException.class
    })
    @ResponseStatus(HttpStatus.OK)
    public final ErrorResult handleException(MethodArgumentTypeMismatchException ex){
        String code = "chk.common.format";
        String msg = getMsg(code, ex.getName());
        log.error(ex.getMessage(), ex);
        return new ErrorResult(HttpStatus.BAD_REQUEST.value(), code, msg);
    }



    /**
     * 基本参数错误
     * 一般用于query和普通表单参数的绑定异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {BindException.class})
    @ResponseStatus(HttpStatus.OK)
    public final ErrorResult handleException(BindException ex) {

        // 按类中成员变量的声明顺序排序所有错误信息
        List<FieldError> errors = getSortedFieldErrors(ex);

        return parseFirstError(ex, errors);
    }

    /**
     * 基本参数错误
     * 一般用于body参数的绑定异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public final ErrorResult handleException(MethodArgumentNotValidException ex) {

        final BindingResult bindingResult = ex.getBindingResult();

        // 按类中成员变量的声明顺序排序所有错误信息
        List<FieldError> errors = getSortedFieldErrors(bindingResult);

        return parseFirstError(ex, errors);
    }


    /**
     * 基本的框架自定义错误
     * 包含：
     * CheckException、ServiceException、SysException
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {ServiceException.class})
    @ResponseStatus(HttpStatus.OK)
    public final ErrorResult handleException(ServiceException ex, ServletWebRequest request) {
        String msg = getMsg(ex.getCode(), ex.getArgs());
        // 已知的业务异常仅警告
        log.warn(String.format("request %s => %s", request.getRequest().getRequestURI(), msg), ex);
        //return new ErrorResult(ex.getStatusCode(), ex.getCode(), msg);
        return new ErrorResult(HttpStatus.OK.value(), ex.getCode(), msg);
    }



    /**
     * 数据库级别的防重复记录
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {DuplicateKeyException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public final ErrorResult handleException(DuplicateKeyException ex) {
        int code = HttpStatus.BAD_REQUEST.value();
        String errCode = "chk.common.duplicate";
        String msg = getMsg(errCode);
        log.error(msg, ex);
        return new ErrorResult(code, errCode, msg);
    }


    /**
     * 系统异常，此时异常信息需要对外不可见
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public final ErrorResult handleException(Exception ex) {
        String err = "sys.common.500";
        String msg = getMsg(err);
        log.error(ex.getMessage(), ex);
        return new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), err, msg);
    }

    /**
     * 通过对错误信息的排序，来达到每次请求错误提示一致的问题
     * 解决dto包含多个注解，并同时出错时，error信息排序随机的问题
     *
     * @param ex
     * @return
     */
    private List<FieldError> getSortedFieldErrors(BindingResult ex) {

        List<FieldError> errors = ex.getFieldErrors();

        Object dto = ex.getTarget();

        final Field[] declaredFields = dto.getClass().getDeclaredFields();

        List<String> fieldOrder = new ArrayList<>();

        for (Field field : declaredFields) {
            fieldOrder.add(field.getName());
        }

        errors = new ArrayList<>(errors);

        //按参数在DTO中定义的顺序，显示错误信息
        errors.sort((a, b) -> {
            if (!a.getField().equals(b.getField())) {
                return fieldOrder.indexOf(a.getField()) - fieldOrder.indexOf(b.getField());
            } else {
                return ERROR_ORDER.indexOf(b.getCode()) - ERROR_ORDER.indexOf(a.getCode());
            }
        });

        return errors;
    }

    private ErrorResult parseFirstError(Exception ex, List<FieldError> errors) {

        FieldError error = errors.get(0);

        String misMatch = "typeMismatch";

        String err;
        Object[] args;

        if (misMatch.equals(error.getCode())) {
            err = "chk.common.format";
            args = new Object[]{error.getField()};
        } else {
            err = error.getDefaultMessage();
            args = error.getArguments();
        }

        String msg = getMsg(err, args);

        log.error(ex.getMessage(), ex);

        return new ErrorResult(HttpStatus.BAD_REQUEST.value(), err, msg);
    }


    /**
     * 不可处理的请求
     *
     * @param ex
     * @return
     */
    private ErrorResult return404(Exception ex) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        log.warn("requestURI {} {}?{}", request.getMethod(), request.getRequestURI(), request.getQueryString());
        String err = "sys.common.404";
        String msg = getMsg(err);
        log.warn(msg, ex);
        return new ErrorResult(HttpStatus.NOT_FOUND.value(), err, msg);
    }

    /**
     * 国际化方法
     *
     * @param code
     * @param args
     * @return
     */
    protected String getMsg(String code, Object... args) {
        Locale locale = LocaleContextHolder.getLocale();
        try {
            return ms.getMessage(code, args, locale);
        } catch (NoSuchMessageException e) {
            // 兼容直接传入中文文案/动态字符串的业务异常
            return code;
        }
    }

    protected String getMsg(Locale locale, String code, Object... args) {
        try {
            return ms.getMessage(code, args, locale);
        } catch (NoSuchMessageException e) {
            return code;
        }
    }

}
