package com.njcn.gather.system.config.handler;

import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil;
import com.njcn.gather.system.log.service.ISysLogAuditService;
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
import com.njcn.web.utils.HttpResultUtil;
import com.njcn.web.utils.HttpServletUtil;
import com.njcn.web.utils.ReflectCommonUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.util.NestedServletException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 全局通用业务异常处理器
 *
 * @author hongawen
 * @version 1.0.0
 * @date 2021年04月20日 18:04
 */
@Slf4j
@AllArgsConstructor
@RestControllerAdvice
public class GlobalBusinessExceptionHandler {

    @Resource
    private final ISysLogAuditService sysLogAuditService;

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            4, 8, 30, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            // 队列满时由主线程执行
            new ThreadPoolExecutor.CallerRunsPolicy()
    );


    /**
     * 捕获业务功能异常，通常为业务数据抛出的异常
     *
     * @param businessException 业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public HttpResult<String> handleBusinessException(BusinessException businessException) {
        String operate = ReflectCommonUtil.getMethodDescribeByException(businessException);
        recodeBusinessExceptionLog(businessException, businessException.getMessage());
        return HttpResultUtil.assembleBusinessExceptionResult(businessException, null, operate);
    }


    /**
     * 空指针异常捕捉
     *
     * @param nullPointerException 空指针异常
     */
    @ExceptionHandler(NullPointerException.class)
    public HttpResult<String> handleNullPointerException(NullPointerException nullPointerException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage(), nullPointerException);
        recodeBusinessExceptionLog(nullPointerException, CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NULL_POINTER_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(nullPointerException));
    }

    /**
     * 算数运算异常
     *
     * @param arithmeticException 算数运算异常，由于除数为0引起的异常
     */
    @ExceptionHandler(ArithmeticException.class)
    public HttpResult<String> handleArithmeticException(ArithmeticException arithmeticException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage(), arithmeticException);
        recodeBusinessExceptionLog(arithmeticException, CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ARITHMETIC_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(arithmeticException));
    }

    /**
     * 类型转换异常捕捉
     *
     * @param classCastException 类型转换异常
     */
    @ExceptionHandler(ClassCastException.class)
    public HttpResult<String> handleClassCastException(ClassCastException classCastException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage(), classCastException);
        recodeBusinessExceptionLog(classCastException, CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.CLASS_CAST_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(classCastException));
    }


    /**
     * 索引下标越界异常捕捉
     *
     * @param indexOutOfBoundsException 索引下标越界异常
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public HttpResult<String> handleIndexOutOfBoundsException(IndexOutOfBoundsException indexOutOfBoundsException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage(), indexOutOfBoundsException);
        recodeBusinessExceptionLog(indexOutOfBoundsException, CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(indexOutOfBoundsException));
    }

    /**
     * 文件未找到异常捕捉
     *
     * @param noSuchFileException 文件未找到异常
     */
    @ExceptionHandler(NoSuchFileException.class)
    public HttpResult<String> handleNoSuchFileException(NoSuchFileException noSuchFileException) {
        String filePath = noSuchFileException.getFile();
        // 日志记录详细路径，用于后端排查
        log.warn("文件未找到异常 - 文件路径: {}", filePath, noSuchFileException);
        recodeBusinessExceptionLog(noSuchFileException, SystemResponseEnum.FILE_NOT_FOUND.getMessage());
        return HttpResultUtil.assembleResult(SystemResponseEnum.FILE_NOT_FOUND.getCode(), null, 
                StrFormatter.format("{}{}{}", ReflectCommonUtil.getMethodDescribeByException(noSuchFileException), 
                        StrUtil.C_COMMA, SystemResponseEnum.FILE_NOT_FOUND.getMessage()));
    }

    /**
     * 文件IO异常捕捉（通用IOException，排除NoSuchFileException）
     *
     * @param ioException IO异常
     */
    @ExceptionHandler(IOException.class)
    public HttpResult<String> handleIOException(IOException ioException) {
        // 排除 NoSuchFileException，已由专门的处理器处理
        if (ioException instanceof NoSuchFileException) {
            return handleNoSuchFileException((NoSuchFileException) ioException);
        }
        // 日志记录详细异常信息，用于后端排查
        LogUtil.logExceptionStackInfo(SystemResponseEnum.FILE_IO_ERROR.getMessage(), ioException);
        recodeBusinessExceptionLog(ioException, SystemResponseEnum.FILE_IO_ERROR.getMessage());
        return HttpResultUtil.assembleResult(SystemResponseEnum.FILE_IO_ERROR.getCode(), null, 
                StrFormatter.format("{}{}{}", ReflectCommonUtil.getMethodDescribeByException(ioException), 
                        StrUtil.C_COMMA, SystemResponseEnum.FILE_IO_ERROR.getMessage()));
    }

    /**
     * 前端请求后端，请求中参数的媒体方式不支持异常
     *
     * @param httpMediaTypeNotSupportedException 请求中参数的媒体方式不支持异常
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public HttpResult<String> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage(), httpMediaTypeNotSupportedException);
        // 然后提取错误提示信息进行返回
        recodeBusinessExceptionLog(httpMediaTypeNotSupportedException, CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(httpMediaTypeNotSupportedException));
    }

    /**
     * 前端请求后端，参数校验异常捕捉
     * RequestBody注解参数异常
     *
     * @param methodArgumentNotValidException 参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public HttpResult<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException) {
        // 从异常对象中拿到allErrors数据
        String messages = methodArgumentNotValidException.getBindingResult().getAllErrors()
                .stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining("；"));
        // 然后提取错误提示信息进行返回
        LogUtil.njcnDebug(log, "参数校验异常，异常为：{}", messages);
        recodeBusinessExceptionLog(methodArgumentNotValidException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages, ControllerUtil.getMethodArgumentNotValidException(methodArgumentNotValidException));
    }

    /**
     * 前端请求后端，参数校验异常捕捉
     * PathVariable注解、RequestParam注解参数异常
     *
     * @param constraintViolationException 参数校验异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public HttpResult<String> constraintViolationExceptionExceptionHandler(ConstraintViolationException constraintViolationException) {
        String exceptionMessage = constraintViolationException.getMessage();
        StringBuilder messages = new StringBuilder();
        if (exceptionMessage.indexOf(StrUtil.COMMA) > 0) {
            String[] tempMessage = exceptionMessage.split(StrUtil.COMMA);
            Stream.of(tempMessage).forEach(message -> {
                messages.append(message.substring(message.indexOf(StrUtil.COLON) + 2)).append(';');
            });
        } else {
            messages.append(exceptionMessage.substring(exceptionMessage.indexOf(StrUtil.COLON) + 2));
        }
        // 然后提取错误提示信息进行返回
        LogUtil.njcnDebug(log, "参数校验异常，异常为：{}", messages);
        recodeBusinessExceptionLog(constraintViolationException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage());
        List<ConstraintViolation<?>> constraintViolationList = new ArrayList<>(constraintViolationException.getConstraintViolations());
        ConstraintViolation<?> constraintViolation = constraintViolationList.get(0);
        Class<?> rootBeanClass = constraintViolation.getRootBeanClass();
        //判断校验参数异常捕获的根源是controller还是service处
        if (rootBeanClass.getName().endsWith("Controller")) {
            String methodName = constraintViolation.getPropertyPath().toString().substring(0, constraintViolation.getPropertyPath().toString().indexOf(StrUtil.DOT));
            return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByClassAndMethodName(rootBeanClass, methodName));
        } else {
            return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByException(constraintViolationException));
        }

    }


    /**
     * 索引下标越界异常捕捉
     *
     * @param illegalArgumentException 参数校验异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public HttpResult<String> handleIndexOutOfBoundsException(IllegalArgumentException illegalArgumentException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage(), illegalArgumentException);
        recodeBusinessExceptionLog(illegalArgumentException, CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION, illegalArgumentException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(illegalArgumentException));
    }


    /**
     * 未声明异常捕捉
     *
     * @param exception 未声明异常
     */
    @ExceptionHandler(Exception.class)
    public HttpResult<String> handleException(Exception exception) {
        //针对fallbackFactory降级异常特殊处理
        Exception tempException = exception;
        String exceptionCause = CommonResponseEnum.UN_DECLARE.getMessage();
        String code = CommonResponseEnum.UN_DECLARE.getCode();
        if (exception instanceof NestedServletException) {
            Throwable cause = exception.getCause();
            if (cause instanceof AssertionError) {
                if (cause.getCause() instanceof BusinessException) {
                    tempException = (BusinessException) cause.getCause();
                    BusinessException tempBusinessException = (BusinessException) cause.getCause();
                    exceptionCause = tempBusinessException.getMessage();
                    code = tempBusinessException.getCode();
                }
            }
        }
        LogUtil.logExceptionStackInfo(exceptionCause, tempException);
        recodeBusinessExceptionLog(exception, exceptionCause);
        //判断方法上是否有自定义注解，做特殊处理
//        Method method = ReflectCommonUtil.getMethod(exception);
//        if (!Objects.isNull(method)){
//            if(method.isAnnotationPresent(ReturnMsg.class)){
//                return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}",exceptionCause));
//            }
//        }
        return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}{}{}", ReflectCommonUtil.getMethodDescribeByException(tempException), StrUtil.C_COMMA, exceptionCause));
    }


    /**
     * json解析异常
     *
     * @param jsonException json参数
     */
    @ExceptionHandler(JSONException.class)
    public HttpResult<String> handleIndexOutOfBoundsException(JSONException jsonException) {
        LogUtil.logExceptionStackInfo(CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage(), jsonException);
        recodeBusinessExceptionLog(jsonException, CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage());
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.JSON_CONVERT_EXCEPTION, jsonException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(jsonException));
    }

    private void recodeBusinessExceptionLog(Exception businessException, String methodDescribe) {
        HttpServletRequest httpServletRequest = HttpServletUtil.getRequest();
        Future<?> future = executor.submit(() -> {
            HttpServletUtil.setRequest(httpServletRequest);
            sysLogAuditService.recodeBusinessExceptionLog(businessException, methodDescribe);
        });
        try {
            // 抛出 ExecutionException
            future.get();
        } catch (ExecutionException | InterruptedException e) {
            log.error("保存审计日志异常，异常为：" + e.getMessage());
        }
    }

}
