import { describe, expect, it, vi } from "vitest";
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
import { AssistantCloudThreads } from "./AssistantCloudThreads";

const createCloudThreads = () => {
  const makeRequest = vi.fn();
  const api = { makeRequest } as unknown as AssistantCloudAPI;
  return { threads: new AssistantCloudThreads(api), makeRequest };
};

const threadResponse = {
  title: null,
  last_message_at: "2026-07-16T12:30:00.000Z",
  metadata: { created_at: "leave-this-string-untouched" },
  external_id: null,
  id: "thread-1",
  project_id: "project-1",
  created_at: "2026-07-16T12:00:00.000Z",
  updated_at: "2026-07-16T12:15:00.123Z",
  workspace_id: "workspace-1",
  is_archived: false,
};

describe("AssistantCloudThreads responses", () => {
  it("validates created thread IDs", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValueOnce({ thread_id: "thread-1" });

    await expect(
      threads.create({ last_message_at: new Date() }),
    ).resolves.toEqual({ thread_id: "thread-1" });

    makeRequest.mockResolvedValueOnce({});

    await expect(
      threads.create({ last_message_at: new Date() }),
    ).rejects.toThrow(
      'Invalid Assistant Cloud response for "thread_id": expected a string',
    );
  });

  it("sends upsert with the external id when creating a thread", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValueOnce({ thread_id: "thread-1" });
    const last_message_at = new Date("2026-09-25T09:30:00.000Z");

    await expect(
      threads.create({ last_message_at, external_id: "crm:42", upsert: true }),
    ).resolves.toEqual({ thread_id: "thread-1" });
    expect(makeRequest).toHaveBeenCalledWith("/threads", {
      method: "POST",
      body: { last_message_at, external_id: "crm:42", upsert: true },
    });
  });

  it("claims anonymous threads and validates the moved count", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValueOnce({ moved: 2 });

    await expect(
      threads.claim({ refresh_token: "anonymous-refresh" }),
    ).resolves.toEqual({ moved: 2 });
    expect(makeRequest).toHaveBeenLastCalledWith("/threads/claim", {
      method: "POST",
      body: { refresh_token: "anonymous-refresh" },
    });

    makeRequest.mockResolvedValueOnce({ moved: 1.5 });

    await expect(
      threads.claim({ refresh_token: "anonymous-refresh" }),
    ).rejects.toThrow(
      'Invalid Assistant Cloud response for "moved": expected an integer',
    );
  });

  it("forwards both archive filter values", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValue({ threads: [] });

    await threads.list({ is_archived: false });
    expect(makeRequest).toHaveBeenLastCalledWith("/threads", {
      query: { is_archived: "false" },
    });

    await threads.list({ is_archived: true });
    expect(makeRequest).toHaveBeenLastCalledWith("/threads", {
      query: { is_archived: "true" },
    });
  });

  it("decodes canonical thread list responses", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValue({ threads: [threadResponse] });

    const result = await threads.list();
    const thread = result.threads[0]!;

    expect(thread.created_at).toBeInstanceOf(Date);
    expect(thread.updated_at).toBeInstanceOf(Date);
    expect(thread.last_message_at).toBeInstanceOf(Date);
    expect(thread.updated_at.toISOString()).toBe("2026-07-16T12:15:00.123Z");
    expect(thread.title).toBe("");
    expect(thread.metadata).toEqual({
      created_at: "leave-this-string-untouched",
    });
  });

  it("unwraps and decodes a single thread response", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValue({ thread: threadResponse });

    const result = await threads.get("thread-1");

    expect(result.created_at).toBeInstanceOf(Date);
    expect(result.created_at.toISOString()).toBe("2026-07-16T12:00:00.000Z");
  });

  it("rejects a bare thread that is missing the response envelope", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValue(threadResponse);

    await expect(threads.get("thread-1")).rejects.toThrow(
      'Invalid Assistant Cloud response for "thread": expected an object',
    );
  });

  it("rejects genuinely invalid response timestamps with field context", async () => {
    const { threads, makeRequest } = createCloudThreads();
    makeRequest.mockResolvedValue({
      thread: {
        ...threadResponse,
        updated_at: "not-a-timestamp",
      },
    });

    await expect(threads.get("thread-1")).rejects.toThrow(
      'Invalid Assistant Cloud response for "thread.updated_at": expected a canonical ISO timestamp',
    );
  });
});
