/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.common.config;

import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义错误处理器类
 * 该类用于统一处理项目中出现的各种异常情况，通过`@ExceptionHandler`注解定义不同的异常处理方法，针对不同类型的异常进行相应的处理，
 * 比如提取异常信息、构建合适的响应实体对象等，最终返回包含错误信息的响应给客户端，以提供更友好、规范的错误提示，增强系统的稳定性和用户体验。
 *
 * @author LGH
 */
@Slf4j
// 使用 @Slf4j 注解，由 lombok 自动生成一个名为 log 的日志记录器对象，方便在类中记录日志信息
@Controller
// 使用 @Controller 注解声明该类是一个Spring MVC中的控制器类，虽然这里主要功能是异常处理，但可能涉及到一些视图相关的处理逻辑（如果有的话）
@RestControllerAdvice
// 使用 @RestControllerAdvice 注解，使得该类可以作为一个全局的异常处理类，能够捕获并处理整个项目中不同控制器层抛出的异常
public class DefaultExceptionHandlerConfig {

    /**
     * 处理方法参数校验不合法异常的方法
     * 该方法用于处理`MethodArgumentNotValidException`（方法参数校验不合法异常，例如使用`@Valid`等注解进行参数校验失败时抛出）和`BindException`（数据绑定异常，类似的参数校验相关异常情况）这两种异常。
     * 它会从异常中提取出字段校验错误信息，构建包含错误信息的响应实体对象后返回给客户端。
     *
     * @param e 捕获到的异常对象，可能是`MethodArgumentNotValidException`或者`BindException`类型。
     * @return 返回一个`ResponseEntity<ServerResponseEntity<List<String>>>`类型的响应，包含了处理后的错误信息列表以及对应的状态码（这里统一设置为`HttpStatus.OK`），若提取错误信息出现异常则返回默认的参数校验失败的通用响应。
     */
    @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
    public ResponseEntity<ServerResponseEntity<List<String>>> methodArgumentNotValidExceptionHandler(Exception e) {
        log.error("methodArgumentNotValidExceptionHandler", e);
        List<FieldError> fieldErrors = null;
        // 判断异常类型是否为 MethodArgumentNotValidException，如果是，则从该异常中获取字段校验错误信息列表
        if (e instanceof MethodArgumentNotValidException) {
            fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
        }
        // 判断异常类型是否为 BindException，如果是，则从该异常中获取字段校验错误信息列表
        if (e instanceof BindException) {
            fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
        }
        // 如果没有获取到字段校验错误信息列表（可能异常类型不匹配等原因），则直接返回参数校验失败的通用响应
        if (fieldErrors == null) {
            return ResponseEntity.status(HttpStatus.OK)
                   .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID));
        }

        List<String> defaultMessages = new ArrayList<>(fieldErrors.size());
        // 遍历字段校验错误信息列表，将每个字段的名称和对应的错误提示信息拼接成字符串后添加到错误信息列表中
        for (FieldError fieldError : fieldErrors) {
            defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());
        }
        // 返回包含具体字段校验错误信息列表以及参数校验失败状态码的响应实体对象
        return ResponseEntity.status(HttpStatus.OK)
               .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages));
    }

    /**
     * 处理自定义业务绑定异常的方法
     * 该方法用于处理`YamiShopBindException`（项目自定义的业务绑定异常，可能在业务逻辑中根据特定情况抛出，比如数据关联异常等情况）异常。
     * 它会根据异常中是否已经包含了自定义的响应实体对象来决定返回内容，如果有则直接返回，否则根据异常的代码和消息构建响应实体对象后返回给客户端。
     *
     * @param e 捕获到的`YamiShopBindException`异常对象。
     * @return 返回一个`ResponseEntity<ServerResponseEntity<?>>`类型的响应，包含了处理后的业务绑定异常相关的错误信息以及对应的状态码（这里统一设置为`HttpStatus.OK`）。
     */
    @ExceptionHandler(YamiShopBindException.class)
    public ResponseEntity<ServerResponseEntity<?>> unauthorizedExceptionHandler(YamiShopBindException e) {
        log.error("mall4jExceptionHandler", e);

        ServerResponseEntity<?> serverResponseEntity = e.getServerResponseEntity();
        if (serverResponseEntity!= null) {
            return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity);
        }
        // 失败返回消息，状态码固定为直接显示消息的状态码，根据异常的代码和消息构建响应实体对象并返回
        return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(), e.getMessage()));
    }

    /**
     * 处理其他通用异常的方法
     * 该方法用于处理除了上述特定异常之外的其他所有异常情况，会先判断异常是否为资源未找到异常（`NoResourceFoundException`），如果是则直接返回包含对应错误消息的响应，
     * 否则记录异常日志，并返回包含通用异常状态码和提示信息的响应实体对象给客户端。
     *
     * @param e 捕获到的其他通用异常对象，即除了前面已经处理的特定异常之外的任何异常。
     * @return 返回一个`ResponseEntity<ServerResponseEntity<Object>>`类型的响应，包含了处理后的通用异常相关的错误信息以及对应的状态码（这里统一设置为`HttpStatus.OK`）。
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ServerResponseEntity<Object>> exceptionHandler(Exception e) {
        if (e instanceof NoResourceFoundException) {
            return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage()));
        }
        log.error("exceptionHandler", e);
        return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION));
    }
}