package com.usky.common.core.handler;

import com.google.common.base.Throwables;
import com.usky.common.core.bean.ApiResult;
import com.usky.common.core.exception.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
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.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

import static com.usky.common.core.exception.ErrorMessageConsts.BIZ_ERROR_LOG_MESSAGE_TEMPLATE;
import static com.usky.common.core.exception.ErrorMessageConsts.SYS_ERROR_LOG_MESSAGE_TEMPLATE;


/**
 * <p>统一异常处理</p>
 *
 * @author chenpeng
 * Create time 2018年11月30日 下午4:41:27
 * @see ErrorCode
 * @see BusinessException
 */
@ControllerAdvice
public class UnifiedExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(UnifiedExceptionHandler.class);

    private static final String EXCEPTION_MESSAGE_SEPARATOR = ":";

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(BusinessException.class)
    public ApiResult<Void> handleBusinessException(BusinessException e) {
        ErrorCode errorCode = e.getErrorCode();
        logWarn(errorCode, e, false);
        String customMessage = getSimpleCustomMessage(e.getCustomMessage());
        return ApiResult.error(errorCode.getCode(), e.getClass().getName(), errorCode.getDefaultMessage()
                + (StringUtils.isBlank(customMessage) ? StringUtils.EMPTY : EXCEPTION_MESSAGE_SEPARATOR + customMessage));
    }

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(BusinessCheckException.class)
    public ApiResult<Void> handleBusinessCheckException(BusinessCheckException e) {
        ErrorCode errorCode = e.getErrorCode();
        logError(errorCode, e, false);
        String customMessage = getSimpleCustomMessage(e.getCustomMessage());
        return ApiResult.error(errorCode.getCode(), e.getClass().getName(), errorCode.getDefaultMessage()
                + (StringUtils.isBlank(customMessage) ? StringUtils.EMPTY : EXCEPTION_MESSAGE_SEPARATOR + customMessage));
    }

    /**
     * <p>系统异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:50:45 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(ApplicationException.class)
    public ApiResult<Void> handleApplicationException(ApplicationException e) {
        ErrorCode errorCode = e.getErrorCode();
        logError(errorCode, e, true);
        return ApiResult.error(errorCode.getCode(), e.getClass().getName(), errorCode.getDefaultMessage());
    }

    /**
     * <p>系统异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:50:45 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(ApplicationCheckException.class)
    public ApiResult<Void> handleApplicationCheckException(ApplicationCheckException e) {
        ErrorCode errorCode = e.getErrorCode();
        logError(errorCode, e, true);
        return ApiResult.error(errorCode.getCode(), e.getClass().getName(), errorCode.getDefaultMessage());
    }

    /**
     * <p>最终兜底异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:50:22 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(Throwable.class)
    public ApiResult<Void> handleThrowable(Throwable e) {

        ErrorCode errorCode;

        if (e instanceof OutOfMemoryError) {
            errorCode = SystemErrorCode.SYS_SYSTEM_OOM_ERROR;
        } else if (e instanceof StackOverflowError) {
            errorCode = SystemErrorCode.SYS_SYSTEM_SOF_ERROR;
        } else {
            errorCode = SystemErrorCode.SYS_SYSTEM_ERROR;
        }

        logError(errorCode, e, true);
        return ApiResult.error(errorCode.getCode(), e.getClass().getName(), String.format("%s-%s-%s", errorCode.getDefaultMessage(),
                e.getClass().getSimpleName(), e.getMessage()));
    }

    /**
     * <p>参数异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:51:01 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(ConstraintViolationException.class)
    public ApiResult<Void> handleConstraintViolationException(ConstraintViolationException ex) {
        String errorMessage = null;
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) {
            StringBuilder messageBuilder = new StringBuilder();
            constraintViolations.forEach(cv -> messageBuilder.append(cv.getMessage()).append(","));
            errorMessage = messageBuilder.toString();
            if (errorMessage.length() > 1) {
                errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
            }
        }
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getCode(),
                BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getDefaultMessage());
        return handleBusinessException(new BusinessException(errorCode, ex, errorMessage));
    }

    /**
     * <p>方法参数异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:51:20 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public ApiResult<Void> handleMethodArgumentNotValidException(Exception ex) {
        String errorMessage = null;
        List<ObjectError> objectErrors = null;
        if (ex instanceof MethodArgumentNotValidException) {
            objectErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors();
        } else if (ex instanceof BindException) {
            objectErrors = ((BindException) ex).getBindingResult().getAllErrors();
        }
        if (!CollectionUtils.isEmpty(objectErrors)) {
            StringBuilder messageBuilder = new StringBuilder();
            objectErrors.forEach(o -> {
                if (o instanceof FieldError) {
                    messageBuilder.append(((FieldError) o).getField());
                }
                messageBuilder.append(o.getDefaultMessage()).append(",");
            });
            errorMessage = messageBuilder.toString();
            if (errorMessage.length() > 1) {
                errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
            }
        }
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getCode(),
                BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getDefaultMessage());
        return handleBusinessException(new BusinessException(errorCode, ex, errorMessage));
    }

    /**
     * <p>Servlet 注解的参数异常处理</p>
     *
     * @author chenpeng
     * Create at March 7, 2019 at 13:51:40 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ApiResult<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_LACK_NECESSARY_PARAM_ERROR.getCode(),
                BusinessErrorCode.BIZ_LACK_NECESSARY_PARAM_ERROR.getDefaultMessage());
        BusinessException be = new BusinessException(errorCode, e, e.getParameterName() + " is required!");
        return handleBusinessException(be);
    }

    /**
     * <p>Jackson Mapping 异常处理</p>
     *
     * @author chenpeng
     * Create at June 5, 2019 at 10:24:48 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ApiResult<Void> handleJsonMappingException(HttpMessageNotReadableException e) {
        Throwable rootCause = Throwables.getRootCause(e);
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getCode(),
                BusinessErrorCode.BIZ_INVALID_PARAM_ERROR.getDefaultMessage());
        BusinessException be = new BusinessException(errorCode, rootCause, rootCause.getMessage());
        return handleBusinessException(be);
    }

    /**
     * <p>Duplicate key 异常处理</p>
     *
     * @author chenpeng
     * Create at June 10, 2019 at 18:27:05 GMT+8
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(DuplicateKeyException.class)
    public ApiResult<Void> handleDuplicateKeyException(DuplicateKeyException e) {
        Throwable rootCause = Throwables.getRootCause(e);
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_DUPLICATE_KEY_ERROR.getCode(),
                BusinessErrorCode.BIZ_DUPLICATE_KEY_ERROR.getDefaultMessage());
        BusinessException be = new BusinessException(errorCode, rootCause, rootCause.getMessage());
        return handleBusinessException(be);
    }

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(NoHandlerFoundException.class)
    public ApiResult<Void> handlerNoFoundException(Exception e) {
        Throwable rootCause = Throwables.getRootCause(e);
        ErrorCode errorCode = ErrorCode.create(BusinessErrorCode.BIZ_PATH_NOT_FOUND.getCode(),
                BusinessErrorCode.BIZ_PATH_NOT_FOUND.getDefaultMessage());
        BusinessException be = new BusinessException(errorCode, rootCause, rootCause.getMessage());
        return handleBusinessException(be);
    }

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    @ExceptionHandler(value = {FeignBadRequestException.class})
    public ApiResult<Void> handleFeignException(FeignBadRequestException ex, WebRequest request) {
        ErrorCode errorCode = ErrorCode.create(SystemErrorCode.SYS_SYSTEM_FEIGN_RESULT_DECODE_ERROR.getCode(),
                SystemErrorCode.SYS_SYSTEM_FEIGN_RESULT_DECODE_ERROR.getDefaultMessage());
        BusinessException be = new BusinessException(errorCode, ex.getMessage());
        return handleBusinessException(be);
    }



    private void logError(ErrorCode errorCode, Throwable t, boolean isSystemError) {
        String logTemplate = isSystemError ? SYS_ERROR_LOG_MESSAGE_TEMPLATE : BIZ_ERROR_LOG_MESSAGE_TEMPLATE;
        LOGGER.error(String.format(logTemplate,
                errorCode.getCode(),
                t.getMessage()), t);
    }

    private void logWarn(ErrorCode errorCode, Throwable t, boolean isSystemError) {
        String logTemplate = isSystemError ? SYS_ERROR_LOG_MESSAGE_TEMPLATE : BIZ_ERROR_LOG_MESSAGE_TEMPLATE;
        LOGGER.warn(String.format(logTemplate,
                errorCode.getCode(),
                t.getMessage()), t);
    }

    private String getSimpleCustomMessage(String message) {
        String customMessage = StringUtils.isBlank(message) ? StringUtils.EMPTY : message;
        customMessage = (StringUtils.isNotBlank(customMessage) && customMessage.contains(EXCEPTION_MESSAGE_SEPARATOR))
                ? customMessage.split(EXCEPTION_MESSAGE_SEPARATOR)[0] : customMessage;
        return customMessage;
    }
}
