package com.bcxin.ferry.common.exception;

import cn.hutool.core.bean.BeanException;
import cn.hutool.core.convert.ConvertException;
import com.alibaba.fastjson.JSONObject;
import com.bcxin.ferry.common.CommonConstant;
import com.bcxin.ferry.common.emus.StatusCode;
import com.bcxin.ferry.common.utils.ExceptionUtils;
import com.bcxin.ferry.dtos.response.result.ErrorRespResult;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
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.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.io.BufferedReader;
import java.io.IOException;
import java.security.SignatureException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLNonTransientConnectionException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 通用异常处理 统一拦截异常并返回标志的JSON响应 如果有开发新的异常需求，请在这边对应添加异常处理机制
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 日志
     */
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    //region 资源或方法找不到异常，返回：HttpStatus.NOT_FOUND

    /**
     * 页面不存在
     */
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorRespResult noFoundException(HttpServletRequest request, NoHandlerFoundException ex) {
        return packageResponse(request, ex, StatusCode.notfound, "访问的资源不存在");
    }

    //endregion

    //region 未授权异常 返回：HttpStatus.UNAUTHORIZED

    /**
     * 标准化返回结果
     */
    private ErrorRespResult packageResponse(HttpServletRequest request, Exception ex, StatusCode statusCode, String errorMessage) {
        printMessage(request, ex);
        saveMessage(request, ex);
//        if (ApplicationContextUtils.getConfig(AppConstants.OPEN_DEBUG_RESPONSE, false)) {
//            // 调试模式下返回明细信息
//            return ErrorRespResult.fail(statusCode, errorMessage, this.printDebugMessage(request, ex));
//        }
        return ErrorRespResult.fail(statusCode, errorMessage, this.printDebugMessage(request, ex));
    }

    /**
     * 打印错误消息
     */
    private void printMessage(HttpServletRequest request, Throwable ex) {
        //过滤掉几个不需要关注的异常
        String path = request.getRequestURI();
        if (ex == null) {
            logger.error("全局异常：path:[{}] - [未知异常]。", path);
            return;
        }
        String className = ex.getClass().getName();
//        if (WebFrameworkConstants.THOROUGH_IGNORE_EXCEPTIONS.contains(className)) {
//            //彻底忽略异常，不显示任何信息。
//            return;
//        }
//        if (WebFrameworkConstants.IGNORE_EXCEPTIONS.contains(className)) {
//            //如果忽略错误并且开启了调试，那么输出简化日志提示一下
//            if (ApplicationContextUtils.getConfig(AppConstants.OPEN_DEBUG_LOGGER, false)) {
//                logger.error("全局异常：path:[{}] - class:[{}]", path, className, ex);
//            } else {
//                logger.error("全局异常：path:[{}] - class:[{}] - ex:[{}] ", path, className, ex.getMessage());
//            }
//        } else {
//            logger.error("全局异常：path:[{}] - class:[{}] ", path, className, ex);
//        }
        logger.error("全局异常：path:[{}] - class:[{}] - ex:[{}] ", path, className, ex.getMessage());
    }

    /**
     * 保存错误消息
     */
    private void saveMessage(HttpServletRequest request, Throwable ex) {
        //取出头部信息

        //取出请求body
        String body = "";
        BufferedReader reader = null;
        try {
            reader = request.getReader();
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
            body = stringBuilder.toString();
        } catch (IOException ignored) {

        }
        //取出自定义属性
        //保存入库

    }

    /**
     * 获取DEBUG异常新
     */
    private String printDebugMessage(HttpServletRequest request, Throwable ex) {
        String path = request.getRequestURI();
        Map<String, String> debugMsg = new HashMap();
        debugMsg.put("path", path);
        debugMsg.put("exception", ExceptionUtils.getExceptionMessage(ex));
        return JSONObject.toJSONString(debugMsg);
    }


    /**
     * 数组对象处理错误
     */
    @ExceptionHandler(value = {ArrayIndexOutOfBoundsException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult arrayIndexOutOfBoundsException(HttpServletRequest request, ArrayIndexOutOfBoundsException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "系统内部错误:业务所需数据对象处理错误");
    }

    /**
     * 数据校验错误
     */
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult httpMessageNotReadableException(HttpServletRequest request,
                                                           HttpMessageNotReadableException ex) {
        return packageResponse(request, ex, StatusCode.parameterError, "请求参数类型错误");
    }

    /**
     * 数据校验错误
     */
    @ExceptionHandler(value = {BindException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult bindException(HttpServletRequest request, BindException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        return this.validException(request, ex, bindingResult);

    }

    //endregion
    //region 断言异常

    /**
     * 数据校验错误（校验器本身错误）
     */
    @ExceptionHandler(value = {ValidationException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult validationException(HttpServletRequest request, ValidationException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "系统内部错误:数据校验器执行错误");
    }

    /**
     * 数据转换异常
     */
    @ExceptionHandler(value = {ConvertException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult convertException(HttpServletRequest request, ConvertException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "系统内部错误:自动数据转换错误");
    }

    /**
     * 获取springbean出错
     */
    @ExceptionHandler(value = {BeanException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult beanException(HttpServletRequest request, BeanException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "系统内部错误:业务处理资源缺失");
    }


    /**
     * IO操作异常
     */
    @ExceptionHandler(value = {IOException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult ioException(HttpServletRequest request, IOException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "IO流操作异常");
    }


    /**
     * 错误的用户令牌
     */
    @ExceptionHandler(value = {TokenException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorRespResult tokenException(HttpServletRequest request, TokenException ex) {
        return packageResponse(request, ex, StatusCode.tokenError, "错误的用户令牌");
    }

    //endregion

    //region 数据异常（包括数据库操作）

    /**
     * 断言异常
     */
    @ExceptionHandler(value = {AssertException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult assertException(HttpServletRequest request, AssertException ex) {
        return packageResponse(request, ex, StatusCode.assertError, ex.getMessage());
    }

    /**
     * 任务执行错误
     */
    @ExceptionHandler(value = {TaskException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult taskException(HttpServletRequest request, TaskException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, ex.getMessage());
    }

    /**
     * 空指针异常
     */
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult nullPointerException(HttpServletRequest request, NullPointerException ex) {
        return packageResponse(request, ex, StatusCode.assertError, "系统内部错误:业务处理所需资源缺失（NP）");
    }

    /**
     * 参数校验错误
     */
    @ExceptionHandler(value = {ValidatedException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult validatedException(HttpServletRequest request, ValidatedException ex) {
        // 参数校验的不用返回调试模式异常
        return packageResponse(request, ex, StatusCode.parameterError, ex.getMessage());
    }

    /**
     * 违法的访问异常
     */
    @ExceptionHandler(value = {IllegalAccessException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult illegalAccessException(HttpServletRequest request, IllegalAccessException ex) {
        return packageResponse(request, ex, StatusCode.assertError, "系统内部错误:未授权业务对象访问权限");
    }

    /**
     * 参数校验错误
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult constraintViolationException(HttpServletRequest request, ConstraintViolationException ex) {
        return packageResponse(request, ex, StatusCode.parameterError, "参数校验错误");
    }



    @ExceptionHandler(value = {CannotGetJdbcConnectionException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult cannotGetJdbcConnectionException(HttpServletRequest request,
                                                            CannotGetJdbcConnectionException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "数据库操作失败");
    }

    @ExceptionHandler(value = {SQLNonTransientConnectionException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult sqlNonTransientConnectionException(HttpServletRequest request,
                                                              SQLNonTransientConnectionException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "数据库操作失败");
    }

    /**
     * 数据库操作失败
     * 违法唯一约束条件或者缺少参数
     */
    @ExceptionHandler(value = {SQLIntegrityConstraintViolationException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult integrityConstraintViolationException(HttpServletRequest request,
                                                                 SQLIntegrityConstraintViolationException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "数据库操作失败");
    }


    /**
     * 数据库操作失败
     */
    @ExceptionHandler(value = {DataIntegrityViolationException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult dataIntegrityViolationException(HttpServletRequest request,
                                                           DataIntegrityViolationException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "数据库操作失败");
    }

    /**
     * 数据库操作失败
     */
    @ExceptionHandler(value = {DataAccessException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult dataAccessException(HttpServletRequest request,
                                               DataAccessException ex) {
        return packageResponse(request, ex, StatusCode.serviceError, "数据库操作失败");
    }

    /**
     * 入参错误
     */
    @ExceptionHandler(value = {IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult illegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
        return packageResponse(request, ex, StatusCode.parameterError, "系统内部错误:业务处理所需参数错误");
    }

    /**
     * 数据签名校验失败
     */
    @ExceptionHandler(value = {SignatureException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult signatureException(HttpServletRequest request, SignatureException ex) {
        return packageResponse(request, ex, StatusCode.signatureError, "数据签名校验失败");
    }


    //endregion


    // region 未知异常


    //endregion

    /**
     * 方法没找到
     */
    @ExceptionHandler(value = {NoSuchMethodException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorRespResult noSuchMethodException(HttpServletRequest request, NoSuchMethodException ex) {
        return packageResponse(request, ex, StatusCode.notfound, "访问的资源不存在（404）");
    }


    /**
     * 没有找到对应的后缀解析器
     */
    @ExceptionHandler(value = {HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult httpRequestMethodNotSupportedException(HttpServletRequest request,
                                                                  HttpRequestMethodNotSupportedException ex) {
        return packageResponse(request, ex, StatusCode.methodNotAllowed, "错误的请求方式");
    }

    /**
     * 参数校验失败
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        return this.validException(request, ex, bindingResult);
    }

    private ErrorRespResult validException(HttpServletRequest request, Exception ex,
                                           BindingResult bindingResult) {
        if (!bindingResult.hasErrors()) {
            return ErrorRespResult.fail(StatusCode.parameterError, "参数校验失败。");
        }
        List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
        StringBuilder info = new StringBuilder("参数校验失败:");
        boolean hasMoreError = (fieldErrorList.size() > 1);
        for (int i = 0; i < fieldErrorList.size(); i++) {
            if (hasMoreError) {
                info.append(i + 1).append(":");
            }
            //标示转换失败的情况，特殊处理。
            if (StringUtils.isBlank(fieldErrorList.get(i).getDefaultMessage())) {
                continue;
            }
            if (fieldErrorList.get(i).getDefaultMessage().startsWith("Failed to convert")) {
                info.append("参数类型错误。");
            } else {
                info.append(fieldErrorList.get(i).getDefaultMessage());
                if (!fieldErrorList.get(i).getDefaultMessage().endsWith("。")) {
                    info.append("。");
                }
            }
        }
        return packageResponse(request, ex, StatusCode.parameterError, info.toString());
    }

    /**
     * 接口处理异常
     */
    @ExceptionHandler(value = {RemoteException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult remoteException(HttpServletRequest request, RemoteException ex) {
        return packageResponse(request, ex, StatusCode.remoteError, ex.getMessage());
    }

    /**
     * 其它异常
     */
    @ExceptionHandler(value = {Exception.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult exception(HttpServletRequest request, Exception exception) {
        return this.otherException(request, exception);
    }

    @ExceptionHandler(value = {RuntimeException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorRespResult runtimeException(HttpServletRequest request, Exception exception) {
        return this.otherException(request, exception);
    }

    private ErrorRespResult otherException(HttpServletRequest request, Exception exception) {
        if (exception == null) {
            return ErrorRespResult.fail(StatusCode.unknown, "未知错误（错误信息缺失）。");
        }
        // 根据更多不同的类型进行返回值的确定
        logger.error("未知错误：[{}]", exception.getClass().getName());
        //过滤几个常见的异常
        String clazz = exception.getClass().getName();
        if (clazz.startsWith("java.sql")) {
            return packageResponse(request, exception, StatusCode.serviceError, "数据库操作异常");
        }
        return packageResponse(request, exception, StatusCode.unknown, String.format("未知错误：[%s]", exception.getMessage()));

    }
}
