package tech.easyflow.agent.runtime.agui;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 将 AG-UI 运行输出写入可重放日志，同时尽力推送到首次 HTTP 连接。
 *
 * <p>底层浏览器连接关闭后，本对象仍保持逻辑可写，模型运行只会因终态或显式取消而结束。</p>
 */
public final class ResumableAguiSseEmitter extends ChatSseEmitter {

    private static final Logger LOG = LoggerFactory.getLogger(ResumableAguiSseEmitter.class);

    private final String threadId;
    private final String runId;
    private final AgentAguiRunJournal journal;
    private final SseEmitter subscriberEmitter;
    private final AtomicBoolean completed = new AtomicBoolean(false);
    private final AtomicBoolean journalFailed = new AtomicBoolean(false);

    /**
     * 创建可重连 AG-UI SSE 发射器。
     *
     * @param threadId AG-UI 线程 ID
     * @param runId 运行 ID
     * @param journal 可重放事件日志
     * @param subscriberEmitter 从游标零开始的首次订阅
     */
    public ResumableAguiSseEmitter(String threadId,
                                   String runId,
                                   AgentAguiRunJournal journal,
                                   SseEmitter subscriberEmitter) {
        super(0L);
        this.threadId = threadId;
        this.runId = runId;
        this.journal = journal;
        this.subscriberEmitter = java.util.Objects.requireNonNull(
                subscriberEmitter, "subscriberEmitter cannot be null");
    }

    /**
     * 返回与重连相同的 Redis 游标订阅，首次连接不走旁路直推。
     *
     * @return 首次订阅 SSE
     */
    @Override
    public SseEmitter getEmitter() {
        return subscriberEmitter;
    }

    /**
     * 先记录事件，再尽力发送给当前浏览器连接。
     *
     * @param data 已编码 AG-UI 事件
     * @return 日志已接受事件时为 true
     */
    @Override
    public boolean sendData(String data) {
        if (completed.get()) {
            return false;
        }
        try {
            journal.append(runId, data);
        } catch (RuntimeException exception) {
            LOG.error("AG-UI 事件日志拒绝写入: runId={}", runId, exception);
            journalFailed.set(true);
            completed.set(true);
            failJournal(exception);
            super.complete();
            return false;
        }
        return true;
    }

    /**
     * 刷入尾部事件并关闭首次 HTTP 连接。
     */
    @Override
    public void complete() {
        if (!completed.compareAndSet(false, true)) {
            return;
        }
        try {
            journal.complete(runId);
        } catch (RuntimeException exception) {
            LOG.error("AG-UI 运行终态日志同步写入失败，改写为日志故障终态: runId={}", runId, exception);
            journalFailed.set(true);
            failJournal(exception);
        } finally {
            super.complete();
        }
    }

    /**
     * 刷入尾部事件并关闭首次 HTTP 连接。
     *
     * @param error 关闭原因
     */
    @Override
    public void completeWithError(Throwable error) {
        complete();
    }

    /**
     * 返回运行输出逻辑关闭状态，不受首次浏览器连接断开影响。
     *
     * @return 运行已经收口时为 true
     */
    @Override
    public boolean isClosed() {
        return completed.get();
    }

    /**
     * 判断本次运行是否以可持久恢复的方式完成。
     *
     * @return 事件日志没有发生写入故障时为 {@code true}
     */
    public boolean isJournalCompletionSuccessful() {
        return completed.get() && !journalFailed.get();
    }

    private void failJournal(RuntimeException exception) {
        String detail = exception.getMessage() == null ? "" : exception.getMessage();
        boolean capacityFailure = detail.contains("安全上限") || detail.contains("16 MiB");
        String code = capacityFailure
                ? "AGENT_RUN_JOURNAL_LIMIT"
                : "AGENT_RUN_JOURNAL_UNAVAILABLE";
        String message = capacityFailure
                ? "Agent 输出超过可恢复日志容量，运行已停止"
                : "Agent 输出日志暂时不可用，运行已停止";
        try {
            journal.fail(threadId, runId, code, message);
        } catch (RuntimeException terminalException) {
            LOG.error("AG-UI 日志故障终态暂未写入，将由后台继续重试: runId={}",
                    runId, terminalException);
        }
    }
}
