import { describe, it, expect, beforeAll, afterAll, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ConfigService } from "@nestjs/config";
import { JobEventsService } from "./job-events.service";
import { PrismaService } from "../prisma/prisma.service";
import { VaultService } from "../vault/vault.service";
import { CryptoService } from "../federation/crypto.service";
import { JOB_CREATED, JOB_STARTED, STEP_STARTED } from "./event-types";

/**
 * Performance tests for JobEventsService
 *
 * These tests verify that the composite index [jobId, timestamp] improves
 * query performance for the most common access patterns.
 *
 * NOTE: These tests require a real database connection with realistic data volume.
 * Run with: pnpm test:api -- job-events.performance.spec.ts
 */
const shouldRunDbIntegrationTests =
  process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;

describeFn("JobEventsService Performance", () => {
  let service: JobEventsService;
  let prisma: PrismaService;
  let testJobId: string;
  let testWorkspaceId: string;

  beforeAll(async () => {
    // Mock ConfigService for VaultService/CryptoService
    const mockConfigService = {
      get: vi.fn((key: string) => {
        const config: Record<string, string> = {
          ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
          OPENBAO_ADDR: "http://localhost:8200",
          OPENBAO_ROLE_ID: "test-role-id",
          OPENBAO_SECRET_ID: "test-secret-id",
        };
        return config[key] || null;
      }),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        JobEventsService,
        PrismaService,
        {
          provide: ConfigService,
          useValue: mockConfigService,
        },
        VaultService,
        CryptoService,
      ],
    }).compile();

    service = module.get<JobEventsService>(JobEventsService);
    prisma = module.get<PrismaService>(PrismaService);

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

    // Create test job with many events
    const job = await prisma.runnerJob.create({
      data: {
        workspaceId: testWorkspaceId,
        type: "code-task",
        status: "RUNNING",
        priority: 5,
        progressPercent: 0,
      },
    });
    testJobId = job.id;

    // Create 1000 events to simulate realistic load
    const events = [];
    for (let i = 0; i < 1000; i++) {
      events.push({
        jobId: testJobId,
        type: i % 3 === 0 ? JOB_STARTED : i % 3 === 1 ? STEP_STARTED : JOB_CREATED,
        timestamp: new Date(Date.now() - (1000 - i) * 1000), // Events over ~16 minutes
        actor: "system",
        payload: { iteration: i },
      });
    }

    // Batch insert for performance
    await prisma.jobEvent.createMany({
      data: events,
    });
  });

  afterAll(async () => {
    // Clean up test data
    await prisma.jobEvent.deleteMany({
      where: { jobId: testJobId },
    });
    await prisma.runnerJob.delete({
      where: { id: testJobId },
    });
    await prisma.workspace.delete({
      where: { id: testWorkspaceId },
    });

    await prisma.$disconnect();
  });

  describe("Query Performance", () => {
    it("should efficiently query events by jobId with timestamp ordering", async () => {
      const startTime = performance.now();

      const result = await service.getEventsByJobId(testJobId, {
        page: 1,
        limit: 50,
      });

      const endTime = performance.now();
      const queryTime = endTime - startTime;

      expect(result.data).toHaveLength(50);
      expect(result.meta.total).toBe(1000);
      expect(queryTime).toBeLessThan(100); // Should complete in under 100ms

      // Verify events are ordered by timestamp ascending
      for (let i = 1; i < result.data.length; i++) {
        expect(result.data[i].timestamp.getTime()).toBeGreaterThanOrEqual(
          result.data[i - 1].timestamp.getTime()
        );
      }
    });

    it("should efficiently query events by jobId and type with timestamp ordering", async () => {
      const startTime = performance.now();

      const result = await service.getEventsByJobId(testJobId, {
        type: JOB_STARTED,
        page: 1,
        limit: 50,
      });

      const endTime = performance.now();
      const queryTime = endTime - startTime;

      expect(result.data.length).toBeGreaterThan(0);
      expect(result.data.every((e) => e.type === JOB_STARTED)).toBe(true);
      expect(queryTime).toBeLessThan(100); // Should complete in under 100ms
    });

    it("should efficiently query events with timestamp range (streaming pattern)", async () => {
      // Get a timestamp from the middle of our test data
      const midpointTime = new Date(Date.now() - 500 * 1000);

      const startTime = performance.now();

      const events = await prisma.jobEvent.findMany({
        where: {
          jobId: testJobId,
          timestamp: { gt: midpointTime },
        },
        orderBy: { timestamp: "asc" },
        take: 100,
      });

      const endTime = performance.now();
      const queryTime = endTime - startTime;

      expect(events.length).toBeGreaterThan(0);
      expect(events.length).toBeLessThanOrEqual(100);
      expect(queryTime).toBeLessThan(50); // Range queries should be very fast with index

      // Verify all events are after the midpoint
      events.forEach((event) => {
        expect(event.timestamp.getTime()).toBeGreaterThan(midpointTime.getTime());
      });
    });

    it("should use the composite index in query plan", async () => {
      // Execute EXPLAIN ANALYZE to verify index usage
      const explainResult = await prisma.$queryRaw<Array<{ "QUERY PLAN": string }>>`
        EXPLAIN (FORMAT JSON)
        SELECT * FROM job_events
        WHERE job_id = ${testJobId}::uuid
        ORDER BY timestamp ASC
        LIMIT 50
      `;

      const queryPlan = JSON.stringify(explainResult);

      // Verify that an index scan is used (not a sequential scan)
      expect(queryPlan.toLowerCase()).toContain("index");
      expect(queryPlan.toLowerCase()).not.toContain("seq scan on job_events");

      // The composite index should be named something like:
      // job_events_job_id_timestamp_idx or similar
      expect(queryPlan.includes("job_events_job_id") || queryPlan.includes("index")).toBe(true);
    });
  });

  describe("Pagination Performance", () => {
    it("should efficiently paginate through all events", async () => {
      const startTime = performance.now();

      // Fetch page 10 (events 450-499)
      const result = await service.getEventsByJobId(testJobId, {
        page: 10,
        limit: 50,
      });

      const endTime = performance.now();
      const queryTime = endTime - startTime;

      expect(result.data).toHaveLength(50);
      expect(result.meta.page).toBe(10);
      expect(queryTime).toBeLessThan(150); // Should complete in under 150ms even with OFFSET
    });
  });

  describe("Concurrent Query Performance", () => {
    it("should handle multiple concurrent queries efficiently", async () => {
      const startTime = performance.now();

      // Simulate 10 concurrent clients querying the same job
      const queries = Array.from({ length: 10 }, (_, i) =>
        service.getEventsByJobId(testJobId, {
          page: i + 1,
          limit: 50,
        })
      );

      const results = await Promise.all(queries);

      const endTime = performance.now();
      const totalTime = endTime - startTime;

      expect(results).toHaveLength(10);
      results.forEach((result, i) => {
        expect(result.data).toHaveLength(50);
        expect(result.meta.page).toBe(i + 1);
      });

      // All 10 queries should complete in under 500ms total
      expect(totalTime).toBeLessThan(500);
    });
  });
});
