/**
 * T-series: task_lists and tasks RLS — CRUD by role, completion invariants,
 * cross-collective isolation.
 */
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 } from '../src/seed-constants.js';
import { closePool } from '../src/db-helpers.js';

const admin = createAdminClient();
const createdTaskListIds: string[] = [];
let seedTaskListId: string;

beforeAll(async () => {
	// Seed a task list owned by Ana for the rest of the suite to read against.
	const { data, error } = await admin
		.from('task_lists')
		.insert({ collective_id: COLLECTIVE_ID, name: 'T-series seed list', created_by: ANA_ID })
		.select('id')
		.single();
	if (error) throw error;
	seedTaskListId = data!.id;
	createdTaskListIds.push(seedTaskListId);
});

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

describe('Task list access by role', () => {
	it('T-01a: member (Borja) can create a task list', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('task_lists')
			.insert({ collective_id: COLLECTIVE_ID, name: 'Borja list', created_by: BORJA_ID })
			.select('id')
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBeTruthy();
		if (data?.id) createdTaskListIds.push(data.id);
	});

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

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

describe('Task CRUD by role', () => {
	let taskId: string;

	it('T-02a: member (Borja) can add a task', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('tasks')
			.insert({ list_id: seedTaskListId, title: 'Borja task', created_by: BORJA_ID })
			.select('id')
			.single();
		expect(error).toBeNull();
		expect(data?.id).toBeTruthy();
		taskId = data!.id;
	});

	it('T-02b: guest (David) cannot add a task', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('tasks')
			.insert({ list_id: seedTaskListId, title: 'David task', created_by: DAVID_ID })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});

	it('T-02c: guest (David) can read tasks', async () => {
		const david = await createClientAs(DAVID_ID);
		const { data, error } = await david
			.from('tasks')
			.select('id')
			.eq('list_id', seedTaskListId);
		expect(error).toBeNull();
		expect(data!.length).toBeGreaterThanOrEqual(1);
	});

	it('T-02d: cannot spoof created_by', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data, error } = await borja
			.from('tasks')
			.insert({ list_id: seedTaskListId, title: 'Spoof', created_by: ANA_ID })
			.select('id')
			.single();
		expect(data).toBeNull();
		expect(error).not.toBeNull();
	});
});

describe('Task completion invariants', () => {
	let taskId: string;

	beforeAll(async () => {
		const { data } = await admin
			.from('tasks')
			.insert({ list_id: seedTaskListId, title: 'Complete me', created_by: ANA_ID })
			.select('id')
			.single();
		taskId = data!.id;
	});

	it('T-03a: marking complete sets completed_by + completed_at', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('tasks')
			.update({
				is_completed: true,
				completed_by: BORJA_ID,
				completed_at: new Date().toISOString()
			})
			.eq('id', taskId);
		expect(error).toBeNull();

		const { data } = await admin.from('tasks').select('*').eq('id', taskId).single();
		expect(data?.is_completed).toBe(true);
		expect(data?.completed_by).toBe(BORJA_ID);
		expect(data?.completed_at).not.toBeNull();
	});

	it('T-03b: unmarking clears completed_by + completed_at', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('tasks')
			.update({ is_completed: false, completed_by: null, completed_at: null })
			.eq('id', taskId);
		expect(error).toBeNull();

		const { data } = await admin.from('tasks').select('*').eq('id', taskId).single();
		expect(data?.is_completed).toBe(false);
		expect(data?.completed_by).toBeNull();
		expect(data?.completed_at).toBeNull();
	});

	it('T-03c: CHECK constraint forbids is_completed=true without completed_by', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { error } = await borja
			.from('tasks')
			.update({ is_completed: true, completed_by: null, completed_at: null })
			.eq('id', taskId);
		expect(error).not.toBeNull(); // CHECK violation surfaces as a Postgres error
	});

	it('T-03d: guest (David) cannot mark complete', async () => {
		const david = await createClientAs(DAVID_ID);
		await david
			.from('tasks')
			.update({ is_completed: true, completed_by: DAVID_ID, completed_at: new Date().toISOString() })
			.eq('id', taskId);
		const { data } = await admin.from('tasks').select('is_completed').eq('id', taskId).single();
		expect(data?.is_completed).toBe(false);
	});
});

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

	beforeAll(async () => {
		const { data: collective } = await admin
			.from('collectives')
			.insert({ name: 'Tasks 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: list } = await admin
			.from('task_lists')
			.insert({ collective_id: otherCollectiveId, name: 'Eva tasks', created_by: EVA_ID })
			.select('id')
			.single();
		otherListId = list!.id;
		const { data: task } = await admin
			.from('tasks')
			.insert({ list_id: otherListId, title: 'Eva private task', created_by: EVA_ID })
			.select('id')
			.single();
		otherTaskId = task!.id;
	});

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

	it('T-04a: Eva (non-member of main collective) sees no tasks from main', async () => {
		const eva = await createClientAs(EVA_ID);
		const { data } = await eva.from('tasks').select('id').eq('list_id', seedTaskListId);
		expect(data).toHaveLength(0);
	});

	it('T-04b: Borja cannot read Eva\'s task', async () => {
		const borja = await createClientAs(BORJA_ID);
		const { data } = await borja.from('tasks').select('id').eq('id', otherTaskId);
		expect(data).toHaveLength(0);
	});

	it('T-04c: Borja cannot delete Eva\'s task list', async () => {
		const borja = await createClientAs(BORJA_ID);
		await borja.from('task_lists').delete().eq('id', otherListId);
		const { data } = await admin.from('task_lists').select('id').eq('id', otherListId).single();
		expect(data?.id).toBe(otherListId);
	});
});
