import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication, HttpStatus } from "@nestjs/common";
import request from "supertest";
import { CoordinatorIntegrationController } from "./coordinator-integration.controller";
import { CoordinatorIntegrationService } from "./coordinator-integration.service";
import { ThrottlerModule } from "@nestjs/throttler";
import { APP_GUARD } from "@nestjs/core";
import { ConfigService } from "@nestjs/config";
import { ApiKeyGuard } from "../common/guards";
import { ThrottlerApiKeyGuard } from "../common/throttler";

/**
 * Rate Limiting Tests for Coordinator Integration Endpoints
 *
 * These tests verify that rate limiting is properly enforced on coordinator
 * endpoints to prevent DoS attacks.
 *
 * Test Coverage:
 * - Rate limit enforcement (429 status)
 * - Retry-After header inclusion
 * - Per-API-key rate limiting
 * - Higher limits for health endpoints
 */
describe("CoordinatorIntegrationController - Rate Limiting", () => {
  let app: INestApplication;
  let service: CoordinatorIntegrationService;

  const mockCoordinatorService = {
    createJob: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      status: "PENDING",
    }),
    updateJobStatus: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      status: "RUNNING",
    }),
    updateJobProgress: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      progress: 50,
    }),
    completeJob: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      status: "COMPLETED",
    }),
    failJob: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      status: "FAILED",
    }),
    getJobDetails: vi.fn().mockResolvedValue({
      jobId: "coord-job-123",
      status: "RUNNING",
    }),
    getIntegrationHealth: vi.fn().mockResolvedValue({
      status: "healthy",
      timestamp: new Date().toISOString(),
    }),
  };

  const mockConfigService = {
    get: vi.fn((key: string) => {
      const config: Record<string, string | number> = {
        COORDINATOR_API_KEY: "test-coordinator-key",
        RATE_LIMIT_TTL: "1", // 1 second for faster tests
        RATE_LIMIT_COORDINATOR_LIMIT: "100",
        RATE_LIMIT_HEALTH_LIMIT: "300",
      };
      return config[key];
    }),
  };

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ThrottlerModule.forRoot([
          {
            ttl: 1000, // 1 second for testing
            limit: 100, // Default limit
          },
        ]),
      ],
      controllers: [CoordinatorIntegrationController],
      providers: [
        { provide: CoordinatorIntegrationService, useValue: mockCoordinatorService },
        { provide: ConfigService, useValue: mockConfigService },
        {
          provide: APP_GUARD,
          useClass: ThrottlerApiKeyGuard,
        },
      ],
    })
      .overrideGuard(ApiKeyGuard)
      .useValue({ canActivate: () => true })
      .compile();

    app = moduleFixture.createNestApplication();
    await app.init();

    service = moduleFixture.get<CoordinatorIntegrationService>(CoordinatorIntegrationService);
    vi.clearAllMocks();
  });

  afterEach(async () => {
    await app.close();
  });

  describe("POST /coordinator/jobs - Rate Limiting", () => {
    it("should allow requests within rate limit", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "data-processing",
        data: { input: "test" },
      };

      // Make 3 requests (within limit of 100)
      for (let i = 0; i < 3; i++) {
        const response = await request(app.getHttpServer())
          .post("/coordinator/jobs")
          .set("X-API-Key", "test-coordinator-key")
          .send(payload);

        expect(response.status).toBe(HttpStatus.CREATED);
      }

      expect(mockCoordinatorService.createJob).toHaveBeenCalledTimes(3);
    });

    it("should return 429 when rate limit is exceeded", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "data-processing",
        data: { input: "test" },
      };

      // Exhaust rate limit (100 requests)
      for (let i = 0; i < 100; i++) {
        await request(app.getHttpServer())
          .post("/coordinator/jobs")
          .set("X-API-Key", "test-coordinator-key")
          .send(payload);
      }

      // The 101st request should be rate limited
      const response = await request(app.getHttpServer())
        .post("/coordinator/jobs")
        .set("X-API-Key", "test-coordinator-key")
        .send(payload);

      expect(response.status).toBe(HttpStatus.TOO_MANY_REQUESTS);
    });

    it("should include Retry-After header in 429 response", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "data-processing",
        data: { input: "test" },
      };

      // Exhaust rate limit (100 requests)
      for (let i = 0; i < 100; i++) {
        await request(app.getHttpServer())
          .post("/coordinator/jobs")
          .set("X-API-Key", "test-coordinator-key")
          .send(payload);
      }

      // Get rate limited response
      const response = await request(app.getHttpServer())
        .post("/coordinator/jobs")
        .set("X-API-Key", "test-coordinator-key")
        .send(payload);

      expect(response.status).toBe(HttpStatus.TOO_MANY_REQUESTS);
      expect(response.headers).toHaveProperty("retry-after");
      expect(parseInt(response.headers["retry-after"])).toBeGreaterThan(0);
    });
  });

  describe("PATCH /coordinator/jobs/:id/status - Rate Limiting", () => {
    it("should allow requests within rate limit", async () => {
      const jobId = "coord-job-123";
      const payload = { status: "RUNNING" };

      // Make 3 requests (within limit of 100)
      for (let i = 0; i < 3; i++) {
        const response = await request(app.getHttpServer())
          .patch(`/coordinator/jobs/${jobId}/status`)
          .set("X-API-Key", "test-coordinator-key")
          .send(payload);

        expect(response.status).toBe(HttpStatus.OK);
      }

      expect(mockCoordinatorService.updateJobStatus).toHaveBeenCalledTimes(3);
    });

    it("should return 429 when rate limit is exceeded", async () => {
      const jobId = "coord-job-123";
      const payload = { status: "RUNNING" };

      // Exhaust rate limit (100 requests)
      for (let i = 0; i < 100; i++) {
        await request(app.getHttpServer())
          .patch(`/coordinator/jobs/${jobId}/status`)
          .set("X-API-Key", "test-coordinator-key")
          .send(payload);
      }

      // The 101st request should be rate limited
      const response = await request(app.getHttpServer())
        .patch(`/coordinator/jobs/${jobId}/status`)
        .set("X-API-Key", "test-coordinator-key")
        .send(payload);

      expect(response.status).toBe(HttpStatus.TOO_MANY_REQUESTS);
    });
  });

  describe("GET /coordinator/health - Rate Limiting", () => {
    it("should have higher rate limit than other endpoints", async () => {
      // Health endpoint should allow 300 requests (higher than default 100)
      // Test with a smaller sample to keep test fast
      for (let i = 0; i < 10; i++) {
        const response = await request(app.getHttpServer())
          .get("/coordinator/health")
          .set("X-API-Key", "test-coordinator-key");

        expect(response.status).toBe(HttpStatus.OK);
      }

      expect(mockCoordinatorService.getIntegrationHealth).toHaveBeenCalledTimes(10);
    });

    it("should return 429 when health endpoint limit is exceeded", async () => {
      // Exhaust health endpoint limit (300 requests)
      for (let i = 0; i < 300; i++) {
        await request(app.getHttpServer())
          .get("/coordinator/health")
          .set("X-API-Key", "test-coordinator-key");
      }

      // The 301st request should be rate limited
      const response = await request(app.getHttpServer())
        .get("/coordinator/health")
        .set("X-API-Key", "test-coordinator-key");

      expect(response.status).toBe(HttpStatus.TOO_MANY_REQUESTS);
    }, 30000);
  });

  describe("Per-API-Key Rate Limiting", () => {
    it("should enforce rate limits per API key independently", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "data-processing",
        data: { input: "test" },
      };

      // Exhaust rate limit for first API key (100 requests)
      for (let i = 0; i < 100; i++) {
        await request(app.getHttpServer())
          .post("/coordinator/jobs")
          .set("X-API-Key", "test-coordinator-key-1")
          .send(payload);
      }

      // First API key should be rate limited
      const response1 = await request(app.getHttpServer())
        .post("/coordinator/jobs")
        .set("X-API-Key", "test-coordinator-key-1")
        .send(payload);

      expect(response1.status).toBe(HttpStatus.TOO_MANY_REQUESTS);

      // Second API key should still be allowed
      const response2 = await request(app.getHttpServer())
        .post("/coordinator/jobs")
        .set("X-API-Key", "test-coordinator-key-2")
        .send(payload);

      expect(response2.status).toBe(HttpStatus.CREATED);
    });
  });
});
