/**
 * N-series: notes RLS — CRUD by role, archive, trash window, cross-collective isolation.
 */
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 } from '../src/seed-constants.js';

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

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

describe('Note CRUD by role', () => {
	let noteId: string;

	it('N-01a: member (Borja) can create a note', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('notes')
			.insert({
				collective_id: COLLECTIVE_ID,
				title: 'Borja note',
				content: 'hello',
				created_by: BORJA_ID,
				updated_by: BORJA_ID
			})
			.select('id')
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBeTruthy();
		noteId = data!.id;
		createdNoteIds.push(noteId);
	});

	it('N-01b: guest (David) cannot create a note', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('notes')
			.insert({
				collective_id: COLLECTIVE_ID,
				content: 'David note',
				created_by: DAVID_ID,
				updated_by: DAVID_ID
			})
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('N-01c: guest (David) can read notes', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david.from('notes').select('id').eq('id', noteId);
		expect(error).toBeNull();
		expect(data).toHaveLength(1);
	});

	it('N-01d: any active member can edit any note (Carmen edits Borja\'s)', async () => {
		const carmen = await createClientAs('33333333-3333-3333-3333-333333333333');
		const { error } = await carmen
			.from('notes')
			.update({ content: 'edited by Carmen', updated_by: '33333333-3333-3333-3333-333333333333' })
			.eq('id', noteId);
		expect(error).toBeNull();

		const { data } = await admin.from('notes').select('content, updated_by').eq('id', noteId).single();
		expect(data?.content).toBe('edited by Carmen');
		expect(data?.updated_by).toBe('33333333-3333-3333-3333-333333333333');
	});

	it('N-01e: cannot spoof created_by/updated_by on insert', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('notes')
			.insert({
				collective_id: COLLECTIVE_ID,
				content: 'spoof',
				created_by: ANA_ID,
				updated_by: ANA_ID
			})
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});
});

describe('Note pin / archive / trash flow', () => {
	let noteId: string;

	beforeAll(async () => {
		const { data } = await admin
			.from('notes')
			.insert({
				collective_id: COLLECTIVE_ID,
				content: 'flow note',
				created_by: ANA_ID,
				updated_by: ANA_ID
			})
			.select('id')
			.single();
		noteId = data!.id;
		createdNoteIds.push(noteId);
	});

	it('N-02a: member can pin', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('notes')
			.update({ is_pinned: true, updated_by: BORJA_ID })
			.eq('id', noteId);
		expect(error).toBeNull();
	});

	it('N-02b: pinned + archived combination is rejected (CHECK)', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('notes')
			.update({ is_archived: true, updated_by: BORJA_ID })
			.eq('id', noteId);
		expect(error).not.toBeNull();
	});

	it('N-02c: archive (after unpin) succeeds', async () => {
		const borja = await createClientAs(BORJA_ID);
		await borja
			.from('notes')
			.update({ is_pinned: false, is_archived: true, updated_by: BORJA_ID })
			.eq('id', noteId);
		const { data } = await admin.from('notes').select('is_archived').eq('id', noteId).single();
		expect(data?.is_archived).toBe(true);
	});

	it('N-02d: archived note is still readable', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data } = await david.from('notes').select('id, is_archived').eq('id', noteId).single();
		expect(data?.id).toBe(noteId);
		expect(data?.is_archived).toBe(true);
	});
});

describe('Note trash window', () => {
	let noteId: string;

	beforeAll(async () => {
		const { data } = await admin
			.from('notes')
			.insert({
				collective_id: COLLECTIVE_ID,
				content: 'trash note',
				created_by: ANA_ID,
				updated_by: ANA_ID
			})
			.select('id')
			.single();
		noteId = data!.id;
		createdNoteIds.push(noteId);
	});

	it('N-03a: soft-deleted note is visible within the 7-day window', async () => {
		const borja = await createClientAs(BORJA_ID);
		await borja
			.from('notes')
			.update({ deleted_at: new Date().toISOString(), updated_by: BORJA_ID })
			.eq('id', noteId);

		const { data } = await borja.from('notes').select('id, deleted_at').eq('id', noteId).single();
		expect(data?.id).toBe(noteId);
		expect(data?.deleted_at).not.toBeNull();
	});

	it('N-03b: soft-deleted note older than 7 days is invisible', async () => {
		await sql(`UPDATE public.notes SET deleted_at = now() - INTERVAL '8 days' WHERE id = $1`, [noteId]);
		const borja = await createClientAs(BORJA_ID);
		const { data } = await borja.from('notes').select('id').eq('id', noteId);
		expect(data).toHaveLength(0);
		await sql(`UPDATE public.notes SET deleted_at = now() WHERE id = $1`, [noteId]);
	});

	it('N-03c: guest cannot hard-delete a soft-deleted note', async () => {
		const david = await createClientAs(DAVID_ID);
		await david.from('notes').delete().eq('id', noteId);
		const { data } = await admin.from('notes').select('id').eq('id', noteId).single();
		expect(data?.id).toBe(noteId);
	});

	it('N-03d: member can hard-delete a soft-deleted note', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja.from('notes').delete().eq('id', noteId);
		expect(error).toBeNull();
		const { data } = await admin.from('notes').select('id').eq('id', noteId);
		expect(data).toHaveLength(0);
		const idx = createdNoteIds.indexOf(noteId);
		if (idx !== -1) createdNoteIds.splice(idx, 1);
	});
});

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

	beforeAll(async () => {
		const { data: collective } = await admin
			.from('collectives')
			.insert({ name: 'Notes isolation 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: note } = await admin
			.from('notes')
			.insert({
				collective_id: otherCollectiveId,
				content: 'Eva private',
				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);
		}
	});

	it('N-04a: Borja cannot read Eva\'s note', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data } = await borja.from('notes').select('id').eq('id', otherNoteId);
		expect(data).toHaveLength(0);
	});

	it('N-04b: Borja cannot edit Eva\'s note', async () => {
		const borja = await createClientAs(BORJA_ID);
		await borja
			.from('notes')
			.update({ content: 'hacked', updated_by: BORJA_ID })
			.eq('id', otherNoteId);
		const { data } = await admin.from('notes').select('content').eq('id', otherNoteId).single();
		expect(data?.content).toBe('Eva private');
	});
});
