package com.cf.imes.framework.web.core.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.exception.ErrorData;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.util.monitor.TracerUtils;
import com.cf.imes.framework.web.core.util.WebFrameworkUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.common.util.servlet.ServletUtils;
import com.cf.imes.module.infra.api.logger.ApiErrorLogApi;
import com.cf.imes.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
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.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;

import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.*;

/**
 * 全局异常处理器，将 Exception 翻译成 CommonResult + 对应的异常编号
 *
 * @author 晨丰科技
 */
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    private final String applicationName;

    private final ApiErrorLogApi apiErrorLogApi;

    /**
     * 处理所有异常，主要是提供给 Filter 使用
     * 因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。
     *
     * @param request 请求
     * @param ex 异常
     * @return 通用返回
     */
    public CommonResult allExceptionHandler(HttpServletRequest request, Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException missingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler(missingServletRequestParameterException);
        }
        if (ex instanceof MethodArgumentTypeMismatchException methodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler(methodArgumentTypeMismatchException);
        }
        if (ex instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
            return methodArgumentNotValidExceptionExceptionHandler(methodArgumentNotValidException);
        }
        if (ex instanceof BindException bindException) {
            return bindExceptionHandler(bindException);
        }
        if (ex instanceof ConstraintViolationException constraintViolationException) {
            return constraintViolationExceptionHandler(constraintViolationException);
        }
        if (ex instanceof ValidationException validationException) {
            return validationException(validationException);
        }
        if (ex instanceof NoHandlerFoundException noHandlerFoundException) {
            return noHandlerFoundExceptionHandler(noHandlerFoundException);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler(httpRequestMethodNotSupportedException);
        }
        if (ex instanceof MaxUploadSizeExceededException maxUploadSizeExceededException) {
            return maxUploadSizeExceededExceptionHandler(maxUploadSizeExceededException);
        }
        if (ex instanceof ServiceException serviceException) {
            return serviceExceptionHandler(serviceException);
        }
        if (ex instanceof AccessDeniedException accessDeniedException) {
            return accessDeniedExceptionHandler(request, accessDeniedException);
        }
        if (ex instanceof HttpMessageNotReadableException httpMessageNotReadableException) {
            return httpMessageNotReadableExceptionHandler(request, httpMessageNotReadableException);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn(String.format("==========[missingServletRequestParameterExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(REQUEST_PARAM_MISSING, ex.getParameterName());
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     *
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public CommonResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn(String.format("==========[missingServletRequestParameterExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(REQUEST_PARAM_TYPE_ERROR, ex.getName() + ":" + ex.getValue());
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        log.warn(String.format("==========[methodArgumentNotValidExceptionExceptionHandler]==========，%s", ex.getMessage()));
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null; // 断言，避免告警
        return CommonResult.error(BAD_REQUEST.getCode(), fieldError.getDefaultMessage());
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public CommonResult bindExceptionHandler(BindException ex) {
        log.warn(String.format("==========[handleBindException]==========，%s", ex.getMessage()));
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null; // 断言，避免告警
        String defaultMessage = fieldError.getDefaultMessage();
        // 如果是校验器来的给出校验注解的提示
        if (CharSequenceUtil.isNotEmpty(defaultMessage) && fieldError.contains(ConstraintViolation.class)) {
            return CommonResult.error(BAD_REQUEST.getCode(), defaultMessage);
        } else {
            return CommonResult.error(BAD_REQUEST, fieldError.getField() + ":" + fieldError.getRejectedValue());
        }
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn(String.format("==========[constraintViolationExceptionHandler]==========，%s", ex.getMessage()));
        ConstraintViolation constraintViolation = ex.getConstraintViolations().iterator().next();
        String message = constraintViolation.getMessage();
        if (StringUtils.isNotEmpty(message)) {
            return CommonResult.error(BAD_REQUEST.getCode(), message);
        } else {
            return CommonResult.error(BAD_REQUEST, message);
        }
    }

    /**
     * 处理 Dubbo Consumer 本地参数校验时，抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public CommonResult validationException(ValidationException ex) {
        log.warn(String.format("==========[constraintViolationExceptionHandler]==========，%s", ex.getMessage()));
        // 无法拼接明细的错误信息，因为 Dubbo Consumer 抛出 ValidationException 异常时，是直接的字符串信息，且人类不可读
        return CommonResult.error(BAD_REQUEST);
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     *
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.warn(String.format("==========[noHandlerFoundExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(NOT_FOUND, ex.getRequestURL());
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     *
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.warn(String.format("==========[httpRequestMethodNotSupportedExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(METHOD_NOT_ALLOWED, ex.getMessage());
    }

    /**
     * 处理 Resilience4j 限流抛出的异常
     */
    public CommonResult requestNotPermittedExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.warn("[requestNotPermittedExceptionHandler][url({}) 访问过于频繁]", req.getRequestURL(), ex);
        return CommonResult.error(TOO_MANY_REQUESTS);
    }

    /**
     * 处理 Spring Security 权限不足的异常
     *
     * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public CommonResult accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req),
                req.getRequestURL(), ex);
        return CommonResult.error(FORBIDDEN);
    }

    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public CommonResult httpMessageNotReadableExceptionHandler(HttpServletRequest req, HttpMessageNotReadableException ex) {
        String errMsg = ex.getMessage();
        log.warn(String.format("==========[httpMessageNotReadableExceptionHandler]==========，%s", errMsg));
        Throwable cause = ex.getCause();
        if (cause instanceof InvalidFormatException) {
            errMsg = ((InvalidFormatException) cause).getValue().toString();
        }
        return CommonResult.error(BAD_REQUEST, errMsg);
    }

    /**
     * 处理业务异常 ServiceException
     *
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult serviceExceptionHandler(ServiceException ex) {
        // 系统异常输出堆栈
        if (INTERNAL_SERVER_ERROR.getCode().equals(ex.getCode())) {
            log.error("[serviceExceptionHandler]", ex);
        } else {
            log.error(String.format("==========[serviceExceptionHandler]==========，%s：%s", ex.getCode(), ex.getMessage()));
        }
        CommonResult<Object> error = CommonResult.error(ex.getCode(), ex.getMessage(), ex.getArgs());
        ErrorData errorData = ex.getErrorData();
        if (ObjectUtil.isNotNull(errorData)) {
            error.setData(errorData.getErrorData());
        }
        return error;
    }

    /**
     * 处理 multipart 请求超过全局上传限制。此类异常发生在请求体解析阶段，不能进入默认异常日志流程，
     * 否则错误日志会尝试读取并序列化整个 multipart 请求体，可能导致内存溢出。
     */
    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    public CommonResult maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException ex) {
        log.warn("[maxUploadSizeExceededExceptionHandler] multipart request exceeds configured upload limit");
        return CommonResult.error(UPLOAD_SIZE_EXCEEDED);
    }

    /**
     * 处理数据库DuplicateKeyException，违反唯一约束异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = DuplicateKeyException.class)
    public CommonResult duplicateKeyExceptionHandler(DuplicateKeyException ex) {
        log.warn(String.format("==========[duplicateKeyExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(DUPLICATE_DATA_ERROR);
    }

    /**
     * 处理资源不存在异常 NoResourceFoundException
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = NoResourceFoundException.class)
    public CommonResult noResourceFoundExceptionHandler(NoResourceFoundException ex) {
        log.warn(String.format("==========[noResourceFoundExceptionHandler]==========，%s", ex.getMessage()));
        return CommonResult.error(NOT_FOUND, ex.getResourcePath());
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        // 情况一：处理表不存在的异常
        CommonResult tableNotExistsResult = handleTableNotExists(ex);
        if (tableNotExistsResult != null) {
            return tableNotExistsResult;
        }

        // 情况二：部分特殊的库的处理
        if (Objects.equals("io.github.resilience4j.ratelimiter.RequestNotPermitted", ex.getClass().getName())) {
            return requestNotPermittedExceptionHandler(req, ex);
        }

        // 情况三：处理异常
        log.error("[defaultExceptionHandler]", ex);
        // 插入异常日志
        this.createExceptionLog(req, ex);
        // 返回 ERROR CommonResult
        return CommonResult.error(INTERNAL_SERVER_ERROR);
    }

    private void createExceptionLog(HttpServletRequest req, Throwable e) {
        // 插入错误日志
        ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
        try {
            // 初始化 errorLog
            buildExceptionLog(errorLog, req, e);
            // 执行插入 errorLog
            apiErrorLogApi.createApiErrorLogAsync(errorLog);
        } catch (Throwable th) {
            log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(),  JsonUtils.toJsonString(errorLog), th);
        }
    }

    private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
        // 处理用户信息
        errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
        errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
        // 设置异常字段
        errorLog.setExceptionName(e.getClass().getName());
        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
        StackTraceElement stackTraceElement = stackTraceElements[0];
        errorLog.setExceptionClassName(stackTraceElement.getClassName());
        errorLog.setExceptionFileName(stackTraceElement.getFileName());
        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
        // 设置其它字段
        errorLog.setTraceId(TracerUtils.getTraceId());
        errorLog.setApplicationName(applicationName);
        errorLog.setRequestUrl(request.getRequestURI());
        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
                .put("query", ServletUtils.getParamMap(request))
                .put("body", isMultipartRequest(request) ? "[multipart body omitted]" : ServletUtils.getBody(request)).build();
        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
        errorLog.setRequestMethod(request.getMethod());
        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
        errorLog.setUserIp(ServletUtils.getClientIP(request));
        errorLog.setExceptionTime(LocalDateTime.now());
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null && contentType.regionMatches(true, 0, "multipart/", 0, "multipart/".length());
    }

    /**
     * 处理 Table 不存在的异常情况
     *
     * @param ex 异常
     * @return 如果是 Table 不存在的异常，则返回对应的 CommonResult
     */
    private CommonResult handleTableNotExists(Throwable ex) {
        String message = ExceptionUtil.getRootCauseMessage(ex);
        if (!message.contains("doesn't exist")) {
            return null;
        }
        // 1. 数据报表
        if (message.contains("report_")) {
            log.error("[报表模块 cf-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[报表模块 cf-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
        }
        // 2. 工作流
        if (message.contains("bpm_")) {
            log.error("[工作流模块 cf-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[工作流模块 cf-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
        }
        // 3. 微信公众号
        if (message.contains("mp_")) {
            log.error("[微信公众号 cf-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[微信公众号 cf-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
        }
        // 4. 商城系统
        if (CharSequenceUtil.containsAny(message, "product_", "promotion_", "trade_")) {
            log.error("[商城系统 cf-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[商城系统 cf-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
        }
        // 5. 支付平台
        if (message.contains("pay_")) {
            log.error("[支付模块 cf-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[支付模块 cf-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
        }
        return null;
    }

}
