package tech.easyflow.common.web.error;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.exceptions.BusinessException;

/**
 * 将 Web 层异常转换为具有真实 HTTP 语义的统一 JSON 响应。
 */
public class GlobalErrorResolver implements HandlerExceptionResolver {

    private static final Logger LOG = LoggerFactory.getLogger(GlobalErrorResolver.class);
    private static final String INTERNAL_ERROR_MESSAGE = "服务暂时不可用，请稍后重试";

    /**
     * 解析控制器异常并写入 HTTP 状态和统一错误体。
     *
     * @param request 当前请求
     * @param response 当前响应
     * @param handler 发生异常的处理器
     * @param ex 原始异常
     * @return JSON 视图
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        Result<?> error;
        if (ex instanceof MissingServletRequestParameterException) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            error = Result.fail(400, ((MissingServletRequestParameterException) ex).getParameterName() + " 不能为空");
        } else if (ex instanceof NotLoginException) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            error = Result.fail(401, "请登录");
        } else if (ex instanceof NotPermissionException || ex instanceof NotRoleException) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            error = Result.fail(403, "无权操作");
        } else if (ex instanceof ConstraintViolationException || ex instanceof MethodArgumentNotValidException) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            error = Result.fail(400, ex.getMessage());
        } else if (ex instanceof MethodArgumentTypeMismatchException) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            error = Result.fail(400, "请求参数格式不正确");
        } else if (ex instanceof MaxUploadSizeExceededException) {
            response.setStatus(HttpStatus.PAYLOAD_TOO_LARGE.value());
            error = Result.fail(413, "上传文件超过大小限制");
        } else if (ex instanceof BusinessException businessException) {
            response.setStatus(businessException.getHttpStatus());
            if (businessException.getHttpStatus() >= 500) {
                LOG.error("服务端业务处理异常，method={}, uri={}, errorCode={}",
                        request.getMethod(), request.getRequestURI(), businessException.getErrorCode(), businessException);
            }
            error = Result.fail(businessException.getErrorCode(), businessException.getMessage());
        } else if (ex instanceof ResponseStatusException responseStatusException) {
            response.setStatus(responseStatusException.getStatusCode().value());
            error = Result.fail(responseStatusException.getStatusCode().value(), safeReason(responseStatusException));
        } else if (ex instanceof ErrorResponse errorResponse) {
            HttpStatusCode statusCode = errorResponse.getStatusCode();
            response.setStatus(statusCode.value());
            error = Result.fail(statusCode.value(), safeClientMessage(ex, statusCode));
        } else {
            ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
            if (responseStatus != null) {
                int status = resolveResponseStatus(responseStatus);
                response.setStatus(status);
                error = Result.fail(status, responseStatus.reason().isBlank()
                        ? safeClientMessage(ex, HttpStatusCode.valueOf(status)) : responseStatus.reason());
            } else {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                LOG.error("未处理的 Web 请求异常，method={}, uri={}", request.getMethod(), request.getRequestURI(), ex);
                error = Result.fail(500, INTERNAL_ERROR_MESSAGE);
            }
        }
        JSONObject object = JSON.parseObject(JSON.toJSONString(error));
        return new ModelAndView(new JakartaJsonView())
                .addAllObjects(object);
    }

    /**
     * 读取注解声明的 HTTP 状态。
     *
     * @param responseStatus 状态注解
     * @return HTTP 状态码
     */
    private int resolveResponseStatus(ResponseStatus responseStatus) {
        return responseStatus.code().value();
    }

    /**
     * 返回框架状态异常中可公开的错误原因。
     *
     * @param exception 状态异常
     * @return 安全客户端消息
     */
    private String safeReason(ResponseStatusException exception) {
        if (exception.getStatusCode().is5xxServerError()) {
            LOG.error("Web 请求处理失败，status={}", exception.getStatusCode().value(), exception);
            return INTERNAL_ERROR_MESSAGE;
        }
        if (exception.getReason() != null && !exception.getReason().isBlank()) {
            return exception.getReason();
        }
        HttpStatus status = HttpStatus.resolve(exception.getStatusCode().value());
        return status == null ? "请求处理失败" : status.getReasonPhrase();
    }

    /**
     * 根据状态码选择可公开消息，并记录服务端异常。
     *
     * @param exception 原始异常
     * @param statusCode HTTP 状态
     * @return 安全客户端消息
     */
    private String safeClientMessage(Exception exception, HttpStatusCode statusCode) {
        if (statusCode.is5xxServerError()) {
            LOG.error("Web 请求处理失败，status={}", statusCode.value(), exception);
            return INTERNAL_ERROR_MESSAGE;
        }
        String message = exception.getMessage();
        return message == null || message.isBlank() ? "请求处理失败" : message;
    }
}
