package cn.stylefeng.guns.core.error;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.stylefeng.roses.kernel.auth.api.exception.AuthException;
import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum;
import cn.stylefeng.roses.kernel.demo.exception.DemoException;
import cn.stylefeng.roses.kernel.demo.exception.enums.DemoExceptionEnum;
import cn.stylefeng.roses.kernel.log.api.context.ServerInfoContext;
import cn.stylefeng.roses.kernel.log.api.pojo.security.LogSecurityRequest;
import cn.stylefeng.roses.kernel.log.api.schedule.AsyncLogManager;
import cn.stylefeng.roses.kernel.rule.constants.SymbolConstant;
import cn.stylefeng.roses.kernel.rule.exception.AbstractExceptionEnum;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.roses.kernel.rule.exception.enums.defaults.DefaultBusinessExceptionEnum;
import cn.stylefeng.roses.kernel.rule.pojo.response.ErrorResponseData;
import cn.stylefeng.roses.kernel.rule.util.ExceptionUtil;
import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil;
import cn.stylefeng.roses.kernel.rule.util.ResponseRenderUtil;
import cn.stylefeng.roses.kernel.validator.api.exception.ParamValidateException;
import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.util.List;

import static cn.stylefeng.guns.core.consts.ProjectConstants.ROOT_PACKAGE_NAME;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> missingParam(MissingServletRequestParameterException missingServletRequestParameterException) {
        String parameterName = missingServletRequestParameterException.getParameterName();
        String parameterType = missingServletRequestParameterException.getParameterType();
        return renderJson(ValidatorExceptionEnum.MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION, parameterName, parameterType);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> httpMessageNotReadable(HttpMessageNotReadableException httpMessageNotReadableException) {
        log.error("参数格式传递异常，具体信息为：{}", httpMessageNotReadableException.getMessage());
        return renderJson(ValidatorExceptionEnum.HTTP_MESSAGE_CONVERTER_ERROR);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> httpMediaTypeNotSupport(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
        log.error("参数格式传递异常，具体信息为：{}", httpMediaTypeNotSupportedException.getMessage());
        return renderJson(ValidatorExceptionEnum.HTTP_MEDIA_TYPE_NOT_SUPPORT);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> methodNotSupport(HttpServletRequest request) {
        String httpMethod = request.getMethod().toUpperCase();
        return renderJson(ValidatorExceptionEnum.HTTP_METHOD_NOT_SUPPORT, httpMethod);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> notFound(NoHandlerFoundException e) {
        return renderJson(ValidatorExceptionEnum.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        String bindingResult = getArgNotValidMessage(e.getBindingResult());
        return renderJson(ValidatorExceptionEnum.VALIDATED_RESULT_ERROR, bindingResult);
    }

    @ExceptionHandler(BindException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> bindException(BindException e) {
        String bindingResult = getArgNotValidMessage(e.getBindingResult());
        return renderJson(ValidatorExceptionEnum.VALIDATED_RESULT_ERROR, bindingResult);
    }

    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponseData<?> bindException(ValidationException e) {
        if (e.getCause() instanceof ParamValidateException) {
            ParamValidateException paramValidateException = (ParamValidateException) e.getCause();
            return renderJson(paramValidateException.getErrorCode(), paramValidateException.getUserTip());
        }
        return renderJson(e);
    }

    @ExceptionHandler(AuthException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String authError(AuthException authException, HttpServletRequest request, HttpServletResponse response, Model model) {

        this.asyncRecordSecurityLog(authException, request);

        String errorCode = authException.getErrorCode();

        if (AuthExceptionEnum.AUTH_EXPIRED_ERROR.getErrorCode().equals(errorCode)) {

            if (HttpServletUtil.getNormalRequestFlag(request)) {

                return this.renderLoginResult(response, authException, model);
            } else {

                ErrorResponseData<?> errorResponseData = renderJson(authException.getErrorCode(), authException.getUserTip(), authException);
                ResponseRenderUtil.renderJsonResponse(response, errorResponseData);
                return null;
            }
        }

        if (AuthExceptionEnum.TOKEN_GET_ERROR.getErrorCode().equals(errorCode)) {
            if (HttpServletUtil.getNormalRequestFlag(request)) {

                return this.renderLoginResult(response, authException, model);
            }
        }

        ErrorResponseData<?> errorResponseData = renderJson(authException.getErrorCode(), authException.getUserTip(), authException);
        ResponseRenderUtil.renderJsonResponse(response, errorResponseData);
        return null;
    }

    @ExceptionHandler(ServiceException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData<?> businessError(ServiceException e) {
        log.error("业务异常。", e);
        return renderJson(e.getErrorCode(), e.getUserTip(), e);
    }

    @ExceptionHandler(MyBatisSystemException.class)
    @ResponseBody
    public ErrorResponseData<?> persistenceException(MyBatisSystemException e) {
        log.error(">>> mybatis操作出现异常,", e);
        Throwable cause = e.getCause();
        if (cause instanceof PersistenceException) {
            Throwable secondCause = cause.getCause();
            if (secondCause instanceof DemoException) {
                return renderJson(DemoExceptionEnum.DEMO_OPERATE);
            }
        }
        return renderJson(e);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData<?> serverError(Throwable e) {
        log.error("服务器运行异常", e);
        return renderJson(e);
    }

    private ErrorResponseData<?> renderJson(String code, String message) {
        return renderJson(code, message, null);
    }

    private ErrorResponseData<?> renderJson(AbstractExceptionEnum exception, Object... params) {
        return renderJson(exception.getErrorCode(), StrUtil.format(exception.getUserTip(), params), null);
    }

    private ErrorResponseData<?> renderJson(AbstractExceptionEnum abstractExceptionEnum) {
        return renderJson(abstractExceptionEnum.getErrorCode(), abstractExceptionEnum.getUserTip(), null);
    }

    private ErrorResponseData<?> renderJson(Throwable throwable) {
        return renderJson(DefaultBusinessExceptionEnum.SYSTEM_RUNTIME_ERROR.getErrorCode(), DefaultBusinessExceptionEnum.SYSTEM_RUNTIME_ERROR.getUserTip(), throwable);
    }

    private ErrorResponseData<?> renderJson(String code, String message, Throwable throwable) {
        if (ObjectUtil.isNotNull(throwable)) {
            ErrorResponseData<?> errorResponseData = new ErrorResponseData<>(code, message);
            ExceptionUtil.fillErrorResponseData(errorResponseData, throwable, ROOT_PACKAGE_NAME);
            return errorResponseData;
        } else {
            return new ErrorResponseData<>(code, message);
        }
    }

    private String getArgNotValidMessage(BindingResult bindingResult) {
        if (bindingResult == null) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();

        List<ObjectError> allErrorInfos = bindingResult.getAllErrors();
        for (ObjectError error : allErrorInfos) {
            stringBuilder.append(SymbolConstant.COMMA).append(error.getDefaultMessage());
        }

        return StrUtil.removePrefix(stringBuilder.toString(), SymbolConstant.COMMA);
    }

    private String renderLoginResult(HttpServletResponse response, AuthException authException, Model model) {
        ErrorResponseData<?> errorResponseData = renderJson(authException.getErrorCode(), authException.getUserTip(), authException);
        ResponseRenderUtil.renderJsonResponse(response, errorResponseData);
        return null;
    }

    private void asyncRecordSecurityLog(AuthException authException, HttpServletRequest request) {
        LogSecurityRequest logSecurityRequest = new LogSecurityRequest();

        logSecurityRequest.setRequestUrl(request.getServletPath());

        logSecurityRequest.setRequestParams("");

        logSecurityRequest.setServerIp(ServerInfoContext.getServerIp());

        logSecurityRequest.setClientIp(HttpServletUtil.getRequestClientIp(request));

        logSecurityRequest.setHttpMethod(request.getMethod());

        UserAgent userAgent = HttpServletUtil.getUserAgent(request);
        if (userAgent == null) {
            return;
        }

        if (ObjectUtil.isNotEmpty(userAgent.getBrowser())) {
            logSecurityRequest.setClientBrowser(userAgent.getBrowser().getName());
        }

        if (ObjectUtil.isNotEmpty(userAgent.getOs())) {
            logSecurityRequest.setClientOs(userAgent.getOs().getName());
        }

        logSecurityRequest.setLogContent(authException.getUserTip());

        AsyncLogManager.getInstance().recordSecurityLog(logSecurityRequest);

    }

}
