package com.gzzm.lobster.llm;

import com.gzzm.lobster.audit.ModelCallLogDao;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;

/**
 * 防止"透明代理漏转发"回归：LlmRuntime.AuditingHandler 必须把 isCancelled()
 * 转发给底层 delegate handler，否则 adapter 看到的永远是默认 false，cancel 失效。
 */
public class AuditingHandlerForwardingTest {

    @Test
    public void auditingHandlerForwardsIsCancelledToDelegate() throws Exception {
        final AtomicBoolean cancelSource = new AtomicBoolean(false);

        StreamingResponseHandler delegate = new StreamingResponseHandler() {
            @Override public void onDelta(String delta) {}
            @Override public void onToolCall(ToolCall toolCall) {}
            @Override public void onComplete(LlmResponse response) {}
            @Override public void onError(Throwable error) {}
            @Override public boolean isCancelled() { return cancelSource.get(); }
        };

        // 用反射构造一个 AuditingHandler（package-private 类），只关心 isCancelled 转发
        Class<?> cls = Class.forName("com.gzzm.lobster.llm.LlmRuntime$AuditingHandler");
        Constructor<?> ctor = cls.getDeclaredConstructors()[0];
        ctor.setAccessible(true);

        LlmRuntime outer = new LlmRuntime();
        // outer.modelCallLogDao 可能为 null；onComplete/onError 这条路径这里不触发，
        // 仅验证 isCancelled 转发 —— 所以不需要注入。
        java.lang.reflect.Field f = LlmRuntime.class.getDeclaredField("modelCallLogDao");
        f.setAccessible(true);
        f.set(outer, mock(ModelCallLogDao.class));

        LlmCallRequest req = new LlmCallRequest();
        ModelProfile profile = new ModelProfile();
        profile.setModelId("test");
        profile.setProvider(ModelProvider.openai_compatible);
        profile.setProtocol(ModelProtocol.chat_completions);

        StreamingResponseHandler wrap = (StreamingResponseHandler) ctor.newInstance(
                delegate, req, profile, false, null, System.currentTimeMillis(), outer,
                java.util.Collections.<LobsterMessage>emptyList(),
                java.util.Collections.<ToolSpec>emptyList());

        assertFalse("初始未取消", wrap.isCancelled());
        cancelSource.set(true);
        assertTrue("wrapper 必须把 delegate 的 cancel 信号穿透下去", wrap.isCancelled());
    }

    /**
     * 防"thinking 失踪"回归：thinking-mode 模型的 reasoning_content 必须从 adapter 透传到
     * delegate（最终到 SSE 的 assistant_thinking 事件）；任何中间 wrapper 漏转发，前端就
     * 看不到思考块.
     */
    @Test
    public void auditingHandlerForwardsReasoningDeltaToDelegate() throws Exception {
        final AtomicReference<String> received = new AtomicReference<>();

        StreamingResponseHandler delegate = new StreamingResponseHandler() {
            @Override public void onDelta(String delta) {}
            @Override public void onReasoningDelta(String delta) { received.set(delta); }
            @Override public void onToolCall(ToolCall toolCall) {}
            @Override public void onComplete(LlmResponse response) {}
            @Override public void onError(Throwable error) {}
        };

        Class<?> cls = Class.forName("com.gzzm.lobster.llm.LlmRuntime$AuditingHandler");
        Constructor<?> ctor = cls.getDeclaredConstructors()[0];
        ctor.setAccessible(true);

        LlmRuntime outer = new LlmRuntime();
        java.lang.reflect.Field f = LlmRuntime.class.getDeclaredField("modelCallLogDao");
        f.setAccessible(true);
        f.set(outer, mock(ModelCallLogDao.class));

        LlmCallRequest req = new LlmCallRequest();
        ModelProfile profile = new ModelProfile();
        profile.setModelId("test");
        profile.setProvider(ModelProvider.openai_compatible);
        profile.setProtocol(ModelProtocol.chat_completions);

        StreamingResponseHandler wrap = (StreamingResponseHandler) ctor.newInstance(
                delegate, req, profile, false, null, System.currentTimeMillis(), outer,
                java.util.Collections.<LobsterMessage>emptyList(),
                java.util.Collections.<ToolSpec>emptyList());

        wrap.onReasoningDelta("hmm let me think...");
        assertEquals("wrapper 必须把 reasoning 分片透传给 delegate",
                "hmm let me think...", received.get());
    }
}
