package com.hdkj.lt.core.handler;

import com.google.common.collect.ImmutableMap;
import com.hdkj.lt.base.exception.BusinessException;
import com.hdkj.lt.base.model.response.ResultCode;
import com.hdkj.hussar.ApiResponse;
import com.hdkj.hussar.IResultCode;
import com.hdkj.hussar.HussarException;
import com.ruoyi.common.core.exception.auth.NotLoginException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
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.ObjectError;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Description: 全局异常处理
 * @Author: wenhao
 * @Create: 2022/08/12 17:38
 * @Version: 1.0
 */
@Slf4j
@RestControllerAdvice
@Order(Integer.MIN_VALUE) //此注解的目的是：轻骑兵已经对全局异常做了处理，但不够全面且颗粒度不够细，此处重写让当前异常处理类优先级最高
public class GlobalExceptionHandler {

    //定义Map存放异常类型和错误代码的映射，泛型1指定异常类型，因为所有异常都继承了Throwable，泛型2指定对应错误代码
    //ImmutableMap的特点是一旦创建数据不可改变（只可插入、只读），并且线程安全
    private ImmutableMap<Class<? extends Throwable>, IResultCode> exceptions;

    //定义Map的builder对象，作用是去构建ImmutableMap
    protected static ImmutableMap.Builder<Class<? extends Throwable>, IResultCode> builder = ImmutableMap.builder();

    static {
        builder.put(HttpMessageNotReadableException.class, ResultCode.INVALID_PARAM);
        builder.put(HttpRequestMethodNotSupportedException.class, ResultCode.METHOD_NOT_SUPPORTED);
        builder.put(HttpMediaTypeNotSupportedException.class, ResultCode.MEDIA_TYPE_NOT_SUPPORTED);
        builder.put(SQLIntegrityConstraintViolationException.class, ResultCode.PARAM_VALID_ERROR); //数据库表违反唯一约束条件
        builder.put(DuplicateKeyException.class, ResultCode.PARAM_VALID_ERROR); //数据库表违反唯一约束条件
        builder.put(NotLoginException.class, ResultCode.INVALID_TOKEN); //无效token
    }

    // 参数校验
    @ResponseBody
    @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class, ConstraintViolationException.class})
    public <T> ApiResponse<T> catchParameterNotValidException(Exception e) {
        log.error("Catch ParameterNotValidException: ", e);
        String msg = null;
        // 取出异常中的错误信息
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
            msg = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining("；"));
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            msg = ex.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining("；"));
            if (StringUtils.startsWith(msg, "Failed to convert property")) {
                // 如果不是主动使用注解进行参数校验抛出的异常（如：类型转换异常等），手动去设置异常信息
                msg = ex.getFieldErrors().stream().map(item -> String.format("%s参数格式不正确", item.getField())).collect(Collectors.joining("；"));
            }
        } else if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ex = (ConstraintViolationException) e;
            msg = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("；"));
        }
        return ApiResponse.fail(ResultCode.PARAM_MISS, msg);
    }

    // 参数校验
    @ResponseBody
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public <T> ApiResponse<T> catchMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error("Catch MissingServletRequestParameterException: ", e);
        String msg = String.format("缺少请求的参数%s，类型为%s", e.getParameterName(), e.getParameterType());
        return ApiResponse.fail(ResultCode.PARAM_MISS, msg);
    }

    // 断言(断言异常在Feign的FallbackFactory捕获不到异常信息，改成抛出RuntimeException)
    @ResponseBody
    @ExceptionHandler(IllegalArgumentException.class)
    public <T> ApiResponse<T> catchIllegalArgumentException(IllegalArgumentException e) {
        log.error("Catch IllegalArgumentException: ", e);
//        return ApiResponse.fail(com.jxdinfo.hussar.platform.core.base.apiresult.ResultCode.FAILURE, e.getMessage());
        throw new RuntimeException(e.getMessage());
    }

    // 访问的资源不存在（404）
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public <T> ApiResponse<T> catchNoHandlerFoundException(NoHandlerFoundException e) {
        log.error("Catch NoHandlerFoundException: ", e);
        return ApiResponse.fail(ResultCode.NOT_FOUND);
    }

    @ResponseBody
    @ExceptionHandler({HussarException.class})
    public <T> ApiResponse<T> catchHussarException(HussarException e) {
        log.error("Catch HussarException: ", e);
        return ApiResponse.fail(e.getExceptionCode(), e.getMessage());
    }

    // 无效token未捕获到导致前端不跳转
    @ResponseBody
    @ExceptionHandler(NotLoginException.class)
    public <T> ApiResponse<T> catchNestedServletException(NotLoginException e) {
        log.error("Catch NotLoginException: ", e);
        return ApiResponse.fail(ResultCode.INVALID_LOGIN, e.getMessage());
    }

    // 捕获自定义异常，被@ExceptionHandler注解的方法指定了异常的类型，一旦抛出该类型的异常就会进入此方法，并把该异常类对象传递到此方法
    @ResponseBody
    @ExceptionHandler(BusinessException.class) //指定要捕获的异常的类型
    public <T> ApiResponse<T> catchBusinessException(BusinessException e) {
        log.error("Catch BusinessException: ", e); //记录日志
        // 取出异常中的错误代码
        IResultCode resultCode = e.getResultCode();
        if (Objects.isNull(resultCode)) {
            resultCode = new IResultCode() {
                @Override
                public int getCode() {
                    return ResultCode.SERVER_ERROR.getCode();
                }

                @Override
                public String getMessage() {
                    return e.getMessage();
                }
            };
        } else {
            if (StringUtils.isNotBlank(e.getMessage())) {
                ResultCode currentResultCode = (ResultCode) resultCode;
                resultCode = new IResultCode() {
                    @Override
                    public int getCode() {
                        return currentResultCode.getCode();
                    }

                    @Override
                    public String getMessage() {
                        return e.getMessage();
                    }
                };
            }
        }
        // 响应错误代码给前端
        return ApiResponse.fail(resultCode);
    }

    // 捕获不可预知异常，被@ExceptionHandler注解的方法指定了异常的类型，该类一旦抛出异常就会进入此方法，并把该异常类传递到此方法
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class) //指定异常的类型
    public <T> ApiResponse<T> catchException(Exception e) {
        log.error("Catch Exception: ", e); //记录日志
        if (Objects.isNull(exceptions)) {
            // 如果EXCEPTIONS还没有构建，则让其构建
            exceptions = buildExceptions();
        }
        // 从EXCEPTIONS中获取异常异常类型所对应的错误代码，如果获取到了错误代码则将其响应给前端，如果找不到则统一响应99999异常
        IResultCode resultCode = exceptions.get(e.getClass());
        if (Objects.nonNull(resultCode)) {
            return ApiResponse.fail(resultCode);
        }
        // 响应99999错误代码给前端
        return ApiResponse.fail(ResultCode.SERVER_ERROR);
    }

    private ImmutableMap<Class<? extends Throwable>, IResultCode> buildExceptions() {
        return builder.build();
    }

}
