// [nexus] Tests for assistant handoff route (Plan 33-03)
import { describe, it, expect, vi, beforeEach } from "vitest";

// ---------------------------------------------------------------------------
// Mock chatService so we can track calls without a real DB
// ---------------------------------------------------------------------------

const mockGetConversation = vi.fn();
const mockListMessages = vi.fn();
const mockCreateConversation = vi.fn();
const mockAddSystemMessage = vi.fn();

vi.mock("../services/chat.js", () => ({
  chatService: () => ({
    getConversation: mockGetConversation,
    listMessages: mockListMessages,
    createConversation: mockCreateConversation,
    addSystemMessage: mockAddSystemMessage,
  }),
}));

// Mock authz — assertBoard is a no-op in tests
vi.mock("../routes/authz.js", () => ({
  assertBoard: vi.fn(),
}));

// ---------------------------------------------------------------------------
// Import the route factory under test
// ---------------------------------------------------------------------------
const { buildHandoffSummary, assistantHandoffRoutes } = await import("../routes/assistant-handoff.js");

// ---------------------------------------------------------------------------
// Unit tests for buildHandoffSummary (pure function)
// ---------------------------------------------------------------------------

describe("buildHandoffSummary", () => {
  it("concatenates user messages into a summary", () => {
    const messages = [
      { role: "user", content: "Hello world" },
      { role: "assistant", content: "Hi there" },
      { role: "user", content: "Can you build a login page?" },
    ];
    const summary = buildHandoffSummary(messages as Array<{ role: string; content: string }>);
    expect(summary).toContain("Hello world");
    expect(summary).toContain("Can you build a login page?");
    expect(summary).not.toContain("Hi there"); // assistant message excluded
  });

  it("excludes non-user messages (assistant, system)", () => {
    const messages = [
      { role: "system", content: "You are a helpful assistant" },
      { role: "user", content: "Build me a dashboard" },
      { role: "assistant", content: "Sure!" },
    ];
    const summary = buildHandoffSummary(messages as Array<{ role: string; content: string }>);
    expect(summary).toContain("Build me a dashboard");
    expect(summary).not.toContain("You are a helpful assistant");
    expect(summary).not.toContain("Sure!");
  });

  it("caps summary at 1500 chars", () => {
    const longContent = "A".repeat(600);
    const messages = [
      { role: "user", content: longContent },
      { role: "user", content: longContent },
      { role: "user", content: longContent },
    ];
    const summary = buildHandoffSummary(messages as Array<{ role: string; content: string }>);
    expect(summary.length).toBeLessThanOrEqual(1500);
  });

  it("returns empty string if no user messages", () => {
    const messages = [
      { role: "assistant", content: "Hello!" },
      { role: "system", content: "Context here" },
    ];
    const summary = buildHandoffSummary(messages as Array<{ role: string; content: string }>);
    expect(summary).toBe("");
  });
});

// ---------------------------------------------------------------------------
// Integration-style tests for the route handler
// ---------------------------------------------------------------------------

describe("POST /conversations/:id/assistant-handoff", () => {
  beforeEach(() => {
    vi.clearAllMocks();

    mockGetConversation.mockResolvedValue({
      id: "conv-123",
      companyId: "company-abc",
      agentId: null,
      title: "Test conversation",
    });

    mockListMessages.mockResolvedValue({
      items: [
        { role: "user", content: "I want to build a todo app" },
        { role: "assistant", content: "Great idea! Let me help." },
        { role: "user", content: "It should have drag-and-drop" },
      ],
      hasMore: false,
      nextCursor: null,
    });

    mockCreateConversation.mockResolvedValue({
      id: "new-conv-999",
      companyId: "company-abc",
      title: "Project from assistant chat",
      agentId: null,
    });

    mockAddSystemMessage.mockResolvedValue({
      id: "msg-handoff-1",
      role: "system",
      content: "summary",
      messageType: "handoff_context",
    });
  });

  it("creates a new conversation with handoff_context system message", async () => {
    const router = assistantHandoffRoutes({} as any);

    // Find the POST handler for /conversations/:id/assistant-handoff
    const layer = (router as any).stack.find(
      (l: any) => l.route?.path === "/conversations/:id/assistant-handoff" && l.route?.methods?.post,
    );
    expect(layer).toBeDefined();

    const handler = layer.route.stack[0].handle;
    const req = { params: { id: "conv-123" }, actor: { type: "board" } } as any;
    const res = { json: vi.fn(), status: vi.fn().mockReturnThis() } as any;

    await handler(req, res);

    expect(mockGetConversation).toHaveBeenCalledWith("conv-123");
    expect(mockListMessages).toHaveBeenCalledWith("conv-123", { limit: 20 });
    expect(mockCreateConversation).toHaveBeenCalledWith(
      "company-abc",
      expect.objectContaining({ title: "Project from assistant chat" }),
    );
    expect(mockAddSystemMessage).toHaveBeenCalledWith(
      "new-conv-999",
      expect.objectContaining({ messageType: "handoff_context" }),
    );
    expect(res.json).toHaveBeenCalledWith({ targetConversationId: "new-conv-999" });
  });

  it("builds summary from user messages only", async () => {
    const router = assistantHandoffRoutes({} as any);
    const layer = (router as any).stack.find(
      (l: any) => l.route?.path === "/conversations/:id/assistant-handoff" && l.route?.methods?.post,
    );
    const handler = layer.route.stack[0].handle;
    const req = { params: { id: "conv-123" }, actor: { type: "board" } } as any;
    const res = { json: vi.fn(), status: vi.fn().mockReturnThis() } as any;

    await handler(req, res);

    const callArgs = mockAddSystemMessage.mock.calls[0];
    const systemMessageContent = callArgs[1].content;
    expect(systemMessageContent).toContain("I want to build a todo app");
    expect(systemMessageContent).toContain("It should have drag-and-drop");
    expect(systemMessageContent).not.toContain("Great idea! Let me help.");
  });

  it("caps summary content at 1500 chars in the system message", async () => {
    const longContent = "X".repeat(600);
    mockListMessages.mockResolvedValue({
      items: [
        { role: "user", content: longContent },
        { role: "user", content: longContent },
        { role: "user", content: longContent },
      ],
      hasMore: false,
      nextCursor: null,
    });

    const router = assistantHandoffRoutes({} as any);
    const layer = (router as any).stack.find(
      (l: any) => l.route?.path === "/conversations/:id/assistant-handoff" && l.route?.methods?.post,
    );
    const handler = layer.route.stack[0].handle;
    const req = { params: { id: "conv-123" }, actor: { type: "board" } } as any;
    const res = { json: vi.fn(), status: vi.fn().mockReturnThis() } as any;

    await handler(req, res);

    const callArgs = mockAddSystemMessage.mock.calls[0];
    expect(callArgs[1].content.length).toBeLessThanOrEqual(1500);
  });
});
