package xtools.boot.web.base.exception;

import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
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.HandlerMethodValidationException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import xtools.base.config.BaseParams;
import xtools.boot.api.enums.ResultType;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizPublicKeyError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.exection.UnauthorizedError;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.log.LogTrack;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.boot.log.holder.LogTrackHolder;
import xtools.boot.log.interfaces.LogBusType;
import xtools.core.StringUtils;
import xtools.core.enums.LogLevel;
import xtools.core.extend.TemplateUtils;

import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

/**
 * <p>Title : GlobalExceptionHandler</p>
 * <p>Description : GlobalExceptionHandler</p>
 * <p>DevelopTools : Idea_x64_v2026.1</p>
 * <p>DevelopSystem : macOS Sequoia 15.7.5</p>
 * <p>Company : org.xujun</p>
 *
 * @author : XuJun
 * @version : 5.0.0
 * @date : 2026/1/5 15:21
 */
@Slf4j
@RestControllerAdvice
public class GlobalControllerExceptionHandler implements BaseParams {

    /**
     * 参数类型不匹配异常模板
     */
    private static final String ARGUMENT_TYPE_MISMATCH_TEMPLATE = "参数[{}]类型不匹配";

    /**
     * 参数类型不匹配
     *
     * @param e       参数类型不匹配异常
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        String errMsg = TemplateUtils.format(ARGUMENT_TYPE_MISMATCH_TEMPLATE, e.getPropertyName());
        debugLog("参数类型不匹配", errMsg, request, e);
        return Result.badRequest(errMsg);
    }

    /**
     * 参数校验异常
     *
     * @param e       参数校验异常
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(HandlerMethodValidationException.class)
    public Result<Object> handleHandlerMethodValidationException(HandlerMethodValidationException e, HttpServletRequest request) {
        // 错误结果
        StringJoiner msg = new StringJoiner(CP_COMMA);
        // 处理参数验证结果
        e.getParameterValidationResults().forEach(validation -> {
            // 获取参数名称
            String paramName = validation.getMethodParameter().getParameterName();
            // 获取字段约束违规
            validation.getResolvableErrors().forEach(item -> msg.add(paramName + CP_COLON + item.getDefaultMessage()));
        });
        String errMsg = msg.toString();
        debugLog("参数校验异常", errMsg, request, e);
        return Result.badRequest(errMsg);
    }

    /**
     * 参数校验异常
     *
     * @param e       参数校验异常
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        // 错误结果
        StringJoiner msg = new StringJoiner(CP_COMMA);
        // 处理参数验证结果
        e.getBindingResult().getFieldErrors().forEach(fieldError -> {
            String fieldName = fieldError.getField();
            String errorMessage = fieldError.getDefaultMessage();
            msg.add(fieldName + CP_COLON + errorMessage);
        });
        String errMsg = msg.toString();
        debugLog("参数校验异常", errMsg, request, e);
        return Result.badRequest(errMsg);
    }

    /**
     * 消息不可读异常
     *
     * @param e       消息不可读异常
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        String errMsg = "请求参数格式错误";
        debugLog(errMsg, e.getMessage(), request, e);
        return Result.badRequest(errMsg);
    }

    /**
     * 公钥错误
     *
     * @param e       公钥错误
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(BizPublicKeyError.class)
    public Result<Object> handleBizPublicKeyErrorException(BizPublicKeyError e, HttpServletRequest request) {
        debugLog("公钥错误", e.getMessage(), request, e);
        return new Result<>(true, e.getCode(), e.getMessage(), null);
    }

    /**
     * 业务异常
     *
     * @param e       业务异常
     * @param request 请求
     * @return 错误结果
     */
    @ExceptionHandler(BizError.class)
    public Result<Object> handleBizErrorException(BizError e, HttpServletRequest request) {
        debugLog("业务异常", e.getMessage(), request, e);
        return new Result<>(true, e.getCode(), e.getMessage(), null);
    }

    /**
     * 业务警告
     *
     * @param e       业务异常
     * @param request 请求
     * @return 警告结果
     */
    @ExceptionHandler(BizWarning.class)
    public Result<Object> handleBizWarningException(BizWarning e, HttpServletRequest request) {
        debugLog("业务警告", e.getMessage(), request, e);
        return new Result<>(true, e.getCode(), e.getMessage(), null);
    }

    /**
     * 认证异常
     *
     * @param e 认证异常
     * @return 错误结果
     */
    @ExceptionHandler(UnauthorizedError.class)
    public Result<Object> handleUnauthorizedErrorException(UnauthorizedError e) {
        errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "认证异常", e);
        return new Result<>(ResultType.UNAUTHORIZED, null);
    }

    /**
     * 请求方法不支持
     *
     * @param e 请求方法不支持异常
     * @return 错误结果
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "请求方法不支持", e, false);
        return new Result<>(ResultType.METHOD_NOT_ALLOWED, null);
    }

    /**
     * 资源未找到
     *
     * @param e 资源未找到异常
     * @return 错误结果
     */
    @ExceptionHandler(NoResourceFoundException.class)
    public ResponseEntity<?> handleNoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) {
        errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "资源未找到", e, false);
        if (checkTextResponse(request)) {
            String body = JSONObject.toJSONString(new Result<>(ResultType.NOT_FOUND, null));
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    /**
     * 检查是否为文本响应
     *
     * @param request 请求
     * @return 是否为文本响应
     */
    private boolean checkTextResponse(HttpServletRequest request) {
        String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
        if (StringUtils.isBlank(acceptHeader)) {
            return true;
        }
        List<MediaType> acceptedTypes = MediaType.parseMediaTypes(acceptHeader);
        for (MediaType item : acceptedTypes) {
            // 通配符 */* 表示可接受任意类型,包括文本
            if (item.equals(MediaType.ALL)) {
                return true;
            }
            String type = item.getType();
            switch (type) {
                case "text" -> {
                    return true;
                }
                case "image", "multipart" -> {
                    return false;
                }
                default -> {
                }
            }
            if (
                    item.isCompatibleWith(MediaType.APPLICATION_ATOM_XML) ||
                            item.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED) ||
                            item.isCompatibleWith(MediaType.APPLICATION_GRAPHQL_RESPONSE) ||
                            item.isCompatibleWith(MediaType.APPLICATION_JSON) ||
                            item.isCompatibleWith(MediaType.APPLICATION_NDJSON) ||
                            item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_JSON) ||
                            item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_XML) ||
                            item.isCompatibleWith(MediaType.APPLICATION_RSS_XML) ||
                            item.isCompatibleWith(MediaType.APPLICATION_XHTML_XML) ||
                            item.isCompatibleWith(MediaType.APPLICATION_XML) ||
                            item.isCompatibleWith(MediaType.APPLICATION_YAML)
            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * 客户端提前断开连接
     *
     * @param e 客户端提前断开连接异常
     */
    @ExceptionHandler(ClientAbortException.class)
    public void handleClientAbortException(ClientAbortException e) {
        errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "客户端提前断开连接", e, false);
    }

    /**
     * 默认全局异常处理
     *
     * @param e 异常
     * @return 错误结果
     */
    @ExceptionHandler(Exception.class)
    public Result<Object> handleException(Exception e) {
        errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "默认全局异常处理", e);
        return new Result<>(ResultType.INTERNAL_SERVER_ERROR, null);
    }

    /**
     * 错误日志
     *
     * @param level 日志级别
     * @param type  异常类型
     * @param title 标题
     * @param e     异常
     */
    private void errorLog(LogLevel level, LogBusType type, String title, Exception e) {
        errorLog(level, type, title, e, null);
    }

    /**
     * 错误日志
     *
     * @param level 日志级别
     * @param type  异常类型
     * @param title 标题
     * @param e     异常
     * @param print 是否打印
     */
    private void errorLog(LogLevel level, LogBusType type, String title, Exception e, Boolean print) {
        LogTrack logTrack = LogTrackHolder.getDefNull();
        if (Objects.isNull(logTrack)) {
            logTrack = LogTrackHolder.getNewMain();
        }
        LogBus logBus = LogBus.init(level, type, logTrack).title(title).error(e);
        if (Objects.nonNull(print)) {
            logBus.print(print);
        }
        logBus.save();
    }

    /**
     * 调试日志
     *
     * @param type    异常类型
     * @param msg     错误消息
     * @param request 请求
     * @param e       异常
     */
    private void debugLog(String type, String msg, HttpServletRequest request, Exception e) {
        if (StringUtils.isBlank(msg)) {
            log.debug("请求[{}]异常,类型[{}]", request.getRequestURI(), type, e);
        } else {
            log.debug("请求[{}]异常,类型[{}],错误消息[{}]", request.getRequestURI(), type, msg);
        }
    }

}
