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 { StitcherController } from "./stitcher.controller";
import { StitcherService } from "./stitcher.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 Stitcher Endpoints
 *
 * These tests verify that rate limiting is properly enforced on webhook endpoints
 * to prevent DoS attacks.
 *
 * Test Coverage:
 * - Rate limit enforcement (429 status)
 * - Retry-After header inclusion
 * - Per-IP rate limiting
 * - Requests within limit are allowed
 */
describe("StitcherController - Rate Limiting", () => {
  let app: INestApplication;
  let service: StitcherService;

  const mockStitcherService = {
    dispatchJob: vi.fn().mockResolvedValue({
      jobId: "job-123",
      queueName: "mosaic-jobs",
      status: "PENDING",
    }),
    handleWebhook: vi.fn().mockResolvedValue({
      jobId: "job-456",
      queueName: "mosaic-jobs",
      status: "PENDING",
    }),
  };

  const mockConfigService = {
    get: vi.fn((key: string) => {
      const config: Record<string, string | number> = {
        STITCHER_API_KEY: "test-api-key-12345",
        RATE_LIMIT_TTL: "1", // 1 second for faster tests
        RATE_LIMIT_WEBHOOK_LIMIT: "5",
      };
      return config[key];
    }),
  };

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ThrottlerModule.forRoot([
          {
            ttl: 1000, // 1 second for testing
            limit: 5, // 5 requests per window
          },
        ]),
      ],
      controllers: [StitcherController],
      providers: [
        { provide: StitcherService, useValue: mockStitcherService },
        { provide: ConfigService, useValue: mockConfigService },
        {
          provide: APP_GUARD,
          useClass: ThrottlerApiKeyGuard,
        },
      ],
    })
      .overrideGuard(ApiKeyGuard)
      .useValue({ canActivate: () => true })
      .compile();

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

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

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

  describe("POST /stitcher/webhook - Rate Limiting", () => {
    it("should allow requests within rate limit", async () => {
      const payload = {
        issueNumber: "42",
        repository: "mosaic/stack",
        action: "assigned",
      };

      // Make 3 requests (within limit of 60 as configured in controller)
      for (let i = 0; i < 3; i++) {
        const response = await request(app.getHttpServer())
          .post("/stitcher/webhook")
          .set("X-API-Key", "test-api-key-12345")
          .send(payload);

        expect(response.status).toBe(HttpStatus.CREATED);
        expect(response.body).toHaveProperty("jobId");
      }

      expect(mockStitcherService.handleWebhook).toHaveBeenCalledTimes(3);
    });

    it("should return 429 when rate limit is exceeded", async () => {
      const payload = {
        issueNumber: "42",
        repository: "mosaic/stack",
        action: "assigned",
      };

      // Make requests up to the limit (60 as configured in controller)
      for (let i = 0; i < 60; i++) {
        await request(app.getHttpServer())
          .post("/stitcher/webhook")
          .set("X-API-Key", "test-api-key-12345")
          .send(payload);
      }

      // The 61st request should be rate limited
      const response = await request(app.getHttpServer())
        .post("/stitcher/webhook")
        .set("X-API-Key", "test-api-key-12345")
        .send(payload);

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

    it("should include Retry-After header in 429 response", async () => {
      const payload = {
        issueNumber: "42",
        repository: "mosaic/stack",
        action: "assigned",
      };

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

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

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

    it("should enforce rate limits per API key", async () => {
      const payload = {
        issueNumber: "42",
        repository: "mosaic/stack",
        action: "assigned",
      };

      // Exhaust rate limit from first API key
      for (let i = 0; i < 60; i++) {
        await request(app.getHttpServer())
          .post("/stitcher/webhook")
          .set("X-API-Key", "test-api-key-1")
          .send(payload);
      }

      // First API key should be rate limited
      const response1 = await request(app.getHttpServer())
        .post("/stitcher/webhook")
        .set("X-API-Key", "test-api-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("/stitcher/webhook")
        .set("X-API-Key", "test-api-key-2")
        .send(payload);

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

  describe("POST /stitcher/dispatch - Rate Limiting", () => {
    it("should allow requests within rate limit", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "code-task",
        context: { issueId: "42" },
      };

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

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

      expect(mockStitcherService.dispatchJob).toHaveBeenCalledTimes(3);
    });

    it("should return 429 when rate limit is exceeded", async () => {
      const payload = {
        workspaceId: "workspace-123",
        type: "code-task",
        context: { issueId: "42" },
      };

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

      // The 61st request should be rate limited
      const response = await request(app.getHttpServer())
        .post("/stitcher/dispatch")
        .set("X-API-Key", "test-api-key-12345")
        .send(payload);

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