package cn.palmte.work.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.palmte.work.ErrorMessage;
import cn.palmte.work.ErrorMessageException;
import cn.palmte.work.Json;
import cn.palmte.work.Result;
import cn.palmte.work.ValidationError;

/**
 * 异常处理
 *
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
 * @since 2.0 2022/12/30 15:24
 */
@RestControllerAdvice
public class ApplicationExceptionHandler {
  private static final Logger log = LoggerFactory.getLogger(ApplicationExceptionHandler.class);

  public static final ErrorMessage argsErrorMessage = ErrorMessage.failed("参数错误");
  public static final ErrorMessage sizeExceeded = ErrorMessage.failed("上传文件大小超出限制");
  public static final ErrorMessage methodNotSupported = ErrorMessage.failed("请求方式不支持");
  public static final ErrorMessage internalServerError = ErrorMessage.failed("服务器内部异常");
  public static final ErrorMessage notWritableError = ErrorMessage.failed("数据无法正常返回到客户端");

  @ExceptionHandler(ErrorMessageException.class)
  public ResponseEntity<ErrorMessage> errorMessage(ErrorMessageException errorMessage) {
    HttpStatus httpStatus = errorMessage.getStatus();
    return ResponseEntity.status(httpStatus)
            .body(ErrorMessage.failed(errorMessage.getMessage()));
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(IllegalArgumentException.class)
  public ErrorMessage badRequest(IllegalArgumentException exception) {
    return ErrorMessage.failed(exception.getMessage());
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({ MaxUploadSizeExceededException.class })
  public ErrorMessage badRequest() {
    return sizeExceeded;
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  public ErrorMessage methodNotSupported() {
    return methodNotSupported;
  }

  @ExceptionHandler
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ErrorMessage error(Exception exception) {
    log.error("An Exception occurred", exception);
    if (exception instanceof SQLException) {
      return internalServerError;
    }
    if (exception instanceof HttpMessageNotWritableException) {
      return notWritableError;
    }
    if (exception instanceof TransactionSystemException) {
      return ErrorMessage.failed("数据库出错");
    }
    return ErrorMessage.failed(exception.getMessage());
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MethodArgumentTypeMismatchException.class)
  public ErrorMessage typeMismatch(MethodArgumentTypeMismatchException mismatch) {
    return ErrorMessage.failed("参数'" + mismatch.getName() + "'不能转换到对应类型");
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MissingServletRequestParameterException.class)
  public ErrorMessage parameterError(MissingServletRequestParameterException e) {
    return ErrorMessage.failed("缺少参数'" + e.getParameterName() + "'");
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({
          MultipartException.class,
          HttpMessageNotReadableException.class,
          HttpMediaTypeNotSupportedException.class
  })
  public ErrorMessage messageNotReadable() {
    return argsErrorMessage;
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({ BindException.class, MethodArgumentNotValidException.class })
  public Result validExceptionHandler(Exception e) {

    BindingResult result;
    if (e instanceof MethodArgumentNotValidException) {
      result = ((MethodArgumentNotValidException) e).getBindingResult();
    }
    else if (e instanceof BindException) {
      result = (BindingResult) e;
    }
    else {
      return ErrorMessage.failed();
    }
    List<ObjectError> allErrors = result.getAllErrors();
    Map<String, String> model = new HashMap<>(16);

    for (ObjectError error : allErrors) {
      if (error instanceof FieldError) {
        FieldError fieldError = (FieldError) error;
        String field = fieldError.getField();
        String defaultMessage = error.getDefaultMessage();
        model.put(field, defaultMessage);
        // log.error("[{}] -> [{}]", field, defaultMessage);
      }
    }
    return ValidationError.failed(model);
  }

  @ExceptionHandler(NullPointerException.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public Json nullPointer(NullPointerException exception) {
    log.error("An Exception occurred", exception);
    final StackTraceElement[] stackTrace = exception.getStackTrace();
    if (ObjectUtils.isEmpty(stackTrace)) {
      return Json.failed("空指针", "暂无堆栈信息");
    }
    return Json.failed("空指针", stackTrace[0]);
  }

  @ExceptionHandler(DataAccessException.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ErrorMessage dataAccessException(DataAccessException accessException) {
    String message = getDataAccessMessage(accessException.getCause());
    log.error(message, accessException);
    return ErrorMessage.failed(message);
  }

  String getDataAccessMessage(Throwable cause) {
    return "数据库出错";
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(DataAccessResourceFailureException.class)
  public ErrorMessage dataAccessException(DataAccessResourceFailureException accessException) {
    log.error("数据库连接出错", accessException);
    return ErrorMessage.failed("数据库连接出错");
  }
}
