/**
 * RL-series: Kong rate-limiting on the invitation endpoint (Fase 6.3)
 *
 * Kong ships with the rate-limiting plugin; we configure `local` policy
 * (single-instance). This test hits the Supabase Kong gateway on localhost
 * and asserts that the 21st POST in the window returns 429.
 *
 * Scope note: we deliberately do NOT rate-limit /auth/v1/* at the Kong layer.
 * Keycloak's native `bruteForceProtected` flag on the `colectivo` realm
 * (maxFailureWaitSeconds=900, failureFactor=30) covers password attacks at the
 * IdP level — the authoritative place. A Kong limit on /auth/v1/token would
 * also throttle legitimate PKCE code exchanges during tests.
 *
 * Threshold (in sync with infra/kong.yml):
 *   /rest/v1/collective_invitations POST — 20 per hour per authenticated user
 */
import { describe, it, expect } from 'vitest';
import { createClientAs } from '../src/supabase-clients.js';
import { ANA_ID, COLLECTIVE_ID } from '../src/seed-constants.js';

const KONG = process.env.PUBLIC_SUPABASE_URL ?? 'http://localhost:8001';
const ANON = process.env.PUBLIC_SUPABASE_ANON_KEY!;

// These tests hit Kong's real rate-limit counters (IP + token-based) and
// consume quota that's shared with the rest of the integration suite. To
// avoid cross-test pollution in `just test-all`, they're gated behind an
// explicit env flag and run via `just test-rate-limit` (which also
// restarts Kong first to ensure a clean counter state).
const RUN = process.env.RUN_RATE_LIMIT_TESTS === '1';
const d = RUN ? describe : describe.skip;

d('Kong rate-limit — /rest/v1/collective_invitations', () => {
	it('RL-02: 21st invitation POST in an hour returns 429', async () => {
		const ana = await createClientAs(ANA_ID);
		const authHeader = (ana as unknown as { rest: { headers: Record<string, string> } }).rest
			?.headers?.Authorization;
		// Fallback: grab the token manually
		const token = authHeader?.replace('Bearer ', '') ?? (await signBorja());

		const headers = {
			'Content-Type': 'application/json',
			apikey: ANON,
			Authorization: `Bearer ${token}`,
			Prefer: 'return=representation'
		};

		for (let i = 0; i < 20; i++) {
			const r = await fetch(`${KONG}/rest/v1/collective_invitations`, {
				method: 'POST',
				headers,
				body: JSON.stringify({
					collective_id: COLLECTIVE_ID,
					role: 'member',
					created_by: ANA_ID
				})
			});
			expect(r.status, `attempt ${i + 1} was 429`).not.toBe(429);
		}

		const limited = await fetch(`${KONG}/rest/v1/collective_invitations`, {
			method: 'POST',
			headers,
			body: JSON.stringify({
				collective_id: COLLECTIVE_ID,
				role: 'member',
				created_by: ANA_ID
			})
		});
		expect(limited.status).toBe(429);
	}, 60_000);
});

// Helper — sign a minimal authenticated JWT for Ana if we need to fall back.
async function signBorja(): Promise<string> {
	const { SignJWT } = await import('jose');
	const secret = new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET!);
	return new SignJWT({ role: 'authenticated' })
		.setProtectedHeader({ alg: 'HS256' })
		.setSubject(ANA_ID)
		.setAudience('authenticated')
		.setIssuedAt()
		.setExpirationTime('1h')
		.sign(secret);
}
