package tech.easyflow.publicapi.error;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.ErrorResponse;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import tech.easyflow.common.web.error.RequestErrorProfile;
import tech.easyflow.common.web.error.RequestIdContext;
import tech.easyflow.common.web.error.WebErrorMapping;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.publicapi.dto.PublicApiErrorDetail;

import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 * 工作流 Public API 的稳定业务错误翻译及 {@code runAsync} 请求格式规则。
 */
public final class WorkflowRunAsyncErrorProfile
        implements RequestErrorProfile {

    /** 共享无状态实例。 */
    public static final WorkflowRunAsyncErrorProfile INSTANCE =
            new WorkflowRunAsyncErrorProfile();

    private static final Pattern MULTIPART_BOUNDARY_PATTERN =
            Pattern.compile(
                    "(?:^|;)\\s*boundary\\s*=\\s*(?:\"[^\"]+\"|[^;\\s]+)",
                    Pattern.CASE_INSENSITIVE);
    private static final List<String> RUN_CONTENT_TYPES = List.of(
            MediaType.APPLICATION_JSON_VALUE,
            MediaType.MULTIPART_FORM_DATA_VALUE);
    private static final int MAX_ACTUAL_LENGTH = 256;

    private WorkflowRunAsyncErrorProfile() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public WebErrorMapping map(
            HttpServletRequest request,
            Exception exception) {
        if (!isRunAsyncRequest(request)) {
            if (exception instanceof BusinessException businessException) {
                return business(request, businessException);
            }
            if (isClientRequestException(exception)) {
                return null;
            }
            return internalError(request);
        }
        String requestContentType = request.getContentType();
        if (exception instanceof MaxUploadSizeExceededException) {
            return error(
                    request,
                    413,
                    41301,
                    "上传文件、文件数量或请求总量超过限制",
                    "body",
                    "files",
                    null,
                    List.of(),
                    false);
        }
        if (exception instanceof MissingServletRequestPartException missingPart) {
            return missingPart(request, missingPart);
        }
        if (exception instanceof MethodArgumentNotValidException invalid) {
            return validation(request, invalid);
        }
        if (exception instanceof HttpMediaTypeNotSupportedException unsupported) {
            return unsupportedMediaType(
                    request,
                    requestContentType,
                    unsupported);
        }
        if (exception instanceof HttpMessageNotReadableException) {
            return unreadableBody(request, requestContentType);
        }
        if (exception instanceof MultipartException) {
            if (isMultipartFormData(requestContentType)
                    && !hasBoundary(requestContentType)) {
                return error(
                        request,
                        400,
                        40012,
                        "multipart/form-data 缺少 boundary；请删除手工设置的 Content-Type，让客户端自动生成",
                        "header",
                        "Content-Type",
                        requestContentType,
                        List.of("multipart/form-data; boundary=<客户端自动生成>"),
                        false);
            }
            return error(
                    request,
                    400,
                    40012,
                    "multipart/form-data 请求无法解析，请检查 boundary 与各 Part 格式",
                    "header",
                    "Content-Type",
                    requestContentType,
                    List.of("multipart/form-data; boundary=<客户端自动生成>"),
                    false);
        }
        if (exception instanceof BusinessException businessException) {
            return business(request, businessException);
        }
        return internalError(request);
    }

    /**
     * 判断非 runAsync 接口异常是否已具有明确的客户端错误语义。
     *
     * @param exception 原始异常
     * @return 是否应交由全局 4xx 规则处理
     */
    private boolean isClientRequestException(Exception exception) {
        if (exception instanceof ConstraintViolationException
                || exception instanceof MethodArgumentNotValidException
                || exception instanceof MethodArgumentTypeMismatchException
                || exception instanceof HttpMessageNotReadableException) {
            return true;
        }
        return exception instanceof ErrorResponse errorResponse
                && errorResponse.getStatusCode().is4xxClientError();
    }

    /**
     * 创建不泄露内部实现的未知服务端错误。
     *
     * @param request 当前请求
     * @return 50001 错误映射
     */
    private WebErrorMapping internalError(
            HttpServletRequest request) {
        return error(
                request,
                500,
                50001,
                "服务暂时不可用，请稍后重试",
                "dependency",
                null,
                null,
                List.of(),
                false);
    }

    /**
     * 翻译缺失的 Multipart Part。
     *
     * @param request 当前请求
     * @param exception 缺失 Part 异常
     * @return 错误映射
     */
    private WebErrorMapping missingPart(
            HttpServletRequest request,
            MissingServletRequestPartException exception) {
        String partName = exception.getRequestPartName();
        if ("metadata".equals(partName)) {
            return error(
                    request,
                    400,
                    40013,
                    "当前使用 multipart/form-data，但缺少 metadata Part；如直接提交 JSON，请将 Content-Type 设置为 application/json",
                    "part",
                    "metadata",
                    null,
                    List.of("application/json"),
                    false);
        }
        return error(
                request,
                400,
                40016,
                "缺少必要的文件 Part：" + safeActual(partName),
                "part",
                partName,
                null,
                List.of("files.<开始节点参数名>"),
                false);
    }

    /**
     * 翻译请求 DTO 校验失败。
     *
     * @param request 当前请求
     * @param exception 参数校验异常
     * @return 错误映射
     */
    private WebErrorMapping validation(
            HttpServletRequest request,
            MethodArgumentNotValidException exception) {
        FieldError idError = exception.getBindingResult()
                .getFieldErrors("id")
                .stream()
                .findFirst()
                .orElse(null);
        if (idError != null) {
            return error(
                    request,
                    400,
                    40015,
                    "metadata.id 不能为空，请传入要执行的工作流 ID",
                    isMultipartFormData(request.getContentType())
                            ? "part"
                            : "body",
                    "id",
                    null,
                    List.of("已发布工作流 ID"),
                    false);
        }
        FieldError first = exception.getBindingResult()
                .getFieldError();
        return error(
                request,
                400,
                40017,
                first == null || first.getDefaultMessage() == null
                        ? "工作流运行参数不合法"
                        : first.getDefaultMessage(),
                "field",
                first == null ? null : first.getField(),
                null,
                List.of(),
                false);
    }

    /**
     * 翻译不支持的媒体类型。
     *
     * @param request 当前请求
     * @param requestContentType 顶层请求内容类型
     * @param exception 媒体类型异常
     * @return 错误映射
     */
    private WebErrorMapping unsupportedMediaType(
            HttpServletRequest request,
            String requestContentType,
            HttpMediaTypeNotSupportedException exception) {
        if (isMultipartFormData(requestContentType)) {
            String actual = exception.getContentType() == null
                    ? null
                    : exception.getContentType().toString();
            return error(
                    request,
                    415,
                    41502,
                    "metadata Part 必须使用 application/json；文件 Part 请使用 files.<开始节点参数名>",
                    "part",
                    "metadata",
                    actual,
                    List.of(MediaType.APPLICATION_JSON_VALUE),
                    false);
        }
        boolean missing = requestContentType == null
                || requestContentType.isBlank();
        return error(
                request,
                415,
                41501,
                missing
                        ? "缺少 Content-Type；JSON 调用请使用 application/json，文件直传请使用 multipart/form-data"
                        : "Content-Type 不受支持；JSON 调用请使用 application/json，文件直传请使用 multipart/form-data",
                "header",
                "Content-Type",
                requestContentType,
                RUN_CONTENT_TYPES,
                false);
    }

    /**
     * 翻译无法读取的 JSON 请求体或 metadata Part。
     *
     * @param request 当前请求
     * @param requestContentType 顶层请求内容类型
     * @return 错误映射
     */
    private WebErrorMapping unreadableBody(
            HttpServletRequest request,
            String requestContentType) {
        if (isMultipartFormData(requestContentType)) {
            return error(
                    request,
                    400,
                    40014,
                    "metadata Part 不是有效 JSON，请检查文件内容或字段格式",
                    "part",
                    "metadata",
                    null,
                    List.of(MediaType.APPLICATION_JSON_VALUE),
                    false);
        }
        return error(
                request,
                400,
                40011,
                "请求头为 application/json，但请求体不是有效 JSON；如需上传文件，请改用 multipart/form-data",
                "body",
                null,
                requestContentType,
                List.of(MediaType.APPLICATION_JSON_VALUE),
                false);
    }

    /**
     * 翻译工作流调用链抛出的安全业务异常。
     *
     * @param request 当前请求
     * @param exception 业务异常
     * @return 错误映射
     */
    private WebErrorMapping business(
            HttpServletRequest request,
            BusinessException exception) {
        int code = normalizeBusinessCode(exception);
        String location = code == 40101 || code == 40102 || code == 40103
                || code == 40301 || code == 40302
                ? "auth"
                : code == 50301 || code == 50001
                ? "dependency"
                : code == 40016
                ? "part"
                : "field";
        String field = code == 40016
                ? "files.<开始节点参数名>"
                : null;
        return error(
                request,
                normalizeHttpStatus(code, exception.getHttpStatus()),
                code,
                publicBusinessMessage(code, exception.getMessage()),
                location,
                field,
                null,
                List.of(),
                code == 50301);
    }

    /**
     * 把尚未迁移的旧业务码归一化为工作流 Public API 稳定错误码。
     *
     * @param exception 业务异常
     * @return 稳定错误码
     */
    private int normalizeBusinessCode(BusinessException exception) {
        int code = exception.getErrorCode();
        if (isStableBusinessCode(code)) {
            return code;
        }
        String message = exception.getMessage() == null
                ? ""
                : exception.getMessage().toLowerCase(Locale.ROOT);
        if (message.contains("执行")
                && (message.contains("不存在")
                || message.contains("已过期")
                || message.contains("不可访问"))) {
            return 40402;
        }
        if (message.contains("工作流")
                && (message.contains("不存在")
                || message.contains("未发布")
                || message.contains("尚未发布")
                || message.contains("不可公开"))) {
            return 40401;
        }
        return switch (exception.getHttpStatus()) {
            case 401 -> message.contains("过期") ? 40103 : 40102;
            case 403 -> message.contains("工作流") ? 40302 : 40301;
            case 404 -> message.contains("执行") ? 40402 : 40401;
            case 409 -> 40901;
            case 413 -> 41301;
            case 503 -> 50301;
            case 500 -> 50001;
            default -> 40017;
        };
    }

    /**
     * 判断业务异常是否已经使用本接口声明的稳定错误码。
     *
     * @param code 业务错误码
     * @return 是否为稳定错误码
     */
    private boolean isStableBusinessCode(int code) {
        return (code >= 40011 && code <= 40017)
                || (code >= 40101 && code <= 40103)
                || (code >= 40301 && code <= 40302)
                || (code >= 40401 && code <= 40402)
                || code == 40901
                || code == 41301
                || (code >= 41501 && code <= 41502)
                || code == 50001
                || code == 50301;
    }

    /**
     * 根据稳定错误码校正旧业务异常携带的 HTTP 状态。
     *
     * @param code 稳定错误码
     * @param fallback 原始 HTTP 状态
     * @return 对外 HTTP 状态
     */
    private int normalizeHttpStatus(int code, int fallback) {
        if (code >= 40011 && code <= 40017) {
            return 400;
        }
        if (code >= 40101 && code <= 40103) {
            return 401;
        }
        if (code >= 40301 && code <= 40302) {
            return 403;
        }
        if (code >= 40401 && code <= 40402) {
            return 404;
        }
        return switch (code) {
            case 40901 -> 409;
            case 41301 -> 413;
            case 41501, 41502 -> 415;
            case 50001 -> 500;
            case 50301 -> 503;
            default -> fallback;
        };
    }

    /**
     * 为需要隐藏资源状态或内部依赖详情的错误生成固定公共消息。
     *
     * @param code 稳定错误码
     * @param originalMessage 原业务消息
     * @return 可安全返回的消息
     */
    private String publicBusinessMessage(
            int code,
            String originalMessage) {
        return switch (code) {
            case 40401 -> "工作流不存在或当前不可公开调用";
            case 40402 -> "执行记录不存在、已过期或不可访问";
            case 50001 -> "服务暂时不可用，请稍后重试";
            case 50301 -> "必要依赖暂时不可用，请稍后重试";
            default -> originalMessage == null || originalMessage.isBlank()
                    ? "请求处理失败"
                    : originalMessage;
        };
    }

    /**
     * 创建统一错误映射。
     *
     * @param request 当前请求
     * @param status HTTP 状态码
     * @param code 业务错误码
     * @param message 安全消息
     * @param location 错误位置
     * @param field 错误字段
     * @param actual 实际值
     * @param expected 期望值
     * @param retryable 是否可重试
     * @return 错误映射
     */
    private WebErrorMapping error(
            HttpServletRequest request,
            int status,
            int code,
            String message,
            String location,
            String field,
            String actual,
            List<String> expected,
            boolean retryable) {
        return new WebErrorMapping(
                status,
                code,
                message,
                new PublicApiErrorDetail(
                        RequestIdContext.get(request),
                        location,
                        field,
                        safeActual(actual),
                        expected,
                        retryable));
    }

    /**
     * 判断顶层内容类型是否为 Multipart 表单。
     *
     * @param contentType 内容类型
     * @return 是否为 Multipart 表单
     */
    private boolean isMultipartFormData(String contentType) {
        return contentType != null
                && contentType.toLowerCase(Locale.ROOT)
                .startsWith(MediaType.MULTIPART_FORM_DATA_VALUE);
    }

    /**
     * 判断当前请求是否为两种媒体类型共用的异步执行入口。
     *
     * @param request 当前请求
     * @return 是否为 runAsync
     */
    private boolean isRunAsyncRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        return uri != null
                && uri.endsWith("/public-api/workflow/runAsync");
    }

    /**
     * 判断 Multipart 内容类型是否包含非空边界。
     *
     * @param contentType 内容类型
     * @return 是否存在边界
     */
    private boolean hasBoundary(String contentType) {
        return contentType != null
                && MULTIPART_BOUNDARY_PATTERN.matcher(contentType).find();
    }

    /**
     * 限制响应中的实际值长度并移除控制字符。
     *
     * @param actual 原始实际值
     * @return 安全值
     */
    private static String safeActual(String actual) {
        if (actual == null) {
            return null;
        }
        String safe = actual.replaceAll("[\\p{Cntrl}]", " ").trim();
        return safe.length() <= MAX_ACTUAL_LENGTH
                ? safe
                : safe.substring(0, MAX_ACTUAL_LENGTH);
    }
}
