/**
 * C-series: Shopping list RLS — create, read, soft-delete, restore, hard-delete.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createClientAs, createAdminClient } from '../src/supabase-clients.js';
import { sql, closePool } from '../src/db-helpers.js';
import { ANA_ID, BORJA_ID, DAVID_ID, EVA_ID, COLLECTIVE_ID, SEED_LIST_ID } from '../src/seed-constants.js';

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

afterAll(async () => {
	// Hard-delete any lists created during tests (bypasses RLS)
	if (createdListIds.length > 0) {
		await admin.from('shopping_lists').delete().in('id', createdListIds);
	}
	await closePool();
});

describe('Shopping list read access', () => {
	it('C-01: member (Borja) can read the seed list', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('shopping_lists')
			.select('id, name')
			.eq('id', SEED_LIST_ID)
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBe(SEED_LIST_ID);
	});

	it('C-02: guest (David) can read the seed list', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('shopping_lists')
			.select('id')
			.eq('id', SEED_LIST_ID)
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBe(SEED_LIST_ID);
	});

	it('C-03: Eva (non-member) sees no lists', async () => {
		const eva = await createClientAs(EVA_ID);
		const { data } = await eva.from('shopping_lists').select('id');
		expect(data).toHaveLength(0);
	});
});

describe('Shopping list create', () => {
	it('C-04: member (Borja) can create a list', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('shopping_lists')
			.insert({ collective_id: COLLECTIVE_ID, name: 'Borja test list', created_by: BORJA_ID })
			.select('id')
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBeTruthy();
		if (data?.id) createdListIds.push(data.id);
	});

	it('C-05: guest (David) cannot create a list', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('shopping_lists')
			.insert({ collective_id: COLLECTIVE_ID, name: 'David sneaky list', created_by: DAVID_ID })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('C-06: cannot spoof created_by to another user', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('shopping_lists')
			.insert({ collective_id: COLLECTIVE_ID, name: 'Spoofed list', created_by: ANA_ID })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});
});

describe('Shopping list soft-delete and trash window', () => {
	let testListId: string;

	beforeAll(async () => {
		// Create a list via admin to avoid depending on prior test
		const { data } = await admin
			.from('shopping_lists')
			.insert({ collective_id: COLLECTIVE_ID, name: 'Trash test list', created_by: ANA_ID })
			.select('id')
			.single();
		testListId = data!.id;
		createdListIds.push(testListId);
	});

	it('C-07: member (Borja) can soft-delete (set deleted_at) on a list', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('shopping_lists')
			.update({ deleted_at: new Date().toISOString() })
			.eq('id', testListId);
		expect(error).toBeNull();
		const { data } = await admin.from('shopping_lists').select('deleted_at').eq('id', testListId).single();
		expect(data?.deleted_at).not.toBeNull();
	});

	it('C-08: soft-deleted list is still visible within the 7-day window', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('shopping_lists')
			.select('id, deleted_at')
			.eq('id', testListId)
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBe(testListId);
		expect(data?.deleted_at).not.toBeNull();
	});

	it('C-09: soft-deleted list older than 7 days is not visible', async () => {
		// Force deleted_at to 8 days ago via direct SQL
		await sql(
			`UPDATE public.shopping_lists SET deleted_at = now() - INTERVAL '8 days' WHERE id = $1`,
			[testListId]
		);
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('shopping_lists')
			.select('id')
			.eq('id', testListId);
		expect(error).toBeNull();
		expect(data).toHaveLength(0);
		// Restore deleted_at so afterAll cleanup can hard-delete it
		await sql(`UPDATE public.shopping_lists SET deleted_at = now() WHERE id = $1`, [testListId]);
	});

	it('C-10: member can restore a list (set deleted_at = null)', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('shopping_lists')
			.update({ deleted_at: null })
			.eq('id', testListId);
		expect(error).toBeNull();
		const { data } = await admin.from('shopping_lists').select('deleted_at').eq('id', testListId).single();
		expect(data?.deleted_at).toBeNull();
	});

	it('C-11: guest (David) cannot permanently delete a list', async () => {
		// Soft-delete first via admin
		await admin
			.from('shopping_lists')
			.update({ deleted_at: new Date().toISOString() })
			.eq('id', testListId);

		const david = await createClientAs(DAVID_ID);
		const { error } = await david.from('shopping_lists').delete().eq('id', testListId);
		// PostgREST returns 204 with 0 rows when RLS blocks — verify row still exists
		const { data } = await admin.from('shopping_lists').select('id').eq('id', testListId).single();
		expect(data?.id).toBe(testListId);
	});

	it('C-12: member (Borja) can permanently hard-delete a soft-deleted list', async () => {
		// Ensure it's soft-deleted
		await admin
			.from('shopping_lists')
			.update({ deleted_at: new Date().toISOString() })
			.eq('id', testListId);

		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('shopping_lists')
			.delete()
			.eq('id', testListId);
		expect(error).toBeNull();

		const { data } = await admin.from('shopping_lists').select('id').eq('id', testListId);
		expect(data).toHaveLength(0);

		// Remove from createdListIds since it's already gone
		const idx = createdListIds.indexOf(testListId);
		if (idx !== -1) createdListIds.splice(idx, 1);
	});
});

describe('Cross-collective isolation', () => {
	let otherCollectiveId: string;
	let otherListId: string;

	beforeAll(async () => {
		// Eva creates her own collective and list (Eva is allowed to create collectives)
		const { data: collective } = await admin
			.from('collectives')
			.insert({ name: 'Eva 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: 'Eva private list', created_by: EVA_ID })
			.select('id')
			.single();
		otherListId = list!.id;
	});

	afterAll(async () => {
		// Cascade delete removes members + lists
		if (otherCollectiveId) {
			await admin.from('collectives').delete().eq('id', otherCollectiveId);
		}
	});

	it('C-13: Borja (member of main collective) cannot see Eva\'s list', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data } = await borja
			.from('shopping_lists')
			.select('id')
			.eq('id', otherListId);
		expect(data).toHaveLength(0);
	});

	it('C-14: Ana (admin of main collective) cannot see Eva\'s list', async () => {
		const ana = await createClientAs(ANA_ID);
		const { data } = await ana
			.from('shopping_lists')
			.select('id')
			.eq('id', otherListId);
		expect(data).toHaveLength(0);
	});
});
