/* (C) 2023 YiRing, Inc. */
package com.yiring.common.config;

import com.yiring.common.core.I18n;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.exception.BusinessException;
import com.yiring.common.exception.FailStatusException;
import jakarta.validation.ConstraintViolationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.aspectj.bridge.AbortException;
import org.hibernate.validator.internal.engine.ConstraintViolationImpl;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 核心异常拦截处理
 *
 * @author Jim
 * @version 0.1
 * 2023/1/12 14:06
 */
@Slf4j
@Order(0)
@RestControllerAdvice
@RequiredArgsConstructor
public class CoreExceptionHandler {

    final I18n i18n;

    /**
     * 参数校验异常
     *
     * @param e 异常信息
     * @return 统一的校验失败信息 {@link Status#EXPECTATION_FAILED
     */
    @ExceptionHandler(
        { BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class }
    )
    public Result<String> validFailHandler(Exception e) {
        String details = null;

        if (e instanceof ConstraintViolationException) {
            details = ((ConstraintViolationException) e).getConstraintViolations().iterator().next().getMessage();
        } else {
            BindingResult result = null;
            if (e instanceof MethodArgumentNotValidException) {
                result = ((MethodArgumentNotValidException) e).getBindingResult();
            } else if (e instanceof BindException) {
                result = ((BindException) e).getBindingResult();
            }

            if (result != null) {
                ObjectError error = result.getAllErrors().iterator().next();
                if (error instanceof FieldError fieldError) {
                    // 构建明确的字段错误提示, 例如: id 不能为 null, 如果自己填写了 message 则不追加 field 字段前缀
                    ConstraintViolationImpl<?> violation = error.unwrap(ConstraintViolationImpl.class);
                    String template = violation.getMessageTemplate();
                    String prefix = "";
                    // 如果是模板字符串, 则在消息前添加字段提示
                    if (template.contains("{") && template.contains("}")) {
                        prefix = "参数" + fieldError.getField();
                    }

                    details = prefix + i18n.get(fieldError);
                } else {
                    details = i18n.get(error);
                }
            }
        }

        return Result.no(Status.EXPECTATION_FAILED, details);
    }

    /**
     * 参数读取解析失败异常
     * eg: 例如参数使用 RequestBody 接收，但是传了个空字符串，导致解析失败
     *
     * @param e 异常
     * @return 校验失败信息 {@link Status#EXPECTATION_FAILED
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<String> httpMessageNotReadableExceptionHandler(Exception e) {
        log.warn(e.getMessage(), e);
        return Result.no(Status.EXPECTATION_FAILED);
    }

    /**
     * 不支持的HttpMethod异常
     *
     * @param e 异常信息
     * @return 异常信息反馈 {@link Status#METHOD_NOT_ALLOWED
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<String> httpRequestMethodNotSupportedErrorHandler(Exception e) {
        return Result.no(Status.METHOD_NOT_ALLOWED, e.getMessage());
    }

    /**
     * 自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<String> businessExceptionHandler(BusinessException e) {
        return Result.no(e.getStatus(), e.getMessage());
    }

    /**
     * 失败状态异常
     */
    @ExceptionHandler(FailStatusException.class)
    public Result<String> failStatusExceptionHandler(FailStatusException e) {
        return Result.no(e.getStatus(), e.getMessage());
    }

    /**
     * 取消请求异常（忽略）
     */
    @ExceptionHandler({ ClientAbortException.class, AbortException.class, HttpMessageNotWritableException.class })
    public void ignoreExceptionHandler() {}
}
