import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication, HttpStatus, Logger } from "@nestjs/common";
import request from "supertest";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { ThrottlerModule } from "@nestjs/throttler";
import { APP_GUARD } from "@nestjs/core";
import { ThrottlerApiKeyGuard } from "../common/throttler";

/**
 * Rate Limiting Tests for Auth Controller Catch-All Route
 *
 * These tests verify that rate limiting is properly enforced on the auth
 * catch-all route to prevent brute-force attacks (SEC-API-10).
 *
 * Test Coverage:
 * - Rate limit enforcement (429 status after 10 requests in 1 minute)
 * - Retry-After header inclusion
 * - Logging occurs for auth catch-all hits
 */
describe("AuthController - Rate Limiting", () => {
  let app: INestApplication;
  let loggerSpy: ReturnType<typeof vi.spyOn>;

  const mockNodeHandler = vi.fn(
    (_req: unknown, res: { statusCode: number; end: (body: string) => void }) => {
      res.statusCode = 200;
      res.end(JSON.stringify({}));
      return Promise.resolve();
    }
  );

  const mockAuthService = {
    getAuth: vi.fn(),
    getNodeHandler: vi.fn().mockReturnValue(mockNodeHandler),
  };

  beforeEach(async () => {
    // Spy on Logger.prototype.debug to verify logging
    loggerSpy = vi.spyOn(Logger.prototype, "debug").mockImplementation(() => {});

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ThrottlerModule.forRoot([
          {
            ttl: 60000, // 1 minute
            limit: 10, // Match the "strict" tier limit
          },
        ]),
      ],
      controllers: [AuthController],
      providers: [
        { provide: AuthService, useValue: mockAuthService },
        {
          provide: APP_GUARD,
          useClass: ThrottlerApiKeyGuard,
        },
      ],
    }).compile();

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

    vi.clearAllMocks();
  });

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

  describe("Auth Catch-All Route - Rate Limiting", () => {
    it("should allow requests within rate limit", async () => {
      // Make 3 requests (within limit of 10)
      for (let i = 0; i < 3; i++) {
        const response = await request(app.getHttpServer()).post("/auth/sign-in").send({
          email: "test@example.com",
          password: "password",
        });

        // Should not be rate limited
        expect(response.status).not.toBe(HttpStatus.TOO_MANY_REQUESTS);
      }

      expect(mockAuthService.getNodeHandler).toHaveBeenCalledTimes(3);
    });

    it("should return 429 when rate limit is exceeded", async () => {
      // Exhaust rate limit (10 requests)
      for (let i = 0; i < 10; i++) {
        await request(app.getHttpServer()).post("/auth/sign-in").send({
          email: "test@example.com",
          password: "password",
        });
      }

      // The 11th request should be rate limited
      const response = await request(app.getHttpServer()).post("/auth/sign-in").send({
        email: "test@example.com",
        password: "password",
      });

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

    it("should include Retry-After header in 429 response", async () => {
      // Exhaust rate limit (10 requests)
      for (let i = 0; i < 10; i++) {
        await request(app.getHttpServer()).post("/auth/sign-in").send({
          email: "test@example.com",
          password: "password",
        });
      }

      // Get rate limited response
      const response = await request(app.getHttpServer()).post("/auth/sign-in").send({
        email: "test@example.com",
        password: "password",
      });

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

    it("should rate limit different auth endpoints under the same limit", async () => {
      // Make 5 sign-in requests
      for (let i = 0; i < 5; i++) {
        await request(app.getHttpServer()).post("/auth/sign-in").send({
          email: "test@example.com",
          password: "password",
        });
      }

      // Make 5 sign-up requests (total now 10)
      for (let i = 0; i < 5; i++) {
        await request(app.getHttpServer()).post("/auth/sign-up").send({
          email: "test@example.com",
          password: "password",
          name: "Test User",
        });
      }

      // The 11th request (any auth endpoint) should be rate limited
      const response = await request(app.getHttpServer()).post("/auth/sign-in").send({
        email: "test@example.com",
        password: "password",
      });

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

  describe("Auth Catch-All Route - Logging", () => {
    it("should log auth catch-all hits with request details", async () => {
      await request(app.getHttpServer()).post("/auth/sign-in").send({
        email: "test@example.com",
        password: "password",
      });

      // Verify logging was called
      expect(loggerSpy).toHaveBeenCalled();

      // Find the log call that contains our expected message
      const logCalls = loggerSpy.mock.calls;
      const authLogCall = logCalls.find(
        (call) => typeof call[0] === "string" && call[0].includes("Auth catch-all:")
      );

      expect(authLogCall).toBeDefined();
      expect(authLogCall?.[0]).toMatch(/Auth catch-all: POST/);
    });

    it("should log different HTTP methods correctly", async () => {
      // Test GET request
      await request(app.getHttpServer()).get("/auth/callback");

      const logCalls = loggerSpy.mock.calls;
      const getLogCall = logCalls.find(
        (call) =>
          typeof call[0] === "string" &&
          call[0].includes("Auth catch-all:") &&
          call[0].includes("GET")
      );

      expect(getLogCall).toBeDefined();
    });
  });

  describe("Per-IP Rate Limiting", () => {
    it("should track rate limits per IP independently", async () => {
      // Note: In a real scenario, different IPs would have different limits
      // This test verifies the rate limit tracking behavior

      // Exhaust rate limit with requests
      for (let i = 0; i < 10; i++) {
        await request(app.getHttpServer()).post("/auth/sign-in").send({
          email: "test@example.com",
          password: "password",
        });
      }

      // Should be rate limited now
      const response = await request(app.getHttpServer()).post("/auth/sign-in").send({
        email: "test@example.com",
        password: "password",
      });

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