/**
 * A-series: exhaustive RLS isolation audit — Fase 4.4
 *
 * Proves zero cross-collective leakage on every domain table across the full
 * matrix:
 *   table × role (non-member, guest, member, admin) × op (SELECT/INSERT/UPDATE/DELETE)
 *
 * The finer-grained role behaviour (e.g. guest can INSERT items? answer: no) is
 * covered by the B/C/D/T/N suites. This test focuses on cross-collective:
 * a user of collective X must never see, edit, or delete rows of collective Y.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createClientAs, createAdminClient } from '../src/supabase-clients.js';
import { closePool } from '../src/db-helpers.js';
import { ANA_ID, BORJA_ID, DAVID_ID, EVA_ID } from '../src/seed-constants.js';

const admin = createAdminClient();

// Fixtures: Eva's private collective with one row per domain table.
let otherCollectiveId: string;
let otherListId: string; // shopping list
let otherItemId: string; // shopping item
let otherTaskListId: string;
let otherTaskId: string;
let otherNoteId: string;

beforeAll(async () => {
	const { data: collective } = await admin
		.from('collectives')
		.insert({ name: 'RLS audit collective', emoji: '🛡️', created_by: EVA_ID })
		.select('id')
		.single();
	otherCollectiveId = collective!.id;

	await admin
		.from('collective_members')
		.insert({ collective_id: otherCollectiveId, user_id: EVA_ID, role: 'admin' });

	const { data: list } = await admin
		.from('shopping_lists')
		.insert({ collective_id: otherCollectiveId, name: 'audit list', created_by: EVA_ID })
		.select('id')
		.single();
	otherListId = list!.id;

	const { data: item } = await admin
		.from('shopping_items')
		.insert({ list_id: otherListId, name: 'audit item', sort_order: 1, created_by: EVA_ID })
		.select('id')
		.single();
	otherItemId = item!.id;

	const { data: tl } = await admin
		.from('task_lists')
		.insert({ collective_id: otherCollectiveId, name: 'audit tasks', created_by: EVA_ID })
		.select('id')
		.single();
	otherTaskListId = tl!.id;

	const { data: task } = await admin
		.from('tasks')
		.insert({ list_id: otherTaskListId, title: 'audit task', created_by: EVA_ID })
		.select('id')
		.single();
	otherTaskId = task!.id;

	const { data: note } = await admin
		.from('notes')
		.insert({
			collective_id: otherCollectiveId,
			content: 'audit note',
			created_by: EVA_ID,
			updated_by: EVA_ID
		})
		.select('id')
		.single();
	otherNoteId = note!.id;
});

afterAll(async () => {
	if (otherCollectiveId) {
		await admin.from('collectives').delete().eq('id', otherCollectiveId);
	}
	await closePool();
});

// Every intruder below is NOT a member of Eva's collective. They must each
// see zero rows and fail to mutate every row.
const INTRUDERS: Array<{ label: string; id: string }> = [
	{ label: 'Ana (admin of different collective)', id: ANA_ID },
	{ label: 'Borja (member of different collective)', id: BORJA_ID },
	{ label: 'David (guest of different collective)', id: DAVID_ID }
];

describe.each(INTRUDERS)('RLS audit — $label', ({ id }) => {
	it('A-01: cannot SELECT cross-collective collective row', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('collectives').select('id').eq('id', otherCollectiveId);
		expect(data).toHaveLength(0);
	});

	it('A-02: cannot SELECT cross-collective shopping_list', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('shopping_lists').select('id').eq('id', otherListId);
		expect(data).toHaveLength(0);
	});

	it('A-03: cannot SELECT cross-collective shopping_item', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('shopping_items').select('id').eq('id', otherItemId);
		expect(data).toHaveLength(0);
	});

	it('A-04: cannot SELECT cross-collective task_list', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('task_lists').select('id').eq('id', otherTaskListId);
		expect(data).toHaveLength(0);
	});

	it('A-05: cannot SELECT cross-collective task', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('tasks').select('id').eq('id', otherTaskId);
		expect(data).toHaveLength(0);
	});

	it('A-06: cannot SELECT cross-collective note', async () => {
		const c = await createClientAs(id);
		const { data } = await c.from('notes').select('id').eq('id', otherNoteId);
		expect(data).toHaveLength(0);
	});

	it('A-07: INSERT into cross-collective shopping_list is blocked', async () => {
		const c = await createClientAs(id);
		const { data, error } = await c
			.from('shopping_lists')
			.insert({ collective_id: otherCollectiveId, name: 'sneaky', created_by: id })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('A-08: INSERT into cross-collective task_list is blocked', async () => {
		const c = await createClientAs(id);
		const { data, error } = await c
			.from('task_lists')
			.insert({ collective_id: otherCollectiveId, name: 'sneaky', created_by: id })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('A-09: INSERT into cross-collective note is blocked', async () => {
		const c = await createClientAs(id);
		const { data, error } = await c
			.from('notes')
			.insert({
				collective_id: otherCollectiveId,
				content: 'sneaky',
				created_by: id,
				updated_by: id
			})
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('A-10: UPDATE on cross-collective note is a no-op', async () => {
		const c = await createClientAs(id);
		await c.from('notes').update({ content: 'hacked', updated_by: id }).eq('id', otherNoteId);
		const { data } = await admin.from('notes').select('content').eq('id', otherNoteId).single();
		expect(data?.content).toBe('audit note');
	});

	it('A-11: UPDATE on cross-collective task is a no-op', async () => {
		const c = await createClientAs(id);
		await c
			.from('tasks')
			.update({ is_completed: true, completed_by: id, completed_at: new Date().toISOString() })
			.eq('id', otherTaskId);
		const { data } = await admin.from('tasks').select('is_completed').eq('id', otherTaskId).single();
		expect(data?.is_completed).toBe(false);
	});

	it('A-12: DELETE on cross-collective shopping_item is a no-op', async () => {
		const c = await createClientAs(id);
		await c.from('shopping_items').delete().eq('id', otherItemId);
		const { data } = await admin.from('shopping_items').select('id').eq('id', otherItemId).single();
		expect(data?.id).toBe(otherItemId);
	});

	it('A-13: DELETE on cross-collective task is a no-op', async () => {
		const c = await createClientAs(id);
		await c.from('tasks').delete().eq('id', otherTaskId);
		const { data } = await admin.from('tasks').select('id').eq('id', otherTaskId).single();
		expect(data?.id).toBe(otherTaskId);
	});

	it('A-14: DELETE on cross-collective shopping_list is a no-op', async () => {
		const c = await createClientAs(id);
		await c.from('shopping_lists').delete().eq('id', otherListId);
		const { data } = await admin.from('shopping_lists').select('id').eq('id', otherListId).single();
		expect(data?.id).toBe(otherListId);
	});
});
