import { beforeAll, beforeEach, describe, expect, it, afterAll, vi } from "vitest";
import { randomUUID as uuid } from "crypto";
import { Test, TestingModule } from "@nestjs/testing";
import { ConflictException } from "@nestjs/common";
import { PrismaClient, Prisma } from "@prisma/client";
import { EMBEDDING_DIMENSION } from "@mosaic/shared";
import { ConversationArchiveService } from "./conversation-archive.service";
import { PrismaService } from "../prisma/prisma.service";
import { EmbeddingService } from "../knowledge/services/embedding.service";

const shouldRunDbIntegrationTests =
  process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;

function vector(value: number): number[] {
  return Array.from({ length: EMBEDDING_DIMENSION }, () => value);
}

function toVectorLiteral(input: number[]): string {
  return `[${input.join(",")}]`;
}

describeFn("ConversationArchiveService Integration", () => {
  let moduleRef: TestingModule;
  let prisma: PrismaClient;
  let service: ConversationArchiveService;
  let workspaceId: string;
  let ownerId: string;
  let setupComplete = false;

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

  beforeAll(async () => {
    prisma = new PrismaClient();
    await prisma.$connect();

    const workspace = await prisma.workspace.create({
      data: {
        name: `Conversation Archive Integration ${Date.now()}`,
        owner: {
          create: {
            email: `conversation-archive-integration-${Date.now()}@example.com`,
            name: "Conversation Archive Integration Owner",
          },
        },
      },
    });

    workspaceId = workspace.id;
    ownerId = workspace.ownerId;

    moduleRef = await Test.createTestingModule({
      providers: [
        ConversationArchiveService,
        {
          provide: PrismaService,
          useValue: prisma,
        },
        {
          provide: EmbeddingService,
          useValue: embeddingServiceMock,
        },
      ],
    }).compile();

    service = moduleRef.get<ConversationArchiveService>(ConversationArchiveService);
    setupComplete = true;
  });

  beforeEach(async () => {
    vi.clearAllMocks();
    embeddingServiceMock.isConfigured.mockReturnValue(false);

    if (!setupComplete) {
      return;
    }

    await prisma.conversationArchive.deleteMany({ where: { workspaceId } });
  });

  afterAll(async () => {
    if (!prisma) {
      return;
    }

    if (workspaceId) {
      await prisma.conversationArchive.deleteMany({ where: { workspaceId } });
      await prisma.workspace.deleteMany({ where: { id: workspaceId } });
    }
    if (ownerId) {
      await prisma.user.deleteMany({ where: { id: ownerId } });
    }

    if (moduleRef) {
      await moduleRef.close();
    }
    await prisma.$disconnect();
  });

  it("ingests a conversation log", async () => {
    if (!setupComplete) {
      return;
    }

    const sessionId = `session-${uuid()}`;

    const result = await service.ingest(workspaceId, {
      sessionId,
      agentId: "agent-conversation-ingest",
      messages: [
        { role: "user", content: "Can you summarize deployment issues?" },
        { role: "assistant", content: "Yes, three retries timed out in staging." },
      ],
      summary: "Deployment retry failures discussed",
      startedAt: "2026-02-28T21:00:00.000Z",
      endedAt: "2026-02-28T21:05:00.000Z",
      metadata: { source: "integration-test" },
    });

    expect(result.id).toBeDefined();

    const stored = await prisma.conversationArchive.findUnique({
      where: {
        id: result.id,
      },
    });

    expect(stored).toBeTruthy();
    expect(stored?.workspaceId).toBe(workspaceId);
    expect(stored?.sessionId).toBe(sessionId);
    expect(stored?.messageCount).toBe(2);
    expect(stored?.summary).toBe("Deployment retry failures discussed");
  });

  it("rejects duplicate session ingest per workspace", async () => {
    if (!setupComplete) {
      return;
    }

    const sessionId = `session-${uuid()}`;
    const dto = {
      sessionId,
      agentId: "agent-conversation-duplicate",
      messages: [{ role: "user", content: "hello" }],
      summary: "simple conversation",
      startedAt: "2026-02-28T22:00:00.000Z",
    };

    await service.ingest(workspaceId, dto);

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

  it("rejects semantic search when embeddings are disabled", async () => {
    if (!setupComplete) {
      return;
    }

    embeddingServiceMock.isConfigured.mockReturnValue(false);

    await expect(
      service.search(workspaceId, {
        query: "deployment retries",
      })
    ).rejects.toThrow(ConflictException);
  });

  it("searches archived conversations by vector similarity", async () => {
    if (!setupComplete) {
      return;
    }

    const near = vector(0.02);
    const far = vector(0.85);

    const matching = await prisma.conversationArchive.create({
      data: {
        workspaceId,
        sessionId: `session-search-${uuid()}`,
        agentId: "agent-conversation-search-a",
        messages: [
          { role: "user", content: "What caused deployment retries?" },
          { role: "assistant", content: "A connection pool timeout." },
        ] as unknown as Prisma.InputJsonValue,
        messageCount: 2,
        summary: "Deployment retries caused by connection pool timeout",
        startedAt: new Date("2026-02-28T23:00:00.000Z"),
        metadata: { channel: "cli" } as Prisma.InputJsonValue,
      },
    });

    const nonMatching = await prisma.conversationArchive.create({
      data: {
        workspaceId,
        sessionId: `session-search-${uuid()}`,
        agentId: "agent-conversation-search-b",
        messages: [
          { role: "user", content: "How is billing configured?" },
        ] as unknown as Prisma.InputJsonValue,
        messageCount: 1,
        summary: "Billing and quotas conversation",
        startedAt: new Date("2026-02-28T23:10:00.000Z"),
        metadata: { channel: "cli" } as Prisma.InputJsonValue,
      },
    });

    await prisma.$executeRaw`
      UPDATE conversation_archives
      SET embedding = ${toVectorLiteral(near)}::vector(${EMBEDDING_DIMENSION})
      WHERE id = ${matching.id}::uuid
    `;
    await prisma.$executeRaw`
      UPDATE conversation_archives
      SET embedding = ${toVectorLiteral(far)}::vector(${EMBEDDING_DIMENSION})
      WHERE id = ${nonMatching.id}::uuid
    `;

    embeddingServiceMock.isConfigured.mockReturnValue(true);
    embeddingServiceMock.generateEmbedding.mockResolvedValue(near);

    const result = await service.search(workspaceId, {
      query: "deployment retries timeout",
      agentId: "agent-conversation-search-a",
      similarityThreshold: 0,
      limit: 10,
    });

    const rows = result.data as Array<{ id: string; agent_id: string; similarity: number }>;

    expect(result.pagination.total).toBe(1);
    expect(rows).toHaveLength(1);
    expect(rows[0]?.id).toBe(matching.id);
    expect(rows[0]?.agent_id).toBe("agent-conversation-search-a");
    expect(rows[0]?.similarity).toBeGreaterThan(0);
  });
});
