import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { Queue } from "bullmq";
import { getQueueToken } from "@nestjs/bullmq";
import { EmbeddingQueueService } from "./embedding-queue.service";

describe("EmbeddingQueueService", () => {
  let service: EmbeddingQueueService;
  let queue: Queue;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        EmbeddingQueueService,
        {
          provide: getQueueToken("embeddings"),
          useValue: {
            add: vi.fn(),
            getJobCounts: vi.fn(),
            clean: vi.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<EmbeddingQueueService>(EmbeddingQueueService);
    queue = module.get<Queue>(getQueueToken("embeddings"));
  });

  describe("queueEmbeddingJob", () => {
    it("should queue embedding job with correct data", async () => {
      const entryId = "entry-123";
      const content = "test content";
      const model = "mxbai-embed-large";

      vi.spyOn(queue, "add").mockResolvedValue({} as never);

      await service.queueEmbeddingJob(entryId, content, model);

      expect(queue.add).toHaveBeenCalledWith(
        "generate-embedding",
        {
          entryId,
          content,
          model,
        },
        expect.objectContaining({
          attempts: 3,
          backoff: {
            type: "exponential",
            delay: 5000,
          },
        })
      );
    });

    it("should use default model when not specified", async () => {
      const entryId = "entry-123";
      const content = "test content";

      vi.spyOn(queue, "add").mockResolvedValue({} as never);

      await service.queueEmbeddingJob(entryId, content);

      expect(queue.add).toHaveBeenCalledWith(
        "generate-embedding",
        {
          entryId,
          content,
          model: undefined,
        },
        expect.any(Object)
      );
    });

    it("should apply rate limiting delay", async () => {
      const entryId = "entry-123";
      const content = "test content";

      vi.spyOn(queue, "add").mockResolvedValue({} as never);

      await service.queueEmbeddingJob(entryId, content);

      expect(queue.add).toHaveBeenCalledWith(
        "generate-embedding",
        expect.any(Object),
        expect.objectContaining({
          delay: 1000, // Default 1 second delay
        })
      );
    });
  });

  describe("getQueueStats", () => {
    it("should return queue statistics", async () => {
      vi.spyOn(queue, "getJobCounts").mockResolvedValue({
        waiting: 5,
        active: 2,
        completed: 10,
        failed: 1,
      } as never);

      const stats = await service.getQueueStats();

      expect(stats).toEqual({
        waiting: 5,
        active: 2,
        completed: 10,
        failed: 1,
      });
    });
  });

  describe("cleanCompletedJobs", () => {
    it("should clean completed jobs older than grace period", async () => {
      vi.spyOn(queue, "clean").mockResolvedValue([] as never);

      await service.cleanCompletedJobs(3600000); // 1 hour

      expect(queue.clean).toHaveBeenCalledWith(3600000, 100, "completed");
    });

    it("should use default grace period", async () => {
      vi.spyOn(queue, "clean").mockResolvedValue([] as never);

      await service.cleanCompletedJobs();

      expect(queue.clean).toHaveBeenCalledWith(86400000, 100, "completed"); // 24 hours default
    });
  });
});
