package com.agent.management.engine;

import com.agent.management.config.WorkflowProperties;
import com.fasterxml.jackson.databind.JsonNode;

import java.util.Objects;

/**
 * LLM 类节点的输出提取重试策略。
 *
 * <p>配置读取优先级：</p>
 * <ol>
 *   <li>节点 {@code data} 中的同名字段</li>
 *   <li>全局 {@code application.yml} 的 {@code workflow.llm.retry.*}</li>
 *   <li>代码默认值（runtimeMaxRetries=2、formatMaxRetries=2、全部 retryOn=true）</li>
 * </ol>
 *
 * <p><b>双层重试</b>：</p>
 * <ul>
 *   <li>{@code runtimeMaxRetries}：运行异常（LLM 调用失败、限流、超时）时重新执行完整推理的次数</li>
 *   <li>{@code formatMaxRetries}：格式错误（JSON 解析失败、字段缺失、类型不匹配、键名不匹配）时
 *       仅重组 JSON 的重试次数（不重新执行业务逻辑，仅把上次响应 + JSON 模板回喂 LLM）</li>
 * </ul>
 *
 * <p>不可变值对象。在执行器中通过 {@link #fromNodeData} 一次性构造后传递给
 * {@link StructuredOutputHelper#extractWithRetry}。</p>
 */
public final class RetryPolicy {

    /** 运行异常重试次数（含首次）。每次都会重新完整推理。 */
    public final int runtimeMaxRetries;
    /** 格式错误重试次数（含首次）。每次仅重组 JSON，不重新推理业务逻辑。 */
    public final int formatMaxRetries;
    public final boolean retryOnParseError;
    public final boolean retryOnMissingRequired;
    public final boolean retryOnTypeMismatch;
    /** strict 模式下检测到键名缺失（如返回中文键名）时是否重试。 */
    public final boolean retryOnKeyNameMismatch;

    /**
     * @deprecated 使用 {@link #RetryPolicy(int, int, boolean, boolean, boolean, boolean)} 替代。
     * 保留过渡期兼容：maxRetries 同时赋给 runtimeMaxRetries 和 formatMaxRetries。
     */
    @Deprecated
    public final int maxRetries;

    public RetryPolicy(int runtimeMaxRetries, int formatMaxRetries,
                       boolean retryOnParseError, boolean retryOnMissingRequired,
                       boolean retryOnTypeMismatch, boolean retryOnKeyNameMismatch) {
        if (runtimeMaxRetries < 1) runtimeMaxRetries = 1;
        if (formatMaxRetries < 1) formatMaxRetries = 1;
        this.runtimeMaxRetries = runtimeMaxRetries;
        this.formatMaxRetries = formatMaxRetries;
        this.retryOnParseError = retryOnParseError;
        this.retryOnMissingRequired = retryOnMissingRequired;
        this.retryOnTypeMismatch = retryOnTypeMismatch;
        this.retryOnKeyNameMismatch = retryOnKeyNameMismatch;
        this.maxRetries = runtimeMaxRetries;
    }

    /**
     * 默认策略：runtimeMaxRetries=2、formatMaxRetries=2、全部 retryOn=true。
     */
    public static RetryPolicy defaultPolicy() {
        return new RetryPolicy(2, 2, true, true, true, true);
    }

    /**
     * Hermes 系列专用策略：未显式配置 runtimeMaxRetries 时强制降为 1。
     *
     * <p><b>原因</b>：Hermes 单次重跑成本极高（涉及完整 Agent 循环 + 工具调用 + 上下文重建），
     * 默认 2 次外层重试会让单节点耗时翻倍且容易触发限流。内层 reformat 仍用普通 LLM，
     * 不受此降级影响。</p>
     *
     * <p>节点级显式配置 {@code runtimeMaxRetries} 或 legacy {@code maxRetries} 时，尊重用户选择。</p>
     *
     * @param data  节点 data（可为 null）
     * @param props 全局属性（可为 null）
     */
    public static RetryPolicy forHermes(JsonNode data, WorkflowProperties props) {
        RetryPolicy base = fromNodeData(data, props);
        if (data != null && (data.has("runtimeMaxRetries") || data.has("maxRetries"))) {
            return base;
        }
        if (base.runtimeMaxRetries <= 1) return base;
        return new RetryPolicy(1, base.formatMaxRetries,
                base.retryOnParseError, base.retryOnMissingRequired,
                base.retryOnTypeMismatch, base.retryOnKeyNameMismatch);
    }

    /**
     * 从节点 data + 全局属性构造策略。节点级配置覆盖全局。
     *
     * @param data  节点 data JsonNode（可为 null，走全局）
     * @param props 全局属性（可为 null，走默认）
     */
    public static RetryPolicy fromNodeData(JsonNode data, WorkflowProperties props) {
        WorkflowProperties.Retry global = (props == null || props.getLlm() == null || props.getLlm().getRetry() == null)
                ? new WorkflowProperties.Retry() : props.getLlm().getRetry();

        // runtimeMaxRetries：优先读节点级 runtimeMaxRetries，缺失时读 legacy maxRetries（向后兼容），再缺失走全局
        int runtimeMaxRetries = readInt(data, "runtimeMaxRetries",
                readInt(data, "maxRetries", global.getRuntimeMaxRetries()));
        int formatMaxRetries = readInt(data, "formatMaxRetries", global.getFormatMaxRetries());
        boolean retryOnParseError = readBool(data, "retryOnParseError", global.isRetryOnParseError());
        boolean retryOnMissingRequired = readBool(data, "retryOnMissingRequired", global.isRetryOnMissingRequired());
        boolean retryOnTypeMismatch = readBool(data, "retryOnTypeMismatch", global.isRetryOnTypeMismatch());
        boolean retryOnKeyNameMismatch = readBool(data, "retryOnKeyNameMismatch", global.isRetryOnKeyNameMismatch());

        return new RetryPolicy(runtimeMaxRetries, formatMaxRetries,
                retryOnParseError, retryOnMissingRequired, retryOnTypeMismatch, retryOnKeyNameMismatch);
    }

    private static int readInt(JsonNode data, String field, int fallback) {
        if (data == null) return fallback;
        JsonNode node = data.path(field);
        if (node.isMissingNode() || node.isNull()) return fallback;
        if (node.isNumber()) return node.asInt();
        try { return Integer.parseInt(node.asText()); } catch (Exception e) { return fallback; }
    }

    private static boolean readBool(JsonNode data, String field, boolean fallback) {
        if (data == null) return fallback;
        JsonNode node = data.path(field);
        if (node.isMissingNode() || node.isNull()) return fallback;
        if (node.isBoolean()) return node.asBoolean();
        return Boolean.parseBoolean(node.asText());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof RetryPolicy)) return false;
        RetryPolicy that = (RetryPolicy) o;
        return runtimeMaxRetries == that.runtimeMaxRetries
                && formatMaxRetries == that.formatMaxRetries
                && retryOnParseError == that.retryOnParseError
                && retryOnMissingRequired == that.retryOnMissingRequired
                && retryOnTypeMismatch == that.retryOnTypeMismatch
                && retryOnKeyNameMismatch == that.retryOnKeyNameMismatch;
    }

    @Override
    public int hashCode() {
        return Objects.hash(runtimeMaxRetries, formatMaxRetries, retryOnParseError,
                retryOnMissingRequired, retryOnTypeMismatch, retryOnKeyNameMismatch);
    }

    @Override
    public String toString() {
        return "RetryPolicy{runtimeMaxRetries=" + runtimeMaxRetries
                + ", formatMaxRetries=" + formatMaxRetries
                + ", parseError=" + retryOnParseError
                + ", missingRequired=" + retryOnMissingRequired
                + ", typeMismatch=" + retryOnTypeMismatch
                + ", keyNameMismatch=" + retryOnKeyNameMismatch + '}';
    }
}
