/**
 * IT-series: item_tags + shopping_item_tags RLS
 *
 * Schema invariants (publication, replica identity, cascades, unique/check
 * constraints) are covered by pgTAP in supabase/tests/014_item_tags.sql.
 * Here we verify the policies as observed by an authenticated role:
 *
 *   IT-01 member can create a tag in their collective
 *   IT-02 guest cannot create a tag (read-only role)
 *   IT-03 non-member cannot read tags from another collective
 *   IT-04 attaching a tag from a different collective to an item is rejected
 *   IT-05 unique(collective_id, name) — duplicate insert errors out
 *   IT-06 deleting an item cascades shopping_item_tags
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createClientAs, createAdminClient } from '../src/supabase-clients.js';
import {
	ANA_ID,
	BORJA_ID,
	DAVID_ID,
	EVA_ID,
	COLLECTIVE_ID,
	SEED_LIST_ID
} from '../src/seed-constants.js';

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

afterAll(async () => {
	if (createdTagIds.length) {
		await admin.from('item_tags').delete().in('id', createdTagIds);
	}
	if (createdItemIds.length) {
		await admin.from('shopping_items').delete().in('id', createdItemIds);
	}
	if (createdListIds.length) {
		await admin.from('shopping_lists').delete().in('id', createdListIds);
	}
	if (createdCollectiveIds.length) {
		await admin.from('collectives').delete().in('id', createdCollectiveIds);
	}
});

describe('item_tags RLS', () => {
	it('IT-01: member (Borja) can create a tag in their collective', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name: `IT-01-${Date.now()}`, color: 'green' })
			.select('id, name, color')
			.single();
		expect(error).toBeNull();
		expect(data?.color).toBe('green');
		if (data?.id) createdTagIds.push(data.id);
	});

	it('IT-02: guest (David) cannot create a tag', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name: `IT-02-${Date.now()}`, color: 'red' })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('IT-03: non-member (Eva) sees no tags in another collective', async () => {
		// Seed a tag as Ana so there's something to (not) see.
		const ana = await createClientAs(ANA_ID);
		const { data: tag } = await ana
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name: `IT-03-${Date.now()}`, color: 'sky' })
			.select('id')
			.single();
		if (tag?.id) createdTagIds.push(tag.id);

		const eva = await createClientAs(EVA_ID);
		const { data } = await eva.from('item_tags').select('id').eq('collective_id', COLLECTIVE_ID);
		expect(data ?? []).toHaveLength(0);
	});
});

describe('shopping_item_tags cross-collective protection', () => {
	let foreignCollectiveId: string;
	let foreignListId: string;
	let foreignTagId: string;
	let seedItemId: string;

	beforeAll(async () => {
		// Build a separate collective (no Borja membership) with one tag.
		const { data: coll } = await admin
			.from('collectives')
			.insert({ name: `IT foreign ${Date.now()}`, emoji: '🛡️', created_by: EVA_ID })
			.select('id')
			.single();
		foreignCollectiveId = coll!.id;
		createdCollectiveIds.push(foreignCollectiveId);

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

		const { data: list } = await admin
			.from('shopping_lists')
			.insert({ collective_id: foreignCollectiveId, name: 'foreign list', created_by: EVA_ID })
			.select('id')
			.single();
		foreignListId = list!.id;
		createdListIds.push(foreignListId);

		const { data: tag } = await admin
			.from('item_tags')
			.insert({ collective_id: foreignCollectiveId, name: 'foreign', color: 'pink' })
			.select('id')
			.single();
		foreignTagId = tag!.id;
		createdTagIds.push(foreignTagId);

		// And one item in the home collective that Borja could try to tag.
		const { data: item } = await admin
			.from('shopping_items')
			.insert({
				list_id: SEED_LIST_ID,
				name: `IT cross item ${Date.now()}`,
				sort_order: 9999,
				created_by: BORJA_ID
			})
			.select('id')
			.single();
		seedItemId = item!.id;
		createdItemIds.push(seedItemId);
	});

	it('IT-04: attaching a foreign-collective tag to a home item is rejected', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('shopping_item_tags')
			.insert({ item_id: seedItemId, tag_id: foreignTagId });
		expect(error).not.toBeNull();
	});
});

describe('item_tags constraints + cascade', () => {
	it('IT-05: duplicate (collective_id, name) errors out (unique constraint)', async () => {
		const ana = await createClientAs(ANA_ID);
		const name = `IT-05-dup-${Date.now()}`;
		const first = await ana
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name, color: 'amber' })
			.select('id')
			.single();
		expect(first.error).toBeNull();
		if (first.data?.id) createdTagIds.push(first.data.id);

		const second = await ana
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name, color: 'indigo' })
			.select('id')
			.single();
		expect(second.data).toBeNull();
		expect(second.error).not.toBeNull();
	});

	it('IT-06: deleting an item cascades shopping_item_tags', async () => {
		const ana = await createClientAs(ANA_ID);
		const { data: tag } = await ana
			.from('item_tags')
			.insert({ collective_id: COLLECTIVE_ID, name: `IT-06-${Date.now()}`, color: 'stone' })
			.select('id')
			.single();
		if (tag?.id) createdTagIds.push(tag.id);

		const { data: item } = await ana
			.from('shopping_items')
			.insert({
				list_id: SEED_LIST_ID,
				name: `IT-06 item ${Date.now()}`,
				sort_order: 8888,
				created_by: ANA_ID
			})
			.select('id')
			.single();
		expect(item?.id).toBeTruthy();

		await ana.from('shopping_item_tags').insert({ item_id: item!.id, tag_id: tag!.id });

		// Delete the item; the join row should be gone.
		await ana.from('shopping_items').delete().eq('id', item!.id);

		const { data: links } = await admin
			.from('shopping_item_tags')
			.select('item_id')
			.eq('tag_id', tag!.id);
		expect(links ?? []).toHaveLength(0);
	});
});
