package com.njzscloud.common.mvc.support;

import com.njzscloud.common.core.ex.CliException;
import com.njzscloud.common.core.ex.ExceptionMsg;
import com.njzscloud.common.core.ex.SysError;
import com.njzscloud.common.core.ex.SysException;
import com.njzscloud.common.core.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.*;
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.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 异常处理器
 *
 * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionController {

    /**
     * 请求方法不匹配
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e) {
        printRequest();
        String method = e.getMethod();
        String[] supportedMethods = e.getSupportedMethods();
        log.error("不支持的请求方法：【{}】仅支持：【{}】", method, supportedMethods, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "不支持的请求方法：【{}】仅支持：【{}】", method, supportedMethods);
    }

    /**
     * 内容类型不匹配
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public R<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException e) {
        printRequest();
        MediaType contentType = e.getContentType();
        List<MediaType> supportedMediaTypes = e.getSupportedMediaTypes();
        log.error("不支持的内容类型：【{}】仅支持：【{}】", contentType, supportedMediaTypes, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "不支持的内容类型：【{}】仅支持：【{}】", contentType, supportedMediaTypes);
    }

    /**
     * 未发现多部分表单
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestPartException.class)
    public R<?> missingServletRequestPartExceptionHandler(MissingServletRequestPartException e) {
        printRequest();
        String requestPartName = e.getRequestPartName();
        log.error("未发现多部分表单, 字段：【{}】", requestPartName, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "未发现多部分表单, 字段：【{}】", requestPartName);
    }

    /**
     * 缺少必要参数, 使用 @RequestParam(required = true) 的方法出现的异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
        printRequest();
        String parameterName = e.getParameterName();
        log.error("缺少必要参数, 字段：【{}】必须有值", parameterName, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "缺少必要参数, 字段：【{}】必须有值", parameterName);
    }

    /**
     * 缺少必要参数, 使用 @RequestHeader(required = true) 的方法出现的异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingRequestHeaderException.class)
    public R<?> missingRequestHeaderExceptionHandler(MissingRequestHeaderException e) {
        printRequest();
        String headerName = e.getHeaderName();
        log.error("缺少必要的请求头参数, 请求头：【{}】必须有值", headerName, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "缺少必要的请求头参数, 请求头：【{}】必须有值", headerName);
    }

    /**
     * 缺少必要的请求参数
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ServletRequestBindingException.class)
    public R<?> servletRequestBindingExceptionHandler(ServletRequestBindingException e) {
        printRequest();
        log.error("缺少必要的请求参数", e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "缺少必要参数,【{}】", e.getMessage());
    }

    /**
     * 方法参数类型与给定的参数类型不匹配
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException e) {
        printRequest();
        String name = e.getName();
        Object value = e.getValue();
        Class<?> requiredType = e.getRequiredType();
        String simpleName = null;
        if (requiredType != null) {
            simpleName = requiredType.getSimpleName();
        }
        log.error("参数类型不匹配, 字段：【{}】需要的类型为：【{}】, 接收到的值为：【{}】", name, simpleName, value, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "参数类型不匹配, 字段：【{}】需要的类型为：【{}】, 接收到的值为：【{}】", name, simpleName, value);
    }


    /**
     * 读取请求参数失败，可能为参数格式错误，POST 请求发送 JSON 数据, 数据类型不匹配、时间格式不正确等
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
        printRequest();
        log.error("读取请求体参数失败, 可能是参数格式错误", e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "读取请求体参数失败, 可能是参数格式错误.【{}】", e.getMessage());
    }

    /**
     * 注解 @RequestBody 和 @Validated 标注的对象，校验失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        printRequest();
        List<ObjectError> allErrors = e.getAllErrors();
        List<String> msg = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        log.error("参数校验失败：【{}】", msg, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, msg);
    }

    /**
     * GET 请求参数校验失败（格式、数据类型错误）
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public R<?> bindExceptionHandler(BindException e) {
        printRequest();
        List<ObjectError> allErrors = e.getAllErrors();
        List<String> msg = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        log.error("参数校验失败：【{}】", msg, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, msg);
    }


    /**
     * 404
     * <p>需要配置：</p>
     * <pre title="yml">
     * spring:
     *   web:
     *     resources:
     *       # 关闭静态资源映射
     *       add-mappings: false
     *   mvc:
     *     # 为找到的资源抛出异常
     *     throw-exception-if-no-handler-found: true</code>
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public R<?> noHandlerFoundExceptionHandler(NoHandlerFoundException e) {
        printRequest();
        String httpMethod = e.getHttpMethod();
        String requestURL = e.getRequestURL();
        log.error("资源不存在：[【{}】【{}】]", httpMethod, requestURL, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "资源不存在，【{}】【{}】", httpMethod, requestURL);
    }

    /**
     * 无法生成需要的响应结果
     */
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    public R<?> httpMediaTypeNotAcceptableExceptionHandler(HttpMediaTypeNotAcceptableException e) {
        printRequest();
        log.error("无法生成需要的响应结果", e);
        return R.failed(ExceptionMsg.SYS_ERR_MSG, "无法生成需要的响应结果，{}", e.getMessage());
    }

    /**
     * 路径参数名称不匹配
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingPathVariableException.class)
    public R<?> missingPathVariableExceptionHandler(MissingPathVariableException e) {
        printRequest();
        String variableName = e.getVariableName();
        log.error("路径参数名称与 URL 模板不匹配, 需要的路径参数名称：【{}】, URL 模板中未找到此名称对应的参数", variableName, e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, "路径参数名称与 URL 模板不匹配, 需要的路径参数名称：【{}】, URL 模板中未找到此名称对应的参数", variableName);
    }

    /**
     * 没有合适的转换器
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(ConversionNotSupportedException.class)
    public R<?> conversionNotSupportedExceptionHandler(ConversionNotSupportedException e) {
        printRequest();
        log.error("没有合适的转换器", e);
        return R.failed(ExceptionMsg.SYS_ERR_MSG, "没有合适的转换器,【{}】", e.getMessage());
    }

    /**
     * 写出响应信息时出现错误
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(HttpMessageNotWritableException.class)
    public R<?> httpMessageNotWritableExceptionHandler(HttpMessageNotWritableException e) {
        printRequest();
        log.error("写出响应信息时出现错误", e);
        return R.failed(ExceptionMsg.SYS_ERR_MSG, "响应时出现错误,【{}】", e.getMessage());
    }

    /**
     * 异步请求超时
     */
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public R<?> asyncRequestTimeoutExceptionHandler(AsyncRequestTimeoutException e) {
        printRequest();
        log.error("下游服务响应超时", e);
        return R.failed(ExceptionMsg.SYS_ERR_MSG, "服务器错误, 下游服务响应超时。{}", e.getMessage());
    }

    /**
     * 参数错误
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public R<?> cliExceptionHandler(IllegalArgumentException e) {
        printRequest();
        log.error("客户端参数错误", e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, e.getMessage());
    }

    /**
     * 客户端错误
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(CliException.class)
    public R<?> cliExceptionHandler(CliException e) {
        printRequest();
        log.error(e.getMessage(), e);
        return R.failed(ExceptionMsg.CLI_ERR_MSG, e.message);
    }

    /**
     * 系统异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(SysException.class)
    public R<?> sysExceptionHandler(SysException e) {
        printRequest();
        log.error(e.getMessage(), e);
        return R.failed(e.expect, e.msg, e.message);
    }

    /**
     * 系统错误
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SysError.class)
    public R<?> sysErrorHandler(SysError e) {
        printRequest();
        log.error(e.getMessage(), e);
        return R.failed(e.expect, e.msg, e.message);
    }

    /**
     * 服务器错误
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public R<?> exceptionHandler(Throwable e) {
        printRequest();
        log.error("内部服务错误", e);
        return R.failed(ExceptionMsg.SYS_ERR_MSG, "内部服务错误");
    }

    /**
     * 打印日志
     */
    private void printRequest() {
        try {
            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            String method = request.getMethod();
            String requestURI = request.getRequestURI();
            log.error("接口请求异常：【{}】【{}】", method, requestURI);
        } catch (Throwable e) {
            log.error("接口请求信息打印失败", e);
        }
    }
}
