import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { PrismaClient } from "@prisma/client";

/**
 * Check if fulltext search trigger is properly configured in the database.
 * Returns true if the trigger function exists (meaning the migration was applied).
 */
async function isFulltextSearchConfigured(prisma: PrismaClient): Promise<boolean> {
  try {
    const result = await prisma.$queryRaw<{ exists: boolean }[]>`
      SELECT EXISTS (
        SELECT 1 FROM pg_proc
        WHERE proname = 'knowledge_entries_search_vector_update'
      ) as exists
    `;
    return result[0]?.exists ?? false;
  } catch {
    return false;
  }
}

/**
 * Integration tests for PostgreSQL full-text search setup
 * Tests the tsvector column, GIN index, and automatic trigger
 *
 * NOTE: These tests require a real database connection.
 * Skip when DATABASE_URL is not set. Tests that require the trigger/index
 * will be skipped if the database migration hasn't been applied.
 */
const shouldRunDbIntegrationTests =
  process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;

describeFn("Full-Text Search Setup (Integration)", () => {
  let prisma: PrismaClient;
  let testWorkspaceId: string;
  let testUserId: string;
  let fulltextConfigured = false;

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

    // Check if fulltext search is properly configured (trigger exists)
    fulltextConfigured = await isFulltextSearchConfigured(prisma);
    if (!fulltextConfigured) {
      console.warn(
        "Skipping fulltext-search trigger/index tests: " +
          "PostgreSQL trigger function not found. " +
          "Run the full migration to enable these tests."
      );
    }

    // Create test workspace
    const workspace = await prisma.workspace.create({
      data: {
        name: "Test Workspace",
        owner: {
          create: {
            email: `test-fts-${Date.now()}@example.com`,
            name: "Test User",
          },
        },
      },
    });
    testWorkspaceId = workspace.id;
    testUserId = workspace.ownerId;
  });

  afterAll(async () => {
    // Cleanup
    if (testWorkspaceId) {
      await prisma.knowledgeEntry.deleteMany({
        where: { workspaceId: testWorkspaceId },
      });
      await prisma.workspace.delete({
        where: { id: testWorkspaceId },
      });
    }
    await prisma.$disconnect();
  });

  describe("tsvector column", () => {
    it("should have search_vector column in knowledge_entries table", async () => {
      // Query to check if column exists (always runs - validates schema)
      const result = await prisma.$queryRaw<{ column_name: string; data_type: string }[]>`
        SELECT column_name, data_type
        FROM information_schema.columns
        WHERE table_name = 'knowledge_entries'
          AND column_name = 'search_vector'
      `;

      expect(result).toHaveLength(1);
      expect(result[0].column_name).toBe("search_vector");
      expect(result[0].data_type).toBe("tsvector");
    });

    it("should automatically populate search_vector on insert", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: trigger not configured");
        return;
      }

      const entry = await prisma.knowledgeEntry.create({
        data: {
          workspaceId: testWorkspaceId,
          slug: "auto-populate-test",
          title: "PostgreSQL Full-Text Search",
          content: "This is a test of the automatic trigger functionality.",
          summary: "Testing automatic population",
          createdBy: testUserId,
          updatedBy: testUserId,
        },
      });

      // Query raw to check search_vector was populated
      const result = await prisma.$queryRaw<{ id: string; search_vector: string | null }[]>`
        SELECT id, search_vector::text
        FROM knowledge_entries
        WHERE id = ${entry.id}::uuid
      `;

      expect(result).toHaveLength(1);
      expect(result[0].search_vector).not.toBeNull();
      // Verify 'postgresql' appears in title (weight A)
      expect(result[0].search_vector).toContain("'postgresql':1A");
      // Verify 'search' appears in both title (A) and content (C)
      expect(result[0].search_vector).toContain("'search':5A");
    });

    it("should automatically update search_vector on update", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: trigger not configured");
        return;
      }

      const entry = await prisma.knowledgeEntry.create({
        data: {
          workspaceId: testWorkspaceId,
          slug: "auto-update-test",
          title: "Original Title",
          content: "Original content",
          createdBy: testUserId,
          updatedBy: testUserId,
        },
      });

      // Update the entry
      await prisma.knowledgeEntry.update({
        where: { id: entry.id },
        data: {
          title: "Updated Elasticsearch Title",
          content: "Updated content with Elasticsearch",
        },
      });

      // Check search_vector was updated
      const result = await prisma.$queryRaw<{ id: string; search_vector: string | null }[]>`
        SELECT id, search_vector::text
        FROM knowledge_entries
        WHERE id = ${entry.id}::uuid
      `;

      expect(result).toHaveLength(1);
      // Verify 'elasticsearch' appears in both title (A) and content (C)
      // PostgreSQL combines positions: '2A,7C' means position 2 in title (A) and position 7 in content (C)
      expect(result[0].search_vector).toContain("'elasticsearch':2A,7C");
      expect(result[0].search_vector).not.toContain("'original'");
    });

    it("should include summary in search_vector with weight B", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: trigger not configured");
        return;
      }

      const entry = await prisma.knowledgeEntry.create({
        data: {
          workspaceId: testWorkspaceId,
          slug: "summary-weight-test",
          title: "Title Word",
          content: "Content word",
          summary: "Summary keyword here",
          createdBy: testUserId,
          updatedBy: testUserId,
        },
      });

      const result = await prisma.$queryRaw<{ id: string; search_vector: string | null }[]>`
        SELECT id, search_vector::text
        FROM knowledge_entries
        WHERE id = ${entry.id}::uuid
      `;

      expect(result).toHaveLength(1);
      // Summary should have weight B - 'keyword' appears in summary
      expect(result[0].search_vector).toContain("'keyword':4B");
    });

    it("should handle null summary gracefully", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: trigger not configured");
        return;
      }

      const entry = await prisma.knowledgeEntry.create({
        data: {
          workspaceId: testWorkspaceId,
          slug: "null-summary-test",
          title: "Title without summary",
          content: "Content without summary",
          summary: null,
          createdBy: testUserId,
          updatedBy: testUserId,
        },
      });

      const result = await prisma.$queryRaw<{ id: string; search_vector: string | null }[]>`
        SELECT id, search_vector::text
        FROM knowledge_entries
        WHERE id = ${entry.id}::uuid
      `;

      expect(result).toHaveLength(1);
      expect(result[0].search_vector).not.toBeNull();
      // Verify 'titl' (stemmed from 'title') appears with weight A
      expect(result[0].search_vector).toContain("'titl':1A");
      // Verify 'content' appears with weight C
      expect(result[0].search_vector).toContain("'content':4C");
    });
  });

  describe("GIN index", () => {
    it("should have GIN index on search_vector column", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: GIN index not configured");
        return;
      }

      const result = await prisma.$queryRaw<{ indexname: string; indexdef: string }[]>`
        SELECT indexname, indexdef
        FROM pg_indexes
        WHERE tablename = 'knowledge_entries'
          AND indexname = 'knowledge_entries_search_vector_idx'
      `;

      expect(result).toHaveLength(1);
      expect(result[0].indexdef).toContain("gin");
      expect(result[0].indexdef).toContain("search_vector");
    });
  });

  describe("search performance", () => {
    it("should perform fast searches using the GIN index", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: fulltext search not configured");
        return;
      }

      // Create multiple entries
      const entries = Array.from({ length: 10 }, (_, i) => ({
        workspaceId: testWorkspaceId,
        slug: `perf-test-${i}`,
        title: `Performance Test ${i}`,
        content: i % 2 === 0 ? "Contains database keyword" : "No keyword here",
        createdBy: testUserId,
        updatedBy: testUserId,
      }));

      await prisma.knowledgeEntry.createMany({
        data: entries,
      });

      const startTime = Date.now();

      // Search using the precomputed search_vector
      const results = await prisma.$queryRaw<{ id: string; title: string }[]>`
        SELECT id, title
        FROM knowledge_entries
        WHERE workspace_id = ${testWorkspaceId}::uuid
          AND search_vector @@ plainto_tsquery('english', 'database')
        ORDER BY ts_rank(search_vector, plainto_tsquery('english', 'database')) DESC
      `;

      const duration = Date.now() - startTime;

      expect(results.length).toBeGreaterThan(0);
      // Should be fast with index (< 100ms for small dataset)
      expect(duration).toBeLessThan(100);
    });

    it("should rank results by relevance using weighted fields", async () => {
      if (!fulltextConfigured) {
        console.log("Skipping: fulltext search not configured");
        return;
      }

      // Create entries with keyword in different positions
      await prisma.knowledgeEntry.createMany({
        data: [
          {
            workspaceId: testWorkspaceId,
            slug: "rank-title",
            title: "Redis caching strategies",
            content: "Various approaches to caching",
            summary: "Overview of strategies",
            createdBy: testUserId,
            updatedBy: testUserId,
          },
          {
            workspaceId: testWorkspaceId,
            slug: "rank-summary",
            title: "Database optimization",
            content: "Performance tuning",
            summary: "Redis is mentioned in summary",
            createdBy: testUserId,
            updatedBy: testUserId,
          },
          {
            workspaceId: testWorkspaceId,
            slug: "rank-content",
            title: "Performance guide",
            content: "Use Redis for better performance",
            summary: "Best practices",
            createdBy: testUserId,
            updatedBy: testUserId,
          },
        ],
      });

      const results = await prisma.$queryRaw<{ slug: string; rank: number }[]>`
        SELECT slug, ts_rank(search_vector, plainto_tsquery('english', 'redis')) AS rank
        FROM knowledge_entries
        WHERE workspace_id = ${testWorkspaceId}::uuid
          AND search_vector @@ plainto_tsquery('english', 'redis')
        ORDER BY rank DESC
      `;

      expect(results.length).toBe(3);
      // Title match should rank highest (weight A)
      expect(results[0].slug).toBe("rank-title");
      // Summary should rank second (weight B)
      expect(results[1].slug).toBe("rank-summary");
      // Content should rank third (weight C)
      expect(results[2].slug).toBe("rank-content");
    });
  });
});
