package com.jy.framework.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.jy.framework.model.base.R;
import com.jy.framework.model.event.ExceptionEvent;
import com.jy.framework.utils.ServletUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.warm.flow.core.exception.FlowException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.util.Objects;

/**
 * 异常拦截器
 */
@Slf4j
@RestControllerAdvice
public class UnifiedExceptionHandler {

    @Resource
    private HttpServletRequest request;

    @Resource
    private ApplicationContext applicationContext;

    /**
     * 未知接口异常
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public R handleNoHandlerFoundException() {
        return R.fail(HttpServletResponse.SC_NOT_FOUND, "未知接口");
    }

    /**
     * 自定义业务异常
     */
    @ExceptionHandler(value = ServiceException.class)
    public R handleServiceException(ServiceException e) {
        return R.fail(e.getErrorCode(), e.getMessage());
    }

    /**
     * 非法数据异常
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    public R handleIllegalArgumentException(IllegalArgumentException e) {
        return R.fail(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
    }

    /**
     * 请求参数异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error(request.getServletPath(), e);
        String message = e.getMessage();
        String errorMsg = "未知JSON异常";
        if (StrUtil.isNotBlank(message)) {
            if (message.startsWith("Required request body is missing:")) {
                errorMsg = "请求JSON为空";
            } else if (message.startsWith("JSON parse error:")) {
                errorMsg = "JSON格式错误：" + message;
            }
        }
        return R.fail(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, errorMsg);
    }

    /**
     * 请求方式异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        String method = e.getMethod();
        log.error("{} 不支持 {} 请求", request.getServletPath(), method);
        return R.fail(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "此方法不支持{}请求", method);
    }

    /**
     * 请求参数类型不匹配
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("请求参数类型不匹配'{}'", request.getServletPath(), e);
        return R.fail(String.format("请求参数类型不匹配，参数[%s]要求类型为：'%s'，但输入值为：'%s'",
                e.getName(), Objects.requireNonNull(e.getRequiredType()).getName(), e.getValue()));
    }

    /**
     * 验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        FieldError fieldError = e.getFieldErrors().get(0);
        String field = fieldError.getField();
        String defaultMessage = fieldError.getDefaultMessage();
        return R.fail(field + defaultMessage);
    }

    /**
     * 验证异常
     */
    @ExceptionHandler(BindException.class)
    public R handleBindException(BindException e) {
        FieldError fieldError = e.getFieldErrors().get(0);
        String field = fieldError.getField();
        String defaultMessage = fieldError.getDefaultMessage();
        return R.fail(field + defaultMessage);
    }

    /**
     * 其他未知异常
     */
    @ExceptionHandler(value = Exception.class)
    public R handleException(Exception e) {
        // 打印异常日志
        log.error("系统出现未知异常", e);

        // 发送异常事件
        applicationContext.publishEvent(new ExceptionEvent(e));

        return R.fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "系统出现未知异常，请联系管理员");
    }

    /**
     * 权限码异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String bestMatching = ServletUtil.getBestMatching(request);
        log.error("接口：{}，接口权限校验失败：{}", bestMatching, e.getMessage());
        return R.fail(HttpServletResponse.SC_FORBIDDEN, "没有访问权限，请联系管理员授权");
    }

    /**
     * 角色权限异常
     */
    @ExceptionHandler(NotRoleException.class)
    public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String bestMatching = ServletUtil.getBestMatching(request);
        log.error("接口：{}，角色权限校验失败：{}", bestMatching, e.getMessage());
        return R.fail(HttpServletResponse.SC_FORBIDDEN, "没有访问权限，请联系管理员授权");
    }

    /**
     * 认证失败
     */
    @ExceptionHandler(NotLoginException.class)
    public R handleNotLoginException(NotLoginException e) {
        // 如果已经登录，则强制退出
        String tokenValue = StpUtil.getTokenValue();
        StpUtil.logoutByTokenValue(tokenValue);

        int code = HttpStatus.HTTP_UNAUTHORIZED;
        return switch (e.getType()) {
            case NotLoginException.BE_REPLACED -> R.fail(code, "账号已在其他地方登录");
            case NotLoginException.KICK_OUT -> R.fail(code, "您已被强制下线");
            case NotLoginException.NO_PREFIX_MESSAGE -> R.fail(code, "未按照指定前缀提交 token");
            default -> R.fail(code, "登录已过期，请重新登录");
        };
    }

    /**
     * 角色权限异常
     */
    @ExceptionHandler(FlowException.class)
    public R handleFlowException(FlowException e) {
        log.error("流程节点异常：{}", e.getMessage(), e);
        return R.fail("流程节点异常：{}", e.getMessage());
    }

}
