/**
 * R-series (Isolation): RLS must be enforced on Realtime postgres_changes too.
 *
 * Eva is not a member of the seed collective. Even if she subscribes to the
 * shopping_items channel for SEED_LIST_ID, the Realtime server evaluates RLS
 * using her JWT when deciding which events to forward, and should drop every
 * event whose row is not visible to her.
 *
 * We also verify the positive case: a guest of the same collective (David) DOES
 * receive events, because the existing shopping_items SELECT policy allows
 * guests to read items in their collectives.
 */
import { describe, it, expect, afterAll, afterEach } from 'vitest';
import { createClientAs, createAdminClient } from '../src/supabase-clients.js';
import { subscribePostgresChanges } from '../src/realtime-helpers.js';
import { ANA_ID, DAVID_ID, EVA_ID, SEED_LIST_ID } from '../src/seed-constants.js';
import type { SupabaseClient } from '@supabase/supabase-js';

const admin = createAdminClient();
const createdItemIds: string[] = [];

const activeClients: SupabaseClient[] = [];
function track(c: SupabaseClient): SupabaseClient {
	activeClients.push(c);
	return c;
}
afterEach(async () => {
	for (const c of activeClients.splice(0)) {
		await c.removeAllChannels();
		await c.realtime.disconnect();
	}
});

afterAll(async () => {
	if (createdItemIds.length > 0) {
		await admin.from('shopping_items').delete().in('id', createdItemIds);
	}
});

describe('Realtime RLS isolation — shopping_items', () => {
	it('R-I-01: David (guest) receives events for items in his collective', async () => {
		const david = track(await createClientAs(DAVID_ID));
		const ana = track(await createClientAs(ANA_ID));

		const sub = await subscribePostgresChanges<{ id: string; name: string }>(david, {
			table: 'shopping_items',
			event: 'INSERT',
			filter: `list_id=eq.${SEED_LIST_ID}`
		});

		try {
			const itemName = `R-I-01-${Date.now()}`;
			const { data } = await ana
				.from('shopping_items')
				.insert({ list_id: SEED_LIST_ID, name: itemName, sort_order: 800, created_by: ANA_ID })
				.select('id')
				.single();
			createdItemIds.push(data!.id);

			const evt = await sub.waitFor((e) => e.new.name === itemName);
			expect(evt.new.name).toBe(itemName);
		} finally {
			await sub.unsubscribe();
			await ana.removeAllChannels();
			await david.removeAllChannels();
		}
	});

	it('R-I-02: Eva (non-member) receives NO events for items in the seed list', async () => {
		const eva = track(await createClientAs(EVA_ID));
		const ana = track(await createClientAs(ANA_ID));

		const sub = await subscribePostgresChanges<{ id: string; name: string }>(eva, {
			table: 'shopping_items',
			event: 'INSERT',
			filter: `list_id=eq.${SEED_LIST_ID}`
		});

		try {
			// Ana inserts something that should NOT reach Eva
			const forbiddenName = `R-I-02-forbidden-${Date.now()}`;
			const { data } = await ana
				.from('shopping_items')
				.insert({
					list_id: SEED_LIST_ID,
					name: forbiddenName,
					sort_order: 801,
					created_by: ANA_ID
				})
				.select('id')
				.single();
			createdItemIds.push(data!.id);

			// Give Realtime up to 2s to (not) deliver. If RLS works, Eva sees nothing.
			await new Promise((r) => setTimeout(r, 2_000));

			const evt = sub.events.find((e) => e.new.name === forbiddenName);
			expect(evt).toBeUndefined();
			expect(sub.events.length).toBe(0);
		} finally {
			await sub.unsubscribe();
			await ana.removeAllChannels();
			await eva.removeAllChannels();
		}
	});
});
