import { beforeEach, describe, expect, it } from "vitest";

import { ChatSessionMode, ChatTurnStatus } from "@gadget/api";
import { AgentService, type IAgentWorkOrder } from "./agent.ts";

describe("AgentService", () => {
  let service: AgentService;

  beforeEach(async () => {
    service = new AgentService();
    await service.start();
  });

  it("replays historical tool results as role:tool messages with unaltered content", () => {
    const user = {
      _id: "user-1",
      email: "user@example.com",
      email_lc: "user@example.com",
      displayName: "User",
      flags: {
        isEmailVerified: true,
        isAdmin: false,
        isTest: true,
        isBanned: false,
      },
    };
    const session = {
      _id: "session-1",
      createdAt: new Date(),
      user,
      project: "project-1",
      name: "Test Session",
      mode: ChatSessionMode.Build,
      provider: "provider-1",
      selectedModel: "model",
      stats: {
        turnCount: 0,
        toolCallCount: 0,
        inputTokens: 0,
        outputTokens: 0,
      },
      contextWindowUsage: 0,
      pins: [],
    };
    const workOrder: IAgentWorkOrder = {
      createdAt: new Date(),
      turn: {
        _id: "turn-current",
        createdAt: new Date(),
        user,
        project: "project-1",
        session,
        provider: "provider-1",
        llm: "model",
        mode: ChatSessionMode.Build,
        status: ChatTurnStatus.Processing,
        prompts: { user: "continue" },
        blocks: [],
        toolCalls: [],
        subagents: [],
        stats: {
          toolCallCount: 0,
          inputTokens: 0,
          thinkingTokenCount: 0,
          responseTokens: 0,
          durationMs: 0,
          durationLabel: "pending",
        },
      },
      context: [
        {
          _id: "turn-1",
          createdAt: new Date(),
          user,
          project: "project-1",
          session,
          provider: "provider-1",
          llm: "model",
          mode: ChatSessionMode.Build,
          status: ChatTurnStatus.Finished,
          prompts: { user: "Read index.html" },
          blocks: [
            {
              mode: "responding",
              createdAt: new Date(),
              content: "I read the file.",
            },
          ],
          toolCalls: [
            {
              callId: "call-1",
              name: "file_read",
              parameters: '{"path":"index.html"}',
              response: "PATH: index.html\n---\ncontent",
            },
          ],
          subagents: [],
          stats: {
            toolCallCount: 1,
            inputTokens: 0,
            thinkingTokenCount: 0,
            responseTokens: 0,
            durationMs: 0,
            durationLabel: "done",
          },
        },
      ],
    };

    const messages = service.buildSessionContext(workOrder);

    expect(messages.map((message) => message.role)).toEqual([
      "user",
      "assistant",
      "tool",
    ]);
    expect(messages[1]?.toolCalls).toBeDefined();
    expect(messages[1]?.toolCalls).toHaveLength(1);
    expect(messages[1]?.toolCalls?.[0]?.id).toBe("call-1");
    expect(messages[1]?.toolCalls?.[0]?.function?.name).toBe("file_read");
    expect(messages[2]?.role).toBe("tool");
    expect(messages[2]?.content).toBe("PATH: index.html\n---\ncontent");
    expect(messages[2]?.toolCallId).toBe("call-1");
  });

  it("uses subagent.response instead of full JSON for subagent tool calls in context replay", () => {
    const user = {
      _id: "user-1",
      email: "user@example.com",
      email_lc: "user@example.com",
      displayName: "User",
      flags: {
        isEmailVerified: true,
        isAdmin: false,
        isTest: true,
        isBanned: false,
      },
    };
    const session = {
      _id: "session-1",
      createdAt: new Date(),
      user,
      project: "project-1",
      name: "Test Session",
      mode: ChatSessionMode.Build,
      provider: "provider-1",
      selectedModel: "model",
      stats: {
        turnCount: 0,
        toolCallCount: 0,
        inputTokens: 0,
        outputTokens: 0,
      },
      contextWindowUsage: 0,
      pins: [],
    };

    const fullSubagentResult = JSON.stringify({
      success: true,
      data: {
        prompt: "Find the main entry point",
        thinking: "Let me check package.json first, then look for index files...",
        response: "The main entry point is src/index.ts as specified in package.json.",
        toolCalls: [
          {
            callId: "sa-call-1",
            name: "file_read",
            parameters: '{"path":"package.json"}',
            response: "PATH: package.json\n---\n{\"main\": \"src/index.ts\"}",
          },
        ],
        stats: {
          toolCallCount: 1,
          inputTokens: 150,
          thinkingTokenCount: 80,
          responseTokens: 20,
          durationMs: 3500,
          durationLabel: "00:03",
        },
      },
    });

    const workOrder: IAgentWorkOrder = {
      createdAt: new Date(),
      turn: {
        _id: "turn-current",
        createdAt: new Date(),
        user,
        project: "project-1",
        session,
        provider: "provider-1",
        llm: "model",
        mode: ChatSessionMode.Build,
        status: ChatTurnStatus.Processing,
        prompts: { user: "continue" },
        blocks: [],
        toolCalls: [],
        subagents: [],
        stats: {
          toolCallCount: 0,
          inputTokens: 0,
          thinkingTokenCount: 0,
          responseTokens: 0,
          durationMs: 0,
          durationLabel: "pending",
        },
      },
      context: [
        {
          _id: "turn-sa-1",
          createdAt: new Date(),
          user,
          project: "project-1",
          session,
          provider: "provider-1",
          llm: "model",
          mode: ChatSessionMode.Build,
          status: ChatTurnStatus.Finished,
          prompts: { user: "Find the main entry point" },
          blocks: [
            {
              mode: "responding",
              createdAt: new Date(),
              content: "I'll spawn a subagent to find that.",
            },
          ],
          toolCalls: [
            {
              callId: "sa-call-master",
              name: "subagent",
              parameters: '{"agent_type":"explore","prompt":"Find the main entry point"}',
              response: fullSubagentResult,
              subagent: {
                prompt: "Find the main entry point",
                thinking: "Let me check package.json first...",
                response: "The main entry point is src/index.ts as specified in package.json.",
                toolCalls: [
                  {
                    callId: "sa-call-1",
                    name: "file_read",
                    parameters: '{"path":"package.json"}',
                    response: "PATH: package.json\n---\n{\"main\": \"src/index.ts\"}",
                  },
                ],
                stats: {
                  toolCallCount: 1,
                  inputTokens: 150,
                  thinkingTokenCount: 80,
                  responseTokens: 20,
                  durationMs: 3500,
                  durationLabel: "00:03",
                },
              },
            },
          ],
          subagents: [],
          stats: {
            toolCallCount: 1,
            inputTokens: 0,
            thinkingTokenCount: 0,
            responseTokens: 0,
            durationMs: 0,
            durationLabel: "done",
          },
        },
      ],
    };

    const messages = service.buildSessionContext(workOrder);

    expect(messages.map((message) => message.role)).toEqual([
      "user",
      "assistant",
      "tool",
    ]);
    // The tool response should be ONLY the subagent's response text,
    // not the full JSON blob with thinking, toolCalls, and stats.
    const toolMessage = messages[2];
    expect(toolMessage?.role).toBe("tool");
    expect(toolMessage?.toolCallId).toBe("sa-call-master");
    expect(toolMessage?.toolName).toBe("subagent");
    expect(toolMessage?.content).toBe(
      "The main entry point is src/index.ts as specified in package.json.",
    );
    // Explicitly verify the full JSON is NOT in context
    expect(toolMessage?.content).not.toContain('"success":true');
    expect(toolMessage?.content).not.toContain('"toolCalls"');
    expect(toolMessage?.content).not.toContain('"thinking"');
  });

  it("falls back to toolCall.response for subagent calls without parsed subagent data", () => {
    const user = {
      _id: "user-1",
      email: "user@example.com",
      email_lc: "user@example.com",
      displayName: "User",
      flags: {
        isEmailVerified: true,
        isAdmin: false,
        isTest: true,
        isBanned: false,
      },
    };
    const session = {
      _id: "session-1",
      createdAt: new Date(),
      user,
      project: "project-1",
      name: "Test Session",
      mode: ChatSessionMode.Build,
      provider: "provider-1",
      selectedModel: "model",
      stats: {
        turnCount: 0,
        toolCallCount: 0,
        inputTokens: 0,
        outputTokens: 0,
      },
      contextWindowUsage: 0,
      pins: [],
    };
    const rawResponse = "Subagent completed the task successfully.";

    const workOrder: IAgentWorkOrder = {
      createdAt: new Date(),
      turn: {
        _id: "turn-current",
        createdAt: new Date(),
        user,
        project: "project-1",
        session,
        provider: "provider-1",
        llm: "model",
        mode: ChatSessionMode.Build,
        status: ChatTurnStatus.Processing,
        prompts: { user: "continue" },
        blocks: [],
        toolCalls: [],
        subagents: [],
        stats: {
          toolCallCount: 0,
          inputTokens: 0,
          thinkingTokenCount: 0,
          responseTokens: 0,
          durationMs: 0,
          durationLabel: "pending",
        },
      },
      context: [
        {
          _id: "turn-fallback",
          createdAt: new Date(),
          user,
          project: "project-1",
          session,
          provider: "provider-1",
          llm: "model",
          mode: ChatSessionMode.Build,
          status: ChatTurnStatus.Finished,
          prompts: { user: "Do a task" },
          blocks: [
            {
              mode: "responding",
              createdAt: new Date(),
              content: "Spawning a subagent.",
            },
          ],
          toolCalls: [
            {
              callId: "call-fallback",
              name: "subagent",
              parameters: '{"agent_type":"general","prompt":"Do a task"}',
              response: rawResponse,
              // subagent field is intentionally omitted — simulates
              // historical data from before the fix was deployed
            },
          ],
          subagents: [],
          stats: {
            toolCallCount: 1,
            inputTokens: 0,
            thinkingTokenCount: 0,
            responseTokens: 0,
            durationMs: 0,
            durationLabel: "done",
          },
        },
      ],
    };

    const messages = service.buildSessionContext(workOrder);
    const toolMessage = messages[2];
    expect(toolMessage?.content).toBe(rawResponse);
  });

  it("does not expose mutating file tools in plan mode", () => {
    const toolNames = service.getToolNamesForMode(ChatSessionMode.Plan);

    expect(toolNames).toContain("file_read");
    expect(toolNames).toContain("fetch_url");
    expect(toolNames).toContain("search_google");
    expect(toolNames).not.toContain("file_write");
    expect(toolNames).not.toContain("file_edit");

    // Plan mode has .gadget tools for its own storage
    expect(toolNames).toContain("plan_file_read");
    expect(toolNames).toContain("plan_file_write");
    expect(toolNames).toContain("plan_file_edit");
    expect(toolNames).toContain("plan_list");
  });

  it("exposes mutating file tools in build mode", () => {
    const toolNames = service.getToolNamesForMode(ChatSessionMode.Build);

    expect(toolNames).toContain("file_write");
    expect(toolNames).toContain("file_edit");

    // Build mode also has .gadget plan tools (available in all modes)
    expect(toolNames).toContain("plan_file_read");
    expect(toolNames).toContain("plan_file_write");
    expect(toolNames).toContain("plan_file_edit");
    expect(toolNames).toContain("plan_list");
  });

  it("exposes plan tools in all modes", () => {
    for (const mode of [ChatSessionMode.Build, ChatSessionMode.Test, ChatSessionMode.Ship, ChatSessionMode.Develop]) {
      const toolNames = service.getToolNamesForMode(mode);
      expect(toolNames).toContain("plan_file_read");
      expect(toolNames).toContain("plan_file_write");
      expect(toolNames).toContain("plan_file_edit");
      expect(toolNames).toContain("plan_list");
    }
  });
});
