import { describe, it, expect, vi } from "vitest";
import { InMemoryJobQueue } from "@/jobs/in-memory-job-queue";
import type { IJobQueue } from "@/jobs/job-queue.interface";

describe("InMemoryJobQueue", () => {
  it("returns a synthetic jobId on enqueue", async () => {
    const handler = vi.fn();
    const queue: IJobQueue = new InMemoryJobQueue({
      "test.task": handler,
    });
    const result = await queue.enqueue("test.task", { x: 1 });
    expect(result.jobId).toMatch(/^in-memory-/);
  });

  it("invokes the registered handler asynchronously with the input", async () => {
    const handler = vi.fn();
    const queue = new InMemoryJobQueue({ "test.task": handler });
    await queue.enqueue("test.task", { x: 42 });
    await new Promise((r) => setImmediate(r));
    expect(handler).toHaveBeenCalledWith({ x: 42 });
  });

  it("throws if the task slug has no registered handler", async () => {
    const queue = new InMemoryJobQueue({});
    await expect(queue.enqueue("missing.task", {})).rejects.toThrow(
      /no handler registered for task slug: missing\.task/,
    );
  });

  it("delays execution when runAt is in the future", async () => {
    vi.useFakeTimers();
    try {
      const handler = vi.fn();
      const queue = new InMemoryJobQueue({ "test.task": handler });
      const future = new Date(Date.now() + 1000);
      await queue.enqueue("test.task", {}, { runAt: future });
      expect(handler).not.toHaveBeenCalled();
      vi.advanceTimersByTime(1000);
      await Promise.resolve();
      expect(handler).toHaveBeenCalledTimes(1);
    } finally {
      vi.useRealTimers();
    }
  });

  it("register adds a handler that can be enqueued against", async () => {
    const queue = new InMemoryJobQueue();
    const handler = vi.fn();
    queue.register("late.task", handler);
    await queue.enqueue("late.task", { z: 1 });
    await new Promise((r) => setImmediate(r));
    expect(handler).toHaveBeenCalledWith({ z: 1 });
  });
});
