import { describe, expect, it } from "bun:test";

import {
  buildForkedSessionUiState,
} from "../../src/routes/chat.js";
import {
  buildPromptWithLearningContext,
  extractLatestFrontendTurn,
  generateSessionTitle,
  shouldRestoreConversationForRuntime,
  shouldGenerateSessionTitle,
} from "../../src/routes/chatSession.js";
import { type SessionTurnRecord } from "../../src/sessions/transcriptStore.js";
import { type MemoryStore } from "../../src/memory/store.js";
import { type OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";

describe("shouldGenerateSessionTitle", () => {
  it("allows auto-title generation for the first turn when the title was not edited", () => {
    expect(
      shouldGenerateSessionTitle({
        recentTurnCount: 0,
        isTitleManuallyEdited: false,
      }),
    ).toBe(true);
  });

  it("blocks auto-title generation after the user edits the title manually", () => {
    expect(
      shouldGenerateSessionTitle({
        recentTurnCount: 0,
        isTitleManuallyEdited: true,
      }),
    ).toBe(false);
  });

  it("only allows auto-title generation during the first two turns", () => {
    expect(
      shouldGenerateSessionTitle({
        recentTurnCount: 1,
        isTitleManuallyEdited: false,
      }),
    ).toBe(true);
    expect(
      shouldGenerateSessionTitle({
        recentTurnCount: 2,
        isTitleManuallyEdited: false,
      }),
    ).toBe(false);
  });
});

describe("generateSessionTitle", () => {
  it("uses the current user and assistant turn instead of reading wrapped runtime context", async () => {
    let titlePrompt = "";
    const runtime = {
      createSession: async () => ({ id: "title-session" }),
      prompt: async (_sessionId: string, prompt: string) => {
        titlePrompt = prompt;
      },
      waitForSessionIdle: async () => undefined,
      messages: async () => [
        {
          info: { role: "assistant" },
          parts: [{ type: "text", text: "标题：泵站压力异常排查。" }],
        },
      ],
      abortSession: async () => undefined,
    } as unknown as OpencodeRuntimeAdapter;

    const title = await generateSessionTitle(runtime, {
      sessionId: "chat-session",
      latestUserMessage: "检查一下三号泵站最近压力波动的原因",
      latestAssistantMessage: "三号泵站压力波动主要与夜间阀门开度变化有关。",
      fallbackTitle: "新对话",
    });

    expect(title).toBe("泵站压力异常排查");
    expect(titlePrompt).toContain("用户：检查一下三号泵站最近压力波动的原因");
    expect(titlePrompt).toContain("助手：三号泵站压力波动主要与夜间阀门开度变化有关。");
  });
});

describe("buildPromptWithLearningContext", () => {
  const memoryStore = {
    buildPromptSnapshot: async () => "",
  } as unknown as MemoryStore;

  it("prefers persisted frontend messages so aborted turns remain in restored context", async () => {
    const prompt = await buildPromptWithLearningContext(
      memoryStore,
      "actor-1",
      "project-1",
      {
        recentTurns: [],
        persistedMessages: [
          { role: "user", content: "先分析 3 号泵站夜间压力波动" },
          {
            role: "assistant",
            content: "已定位到夜间阀门开度变化与压力波动时间段重合，下一步准备对比相邻支路。",
            isError: true,
          },
          { role: "assistant", content: "⚠️ **请求已中断**", isError: true },
        ],
        message: "继续刚才的分析，并补充相邻支路影响",
      },
    );

    expect(prompt).toContain("用户：先分析 3 号泵站夜间压力波动");
    expect(prompt).toContain(
      "助手：已定位到夜间阀门开度变化与压力波动时间段重合，下一步准备对比相邻支路。",
    );
    expect(prompt).not.toContain("⚠️ **请求已中断**");
    expect(prompt).toContain("[Current user request]\n继续刚才的分析，并补充相邻支路影响");
  });

  it("falls back to history turns when frontend state is unavailable", async () => {
    const recentTurns: SessionTurnRecord[] = [
      {
        id: "turn-1",
        userMessage: "检查 DMA-2 夜间漏损异常",
        assistantMessage: "DMA-2 在 02:00-04:00 出现持续最小夜流抬升。",
        timestamp: new Date().toISOString(),
        toolCallCount: 1,
      },
    ];

    const prompt = await buildPromptWithLearningContext(
      memoryStore,
      "actor-1",
      "project-1",
      {
        recentTurns,
        message: "继续给出排查建议",
      },
    );

    expect(prompt).toContain("用户：检查 DMA-2 夜间漏损异常");
    expect(prompt).toContain("助手：DMA-2 在 02:00-04:00 出现持续最小夜流抬升。");
  });

  it("skips restored conversation injection when reusing an existing opencode session", async () => {
    const prompt = await buildPromptWithLearningContext(
      memoryStore,
      "actor-1",
      "project-1",
      {
        recentTurns: [
          {
            id: "turn-1",
            userMessage: "上一轮问题",
            assistantMessage: "上一轮回答",
            timestamp: new Date().toISOString(),
            toolCallCount: 0,
          },
        ],
        persistedMessages: [
          { role: "user", content: "旧问题" },
          { role: "assistant", content: "旧回答" },
        ],
        message: "基于刚才结果继续分析",
        restoreConversation: false,
      },
    );

    expect(prompt).not.toContain("[Previous conversation context]");
    expect(prompt).toBe("基于刚才结果继续分析");
  });

  it("restores copied fork context when metadata exists but runtime has no conversation", () => {
    expect(
      shouldRestoreConversationForRuntime({
        hadExistingSessionRecord: true,
        runtimeHasConversation: false,
      }),
    ).toBe(true);
    expect(
      shouldRestoreConversationForRuntime({
        hadExistingSessionRecord: true,
        runtimeHasConversation: true,
      }),
    ).toBe(false);
  });
});

describe("extractLatestFrontendTurn", () => {
  it("extracts the latest valid frontend user and assistant turn", () => {
    const turn = extractLatestFrontendTurn([
      { role: "user", content: "检查 DMA-2 漏损" },
      {
        role: "assistant",
        content: "DMA-2 夜间最小流量持续抬升。",
        progress: [{ id: "tool-dma", phase: "tool" }],
      },
      { role: "user", content: "继续分析相邻分区" },
      { role: "assistant", content: "⚠️ **请求已中断**", isError: true },
    ]);

    expect(turn).toEqual({
      assistantMessage: "DMA-2 夜间最小流量持续抬升。",
      toolCallCount: 1,
      userMessage: "检查 DMA-2 漏损",
    });
  });
});

describe("buildForkedSessionUiState", () => {
  it("copies truncated source messages and preserves tool artifacts", () => {
    const forked = buildForkedSessionUiState(
      {
        messages: [
          { role: "user", content: "画压力曲线" },
          {
            role: "assistant",
            content: "已生成图表",
            artifacts: [
              {
                id: "chart-1",
                tool: "show_chart",
                kind: "chart",
                params: { chart_type: "line" },
              },
            ],
          },
          { role: "user", content: "继续分析" },
        ],
      },
      {
        keepMessageCount: 2,
        targetSessionId: "forked-session",
      },
    );

    expect(forked).toEqual({
      sessionId: "forked-session",
      isTitleManuallyEdited: false,
      messages: [
        { role: "user", content: "画压力曲线" },
        {
          role: "assistant",
          content: "已生成图表",
          artifacts: [
            {
              id: "chart-1",
              tool: "show_chart",
              kind: "chart",
              params: { chart_type: "line" },
            },
          ],
        },
      ],
    });
  });

  it("creates an empty branch state when source UI state is missing or keep count is zero", () => {
    expect(
      buildForkedSessionUiState(null, {
        keepMessageCount: 3,
        targetSessionId: "forked-without-source",
      }),
    ).toEqual({
      sessionId: "forked-without-source",
      isTitleManuallyEdited: false,
      messages: [],
    });

    expect(
      buildForkedSessionUiState(
        { messages: [{ role: "user", content: "不保留" }] },
        {
          keepMessageCount: 0,
          targetSessionId: "forked-empty",
        },
      ),
    ).toEqual({
      sessionId: "forked-empty",
      isTitleManuallyEdited: false,
      messages: [],
    });
  });
});
