package com.simuwang.deploy.components;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.simuwang.base.common.exception.APIException;
import com.simuwang.base.common.exception.ErrorInfo;
import com.smppw.common.pojo.enums.status.ResultCode;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import org.springframework.web.util.WebUtils;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author wangzaijun
 * @date 2023/8/12 16:23
 * @description 错误信息构造工具
 */
@Component
public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered {
    /**
     * 错误KEY
     */
    private final static String ERROR_NAME = "simuwang.error";
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
//    /**
//     * 错误配置(ErrorConfiguration)
//     */
//    private final ErrorProperties errorProperties;
//
//    /**
//     * 错误构造器 (Constructor) 传递配置属性：server.xx -> server.error.xx
//     */
//    public ErrorInfoBuilder(ServerProperties serverProperties) {
//        this.errorProperties = serverProperties.getError();
//    }

    /**
     * 构建错误信息.(ErrorInfo)
     */
    public ErrorInfo getErrorInfo(HttpServletRequest request) {
        return getErrorInfo(request, getError(request));
    }

    /**
     * 构建错误信息.(ErrorInfo)
     */
    public ErrorInfo getErrorInfo(HttpServletRequest request, Throwable error) {
        String url = request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE).toString();
        ErrorInfo errorInfo = new ErrorInfo();
        errorInfo.setTime(LocalDateTime.now().toString());
        errorInfo.setUrl(url);
        String msg;
        int code = getHttpStatus(request).value();
        if (error instanceof NoResourceFoundException) {
            msg = "请求资源找不到";
        } else if (error instanceof UnauthorizedException) {
            msg = "没有对应接口的权限";
        } else if (error instanceof UnknownAccountException || error instanceof IncorrectCredentialsException) {
            msg = ResultCode.AUTH_FAILD.getMsg();
        } else if (error instanceof AuthenticationException) {
            msg = "登录认证失败";
        } else if (error instanceof APIException e) {
            msg = e.getMsg();
            code = e.getCode();
        } else if (error instanceof UnauthenticatedException e) {
            msg = e.getMessage();
        } else if (error instanceof HttpMessageNotReadableException || error instanceof BindValidationException) {
            msg = "请求参数错误";
        } else if (error instanceof BindException e) {
            // 参数绑定或校验错误
            List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
            msg = allErrors.get(0).getDefaultMessage();
        } else if (error instanceof ConstraintViolationException e) {
            // 约束验证错误
            msg = e.getMessage();
        } else {
            msg = "未知的错误";
        }
        errorInfo.setError(msg);
        errorInfo.setStatusCode(code);
        errorInfo.setReasonPhrase(getHttpStatus(request).getReasonPhrase());
        errorInfo.setStackTrace(ExceptionUtil.stacktraceToString(error));
        logger.error(StrUtil.format("{} 接口请求错误：{}", url, errorInfo.getStackTrace()));
        return errorInfo;
    }

    /**
     * 获取错误.(Error/Exception)
     *
     * @see org.springframework.boot.web.servlet.error.DefaultErrorAttributes #addErrorDetails
     */
    public Throwable getError(HttpServletRequest request) {
        //根据HandlerExceptionResolver接口方法来获取错误.
        Throwable error = (Throwable) request.getAttribute(ERROR_NAME);
        //根据Request对象获取错误.
        if (error == null) {
            error = (Throwable) request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE);
        }
        //当获取错误非空,取出RootCause.
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = error.getCause();
            }
        } else {
            //当获取错误为null,此时我们设置错误信息即可.
            String message = (String) request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE);
            if (StringUtils.isEmpty(message)) {
                HttpStatus status = getHttpStatus(request);
                message = "Unknown Exception But " + status.value() + " " + status.getReasonPhrase();
            }
            error = new Exception(message);
        }
        return error;
    }

    /**
     * 获取通信状态(HttpStatus)
     *
     * @see org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController #getStatus
     */
    public HttpStatus getHttpStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
        try {
            return statusCode != null ? HttpStatus.valueOf(statusCode) : HttpStatus.INTERNAL_SERVER_ERROR;
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

//    /**
//     * 获取堆栈轨迹(StackTrace)
//     *
//     * @see org.springframework.boot.web.servlet.error.DefaultErrorAttributes  #addStackTrace
//     */
//    public String getStackTraceInfo(Throwable error, boolean flag) {
//        if (!flag) {
//            return "omitted";
//        }
//        StringWriter stackTrace = new StringWriter();
//        error.printStackTrace(new PrintWriter(stackTrace));
//        stackTrace.flush();
//        return stackTrace.toString();
//    }

//    /**
//     * 判断是否包含堆栈轨迹.(isIncludeStackTrace)
//     *
//     * @see org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController #isIncludeStackTrace
//     */
//    public boolean isIncludeStackTrace(HttpServletRequest request) {
//
//        //读取错误配置(server.error.include-stacktrace=NEVER)
//        ErrorProperties.IncludeAttribute includeStacktrace = errorProperties.getIncludeStacktrace();
//
//        //情况1：若IncludeStacktrace为ALWAYS
//        if (includeStacktrace == ErrorProperties.IncludeAttribute.ALWAYS) {
//            return true;
//        }
//        //情况2：若请求参数含有trace
//        if (includeStacktrace == ErrorProperties.IncludeAttribute.ON_PARAM) {
//            String parameter = request.getParameter("trace");
//            return parameter != null && !"false".equalsIgnoreCase(parameter);
//        }
//        //情况3：其它情况
//        return false;
//    }

    /**
     * 保存错误/异常.
     *
     * @see org.springframework.web.servlet.DispatcherServlet #processHandlerException 进行选举HandlerExceptionResolver
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        request.setAttribute(ERROR_NAME, ex);
        return null;
    }

    /**
     * 提供优先级 或用于排序
     */
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}
