import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ConflictException, NotFoundException } from "@nestjs/common";
import { ConversationArchiveService } from "./conversation-archive.service";
import { PrismaService } from "../prisma/prisma.service";
import { EmbeddingService } from "../knowledge/services/embedding.service";

const mockPrisma = {
  conversationArchive: {
    findUnique: vi.fn(),
    create: vi.fn(),
    count: vi.fn(),
    findMany: vi.fn(),
    findFirst: vi.fn(),
  },
  $queryRaw: vi.fn(),
  $executeRaw: vi.fn(),
};

const mockEmbedding = {
  isConfigured: vi.fn(),
  generateEmbedding: vi.fn(),
};

describe("ConversationArchiveService", () => {
  let service: ConversationArchiveService;

  beforeEach(async () => {
    vi.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ConversationArchiveService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: EmbeddingService, useValue: mockEmbedding },
      ],
    }).compile();

    service = module.get<ConversationArchiveService>(ConversationArchiveService);
  });

  describe("ingest", () => {
    const workspaceId = "ws-1";
    const dto = {
      sessionId: "sess-abc",
      agentId: "agent-xyz",
      messages: [
        { role: "user", content: "Hello" },
        { role: "assistant", content: "Hi there!" },
      ],
      summary: "A greeting conversation",
      startedAt: "2026-02-28T10:00:00Z",
    };

    it("creates a conversation archive and returns id", async () => {
      mockPrisma.conversationArchive.findUnique.mockResolvedValue(null);
      mockPrisma.conversationArchive.create.mockResolvedValue({ id: "conv-1" });
      mockEmbedding.isConfigured.mockReturnValue(false);

      const result = await service.ingest(workspaceId, dto);

      expect(result).toEqual({ id: "conv-1" });
      expect(mockPrisma.conversationArchive.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            workspaceId,
            sessionId: dto.sessionId,
            agentId: dto.agentId,
            messageCount: 2,
          }),
        })
      );
    });

    it("throws ConflictException when session already exists", async () => {
      mockPrisma.conversationArchive.findUnique.mockResolvedValue({ id: "existing" });

      await expect(service.ingest(workspaceId, dto)).rejects.toThrow(ConflictException);
    });
  });

  describe("findAll", () => {
    const workspaceId = "ws-1";

    it("returns paginated list", async () => {
      mockPrisma.conversationArchive.count.mockResolvedValue(5);
      mockPrisma.conversationArchive.findMany.mockResolvedValue([
        { id: "conv-1", sessionId: "sess-1" },
      ]);

      const result = await service.findAll(workspaceId, { page: 1, limit: 10 });

      expect(result.pagination.total).toBe(5);
      expect(result.data).toHaveLength(1);
    });

    it("uses default pagination when not provided", async () => {
      mockPrisma.conversationArchive.count.mockResolvedValue(0);
      mockPrisma.conversationArchive.findMany.mockResolvedValue([]);

      const result = await service.findAll(workspaceId, {});

      expect(result.pagination.page).toBe(1);
      expect(result.pagination.limit).toBe(20);
    });
  });

  describe("findOne", () => {
    const workspaceId = "ws-1";

    it("returns record when found", async () => {
      const record = { id: "conv-1", workspaceId, sessionId: "sess-1" };
      mockPrisma.conversationArchive.findFirst.mockResolvedValue(record);

      const result = await service.findOne(workspaceId, "conv-1");

      expect(result).toEqual(record);
    });

    it("throws NotFoundException when record does not exist", async () => {
      mockPrisma.conversationArchive.findFirst.mockResolvedValue(null);

      await expect(service.findOne(workspaceId, "missing")).rejects.toThrow(NotFoundException);
    });
  });

  describe("search", () => {
    it("throws ConflictException when embedding is not configured", async () => {
      mockEmbedding.isConfigured.mockReturnValue(false);

      await expect(service.search("ws-1", { query: "test query" })).rejects.toThrow(
        ConflictException
      );
    });

    it("performs vector search when configured", async () => {
      mockEmbedding.isConfigured.mockReturnValue(true);
      mockEmbedding.generateEmbedding.mockResolvedValue(new Array(1536).fill(0.1));
      mockPrisma.$queryRaw
        .mockResolvedValueOnce([{ id: "conv-1", similarity: 0.9 }])
        .mockResolvedValueOnce([{ count: BigInt(1) }]);

      const result = await service.search("ws-1", { query: "greetings" });

      expect(result.data).toHaveLength(1);
      expect(result.pagination.total).toBe(1);
    });
  });
});
