import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { EmbeddingProcessor } from "./embedding.processor";
import { OllamaEmbeddingService } from "../services/ollama-embedding.service";
import { Job } from "bullmq";
import { EmbeddingJobData } from "./embedding-queue.service";

describe("EmbeddingProcessor", () => {
  let processor: EmbeddingProcessor;
  let embeddingService: OllamaEmbeddingService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        EmbeddingProcessor,
        {
          provide: OllamaEmbeddingService,
          useValue: {
            generateAndStoreEmbedding: vi.fn(),
          },
        },
      ],
    }).compile();

    processor = module.get<EmbeddingProcessor>(EmbeddingProcessor);
    embeddingService = module.get<OllamaEmbeddingService>(OllamaEmbeddingService);
  });

  describe("processEmbedding", () => {
    it("should process embedding job successfully", async () => {
      const jobData: EmbeddingJobData = {
        entryId: "entry-123",
        content: "test content",
        model: "mxbai-embed-large",
      };

      const job = {
        id: "job-456",
        data: jobData,
      } as Job<EmbeddingJobData>;

      vi.spyOn(embeddingService, "generateAndStoreEmbedding").mockResolvedValue(undefined);

      await processor.processEmbedding(job);

      expect(embeddingService.generateAndStoreEmbedding).toHaveBeenCalledWith(
        "entry-123",
        "test content",
        { model: "mxbai-embed-large" }
      );
    });

    it("should process embedding job without model", async () => {
      const jobData: EmbeddingJobData = {
        entryId: "entry-123",
        content: "test content",
      };

      const job = {
        id: "job-456",
        data: jobData,
      } as Job<EmbeddingJobData>;

      vi.spyOn(embeddingService, "generateAndStoreEmbedding").mockResolvedValue(undefined);

      await processor.processEmbedding(job);

      expect(embeddingService.generateAndStoreEmbedding).toHaveBeenCalledWith(
        "entry-123",
        "test content",
        {}
      );
    });

    it("should throw error when embedding generation fails", async () => {
      const jobData: EmbeddingJobData = {
        entryId: "entry-123",
        content: "test content",
      };

      const job = {
        id: "job-456",
        data: jobData,
      } as Job<EmbeddingJobData>;

      vi.spyOn(embeddingService, "generateAndStoreEmbedding").mockRejectedValue(
        new Error("Ollama unavailable")
      );

      await expect(processor.processEmbedding(job)).rejects.toThrow("Ollama unavailable");
    });
  });

  describe("handleCompleted", () => {
    it("should log successful job completion", async () => {
      const job = {
        id: "job-456",
        data: {
          entryId: "entry-123",
        },
      } as Job<EmbeddingJobData>;

      const logSpy = vi.spyOn(processor["logger"], "log");

      await processor.handleCompleted(job);

      expect(logSpy).toHaveBeenCalledWith(
        expect.stringContaining("Successfully generated embedding for entry entry-123")
      );
    });
  });

  describe("handleFailed", () => {
    it("should log job failure with error", async () => {
      const job = {
        id: "job-456",
        data: {
          entryId: "entry-123",
        },
        attemptsMade: 3,
      } as Job<EmbeddingJobData>;

      const error = new Error("Ollama unavailable");
      const errorSpy = vi.spyOn(processor["logger"], "error");

      await processor.handleFailed(job, error);

      expect(errorSpy).toHaveBeenCalledWith(
        expect.stringContaining("Failed to generate embedding for entry entry-123"),
        error
      );
    });
  });
});
