/// <reference types="vite/client" />

import { convexTest } from "convex-test";
import { describe, expect, test, vi } from "vitest";
import { api, internal } from "./_generated/api.js";
import type { Id } from "./_generated/dataModel.js";
import schema from "./schema.js";
import { modules } from "./setup.test.js";

describe("users", () => {
  test("listUsersWithThreads returns users who have threads", async () => {
    const t = convexTest(schema, modules);

    // Create two users with threads and one without
    await t.mutation(api.threads.createThread, {
      userId: "user1",
      title: "Test thread 1",
    });
    await t.mutation(api.threads.createThread, {
      userId: "user2",
      title: "Test thread 2",
    });
    await t.mutation(api.threads.createThread, {
      userId: "user1", // Same user, different thread
      title: "Test thread 3",
    });

    const result = await t.query(api.users.listUsersWithThreads, {
      paginationOpts: { cursor: null, numItems: 10 },
    });

    expect(result.page).toHaveLength(2);
    expect(result.page).toContain("user1");
    expect(result.page).toContain("user2");
  });

  test("listUsersWithThreads pagination works", async () => {
    const t = convexTest(schema, modules);

    // Create multiple users with threads
    for (let i = 0; i < 5; i++) {
      await t.mutation(api.threads.createThread, {
        userId: `user${i}`,
        title: `Test thread ${i}`,
      });
    }

    const firstPage = await t.query(api.users.listUsersWithThreads, {
      paginationOpts: { cursor: null, numItems: 2 },
    });

    expect(firstPage.page).toHaveLength(2);
    expect(firstPage.isDone).toBe(false);

    const secondPage = await t.query(api.users.listUsersWithThreads, {
      paginationOpts: { cursor: firstPage.continueCursor, numItems: 2 },
    });

    expect(secondPage.page).toHaveLength(2);
    // Should not have duplicate users
    expect(
      firstPage.page.every((user) => !secondPage.page.includes(user)),
    ).toBe(true);
  });

  test("deleteAllForUserId sync deletes all threads and messages for a user", async () => {
    const t = convexTest(schema, modules);

    // Create a user with multiple threads and messages
    const thread1 = await t.mutation(api.threads.createThread, {
      userId: "testUser",
      title: "Thread 1",
    });
    const thread2 = await t.mutation(api.threads.createThread, {
      userId: "testUser",
      title: "Thread 2",
    });

    // Add messages to both threads
    await t.mutation(api.messages.addMessages, {
      threadId: thread1._id as Id<"threads">,
      messages: [
        { message: { role: "user" as const, content: "Hello thread 1" } },
        {
          message: { role: "assistant" as const, content: "Response thread 1" },
        },
      ],
    });

    await t.mutation(api.messages.addMessages, {
      threadId: thread2._id as Id<"threads">,
      messages: [
        { message: { role: "user" as const, content: "Hello thread 2" } },
        {
          message: { role: "assistant" as const, content: "Response thread 2" },
        },
      ],
    });

    // Verify data exists before deletion
    const beforeThreads = await t.query(api.threads.listThreadsByUserId, {
      userId: "testUser",
    });
    expect(beforeThreads.page).toHaveLength(2);

    const beforeMessages1 = await t.query(api.messages.listMessagesByThreadId, {
      threadId: thread1._id as Id<"threads">,
      order: "desc",
      paginationOpts: { cursor: null, numItems: 10 },
    });
    const beforeMessages2 = await t.query(api.messages.listMessagesByThreadId, {
      threadId: thread2._id as Id<"threads">,
      order: "desc",
      paginationOpts: { cursor: null, numItems: 10 },
    });
    expect(beforeMessages1.page).toHaveLength(2);
    expect(beforeMessages2.page).toHaveLength(2);

    // Delete all data for the user
    await t.action(api.users.deleteAllForUserId, { userId: "testUser" });

    // Verify all threads are deleted
    const afterThreads = await t.query(api.threads.listThreadsByUserId, {
      userId: "testUser",
    });
    expect(afterThreads.page).toHaveLength(0);

    // Verify threads are actually deleted from DB
    const thread1After = await t.query(api.threads.getThread, {
      threadId: thread1._id as Id<"threads">,
    });
    const thread2After = await t.query(api.threads.getThread, {
      threadId: thread2._id as Id<"threads">,
    });
    expect(thread1After).toBeNull();
    expect(thread2After).toBeNull();
  });

  test("deleteAllForUserIdAsync deletes data asynchronously", async () => {
    // Enable fake timers for scheduled function testing
    vi.useFakeTimers();

    const t = convexTest(schema, modules);

    // Create a user with a thread and messages
    const thread = await t.mutation(api.threads.createThread, {
      userId: "asyncUser",
      title: "Async Thread",
    });

    await t.mutation(api.messages.addMessages, {
      threadId: thread._id as Id<"threads">,
      messages: [
        { message: { role: "user" as const, content: "Hello async" } },
        { message: { role: "assistant" as const, content: "Response async" } },
      ],
    });

    // Start async deletion
    const result = await t.mutation(api.users.deleteAllForUserIdAsync, {
      userId: "asyncUser",
    });

    // If there's more work to do, advance timers and wait for scheduled functions
    if (!result) {
      // Run all pending timers to trigger scheduled functions
      vi.runAllTimers();

      // Wait for all scheduled functions to complete
      await t.finishInProgressScheduledFunctions();
    }

    // Verify deletion completed
    const afterThreads = await t.query(api.threads.listThreadsByUserId, {
      userId: "asyncUser",
    });
    expect(afterThreads.page).toHaveLength(0);

    // Reset to normal timers
    vi.useRealTimers();
  });

  test("deletePageForUserId handles messages phase correctly", async () => {
    const t = convexTest(schema, modules);

    // Create a thread with many messages to test pagination
    const thread = await t.mutation(api.threads.createThread, {
      userId: "paginationUser",
      title: "Pagination Thread",
    });

    // Add multiple messages to force pagination
    const messages = [];
    for (let i = 0; i < 150; i++) {
      messages.push({
        message: { role: "user" as const, content: `Message ${i}` },
      });
    }
    await t.mutation(api.messages.addMessages, {
      threadId: thread._id as Id<"threads">,
      messages,
    });

    // Test first page deletion
    const result1 = await t.mutation(internal.users._deletePageForUserId, {
      userId: "paginationUser",
      messagesCursor: null,
      threadsCursor: null,
      threadInProgress: null,
      streamsInProgress: false,
      streamOrder: undefined,
      deltaCursor: undefined,
    });

    expect(result1.isDone).toBe(false);
    expect(result1.threadInProgress).toBe(thread._id);
    expect(result1.streamsInProgress).toBe(false);
    expect(result1.messagesCursor).toBeTruthy();

    // Continue until messages are done
    let currentResult = result1;
    let iterations = 0;
    while (!currentResult.streamsInProgress && iterations < 10) {
      currentResult = await t.mutation(internal.users._deletePageForUserId, {
        userId: "paginationUser",
        messagesCursor: currentResult.messagesCursor,
        threadsCursor: currentResult.threadsCursor,
        threadInProgress: currentResult.threadInProgress,
        streamsInProgress: currentResult.streamsInProgress,
        streamOrder: currentResult.streamOrder,
        deltaCursor: currentResult.deltaCursor,
      });
      iterations++;
    }

    // Should now be in streams phase
    expect(currentResult.streamsInProgress).toBe(true);
    expect(currentResult.messagesCursor).toBeNull();
  });

  test("deletePageForUserId handles streams phase correctly", async () => {
    const t = convexTest(schema, modules);

    // Create a thread with messages (no streams for this test)
    const thread = await t.mutation(api.threads.createThread, {
      userId: "streamsUser",
      title: "Streams Thread",
    });

    await t.mutation(api.messages.addMessages, {
      threadId: thread._id as Id<"threads">,
      messages: [
        { message: { role: "user" as const, content: "Test message" } },
      ],
    });

    // Test starting directly in streams phase
    const result = await t.mutation(internal.users._deletePageForUserId, {
      userId: "streamsUser",
      messagesCursor: null,
      threadsCursor: null,
      threadInProgress: thread._id as Id<"threads">,
      streamsInProgress: true,
      streamOrder: undefined,
      deltaCursor: undefined,
    });

    expect(result.isDone).toBe(false);
    expect(result.streamsInProgress).toBe(true);
    expect(result.threadInProgress).toBe(thread._id);

    const result2 = await t.mutation(internal.users._deletePageForUserId, {
      userId: "streamsUser",
      messagesCursor: result.messagesCursor,
      threadsCursor: result.threadsCursor,
      threadInProgress: result.threadInProgress,
      streamsInProgress: result.streamsInProgress,
      streamOrder: result.streamOrder,
      deltaCursor: result.deltaCursor,
    });

    expect(result2.isDone).toBe(false);
    expect(result2.threadInProgress).toBeNull();
    expect(result2.streamsInProgress).toBe(false);
    expect(result2.messagesCursor).toBeNull();
    expect(result2.streamOrder).toBeUndefined();
    expect(result2.deltaCursor).toBeUndefined();

    // Thread should be deleted
    const threadAfter = await t.query(api.threads.getThread, {
      threadId: thread._id as Id<"threads">,
    });
    expect(threadAfter).toBeNull();
  });

  test("deletePageForUserId handles multiple threads correctly", async () => {
    const t = convexTest(schema, modules);

    // Create multiple threads for the same user
    const thread1 = await t.mutation(api.threads.createThread, {
      userId: "multiUser",
      title: "Thread 1",
    });
    const thread2 = await t.mutation(api.threads.createThread, {
      userId: "multiUser",
      title: "Thread 2",
    });

    // Add a message to each
    await t.mutation(api.messages.addMessages, {
      threadId: thread1._id as Id<"threads">,
      messages: [{ message: { role: "user" as const, content: "Message 1" } }],
    });
    await t.mutation(api.messages.addMessages, {
      threadId: thread2._id as Id<"threads">,
      messages: [{ message: { role: "user" as const, content: "Message 2" } }],
    });

    // Process first thread completely
    let currentResult = await t.mutation(internal.users._deletePageForUserId, {
      userId: "multiUser",
      messagesCursor: null,
      threadsCursor: null,
      threadInProgress: null,
      streamsInProgress: false,
      streamOrder: undefined,
      deltaCursor: undefined,
    });

    // Continue until first thread is done
    while (currentResult.threadInProgress !== null) {
      currentResult = await t.mutation(internal.users._deletePageForUserId, {
        userId: "multiUser",
        messagesCursor: currentResult.messagesCursor,
        threadsCursor: currentResult.threadsCursor,
        threadInProgress: currentResult.threadInProgress,
        streamsInProgress: currentResult.streamsInProgress,
        streamOrder: currentResult.streamOrder,
        deltaCursor: currentResult.deltaCursor,
      });
    }

    // Should move to second thread
    currentResult = await t.mutation(internal.users._deletePageForUserId, {
      userId: "multiUser",
      messagesCursor: currentResult.messagesCursor,
      threadsCursor: currentResult.threadsCursor,
      threadInProgress: currentResult.threadInProgress,
      streamsInProgress: currentResult.streamsInProgress,
      streamOrder: currentResult.streamOrder,
      deltaCursor: currentResult.deltaCursor,
    });

    expect(currentResult.threadInProgress).toBeTruthy();
    expect(currentResult.isDone).toBe(false);

    // Complete second thread
    while (!currentResult.isDone) {
      currentResult = await t.mutation(internal.users._deletePageForUserId, {
        userId: "multiUser",
        messagesCursor: currentResult.messagesCursor,
        threadsCursor: currentResult.threadsCursor,
        threadInProgress: currentResult.threadInProgress,
        streamsInProgress: currentResult.streamsInProgress,
        streamOrder: currentResult.streamOrder,
        deltaCursor: currentResult.deltaCursor,
      });
    }

    // All threads should be deleted
    const threadsAfter = await t.query(api.threads.listThreadsByUserId, {
      userId: "multiUser",
    });
    expect(threadsAfter.page).toHaveLength(0);
  });

  test("deleteAllForUserIdAsync with multiple scheduled iterations", async () => {
    // Enable fake timers for scheduled function testing
    vi.useFakeTimers();

    const t = convexTest(schema, modules);

    // Create a user with multiple threads and many messages to force multiple scheduling iterations
    const thread1 = await t.mutation(api.threads.createThread, {
      userId: "multiIterUser",
      title: "Multi Iter Thread 1",
    });
    const thread2 = await t.mutation(api.threads.createThread, {
      userId: "multiIterUser",
      title: "Multi Iter Thread 2",
    });

    // Add many messages to force pagination
    const messages = [];
    for (let i = 0; i < 150; i++) {
      messages.push({
        message: { role: "user" as const, content: `Message ${i}` },
      });
    }
    await t.mutation(api.messages.addMessages, {
      threadId: thread1._id as Id<"threads">,
      messages,
    });
    await t.mutation(api.messages.addMessages, {
      threadId: thread2._id as Id<"threads">,
      messages,
    });

    // Start async deletion
    const result = await t.mutation(api.users.deleteAllForUserIdAsync, {
      userId: "multiIterUser",
    });

    // Should return false since there's a lot of work to do
    expect(result).toBe(false);

    // Use finishAllScheduledFunctions to handle recursive scheduling
    await t.finishAllScheduledFunctions(vi.runAllTimers);

    // Verify all data is deleted
    const afterThreads = await t.query(api.threads.listThreadsByUserId, {
      userId: "multiIterUser",
    });
    expect(afterThreads.page).toHaveLength(0);

    // Reset to normal timers
    vi.useRealTimers();
  });

  test("getThreadUserId returns correct userId", async () => {
    const t = convexTest(schema, modules);

    const thread = await t.mutation(api.threads.createThread, {
      userId: "testUserId",
      title: "Test Thread",
    });

    const userId = await t.query(internal.users.getThreadUserId, {
      threadId: thread._id as Id<"threads">,
    });

    expect(userId).toBe("testUserId");
  });

  test("getThreadUserId returns null for non-existent thread", async () => {
    const t = convexTest(schema, modules);

    // Create a thread, then delete it to get a valid but non-existent ID
    const thread = await t.mutation(api.threads.createThread, {
      userId: "tempUser",
      title: "Temp Thread",
    });

    // Delete the thread so it no longer exists
    await t.action(api.threads.deleteAllForThreadIdSync, {
      threadId: thread._id as Id<"threads">,
    });

    // Now test with the deleted thread's ID
    const userId = await t.query(internal.users.getThreadUserId, {
      threadId: thread._id as Id<"threads">,
    });

    expect(userId).toBeNull();
  });

  test("deleteAllForUserId handles user with no threads", async () => {
    const t = convexTest(schema, modules);

    // Try to delete for a user that doesn't exist
    await expect(
      t.action(api.users.deleteAllForUserId, { userId: "nonexistentUser" }),
    ).resolves.not.toThrow();

    // Should complete successfully without errors
  });
});
