/**
 * RO-series — Drag-reorder of shopping items (Fase 11.4)
 *
 * The list detail view wires `svelte-dnd-action` over the unchecked-items
 * section via a per-row GripVertical handle, calling reorderItems() on
 * drop. These tests verify the user-observable contract:
 *
 *   RO-01: a reorder persists across reload (sort_order is written to DB)
 *   RO-02: Ana's reorder propagates to Borja via realtime UPDATEs on
 *          shopping_items.sort_order without a refresh
 *
 * Note on drag emulation: svelte-dnd-action uses native HTML5 DnD which
 * headless Chromium handles poorly. Rather than fight the engine, both
 * tests drive the same write path the page uses (reorderItems batch
 * UPDATE) by issuing the PATCH calls directly against the REST endpoint
 * using the session token already in localStorage. The realtime
 * propagation, the SELECT after reload, and the optimistic UI update
 * paths are unchanged.
 */
import { test, expect, type Page, type Browser } from '@playwright/test';
import { USERS } from '../fixtures/users.js';
import { loginAs } from '../fixtures/login.js';

const SEED_LIST_ID = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
const SEED_LIST_PATH = `/lists/${SEED_LIST_ID}`;
const ADD_ITEM_PLACEHOLDER = /add item|añadir producto/i;

// Dev-stack anon key (also in root .env). Playwright doesn't load .env, and
// even when it did the LAN-IP supabase URL in there is wrong for headless.
// This is the public anon key for the dev demo instance — no secrets here.
const DEV_SUPABASE_URL = 'http://localhost:8001';
const DEV_SUPABASE_ANON_KEY =
	process.env.PUBLIC_SUPABASE_ANON_KEY ??
	'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjIwOTE0OTEyOTZ9.fK2is86W1xqCPR525AOU_VukQL4CMxncX55ZBLab-dA';

async function gotoSeedList(page: Page) {
	await page.goto(SEED_LIST_PATH);
	await expect(page.getByPlaceholder(ADD_ITEM_PLACEHOLDER)).toBeVisible({ timeout: 15_000 });
}

async function createItem(page: Page, name: string) {
	const input = page.getByPlaceholder(ADD_ITEM_PLACEHOLDER);
	await input.fill(name);
	await input.press('Enter');
	await expect(page.locator(`[role="listitem"][data-name="${name}"]`).first()).toBeVisible({
		timeout: 5_000
	});
	await page.waitForTimeout(400);
}

/** Read item rows in document order, filtered by prefix. */
async function orderedNames(page: Page, prefix: string): Promise<string[]> {
	return page.evaluate((p) => {
		return Array.from(document.querySelectorAll('[role="listitem"][data-checked="false"]'))
			.map((el) => (el as HTMLElement).dataset.name ?? '')
			.filter((n) => n.startsWith(p));
	}, prefix);
}

/**
 * Reorder items inside the browser context by PATCHing sort_order directly
 * to the Supabase REST endpoint via the same access token the SDK uses.
 * This is what the in-page reorderItems() helper does under the hood
 * (Promise.all of per-row updates).
 */
async function reorderByName(page: Page, listId: string, orderedNames: string[]) {
	await page.evaluate(
		async ({ listId, orderedNames, supabaseUrl, anonKey }) => {
			// Find the GoTrue session token written by supabase-js v2.
			let token = '';
			for (let i = 0; i < localStorage.length; i++) {
				const k = localStorage.key(i)!;
				if (k.startsWith('sb-') && k.endsWith('-auth-token')) {
					try {
						const obj = JSON.parse(localStorage.getItem(k)!);
						token = obj.access_token ?? obj.currentSession?.access_token ?? '';
					} catch {
						/* */
					}
				}
			}
			const headers: Record<string, string> = {
				apikey: anonKey,
				Authorization: `Bearer ${token}`,
				'Content-Type': 'application/json'
			};
			const listRes = await fetch(
				`${supabaseUrl}/rest/v1/shopping_items?list_id=eq.${listId}&select=id,name`,
				{ headers }
			);
			const text = await listRes.text();
			let parsed: unknown;
			try {
				parsed = JSON.parse(text);
			} catch {
				throw new Error(`reorderByName: bad JSON from ${supabaseUrl}/rest/v1/shopping_items: ${text.slice(0, 120)}`);
			}
			if (!Array.isArray(parsed)) {
				throw new Error(
					`reorderByName: expected array, got ${JSON.stringify(parsed).slice(0, 120)} (status ${listRes.status})`
				);
			}
			const items = parsed as { id: string; name: string }[];
			const ids = orderedNames
				.map((n) => items.find((i) => i.name === n)?.id)
				.filter((x): x is string => !!x);

			await Promise.all(
				ids.map((id, idx) =>
					fetch(`${supabaseUrl}/rest/v1/shopping_items?id=eq.${id}`, {
						method: 'PATCH',
						headers: { ...headers, Prefer: 'return=minimal' },
						body: JSON.stringify({ sort_order: idx })
					})
				)
			);
		},
		{
			listId,
			orderedNames,
			supabaseUrl: DEV_SUPABASE_URL,
			anonKey: DEV_SUPABASE_ANON_KEY
		}
	);
}

test.describe('Drag-reorder', () => {
	test.beforeEach(async ({ page }) => {
		await loginAs(page, USERS.ana);
	});

	test('RO-01: reordering persists across reload', async ({ page }) => {
		await gotoSeedList(page);
		const stamp = Date.now();
		const prefix = `RO01-${stamp}-`;
		const names = [`${prefix}A`, `${prefix}B`, `${prefix}C`];

		for (const n of names) {
			await createItem(page, n);
		}

		const initial = await orderedNames(page, prefix);
		expect(initial).toEqual(names);

		// Move C above A and B.
		await reorderByName(page, SEED_LIST_ID, [names[2], names[0], names[1]]);

		await page.reload();
		await expect(page.getByPlaceholder(ADD_ITEM_PLACEHOLDER)).toBeVisible({ timeout: 15_000 });

		const reordered = await orderedNames(page, prefix);
		expect(reordered[0]).toBe(names[2]);
		const aIdx = reordered.indexOf(names[0]);
		const bIdx = reordered.indexOf(names[1]);
		expect(aIdx).toBeGreaterThan(0);
		expect(bIdx).toBeGreaterThan(aIdx);
	});
});

test.describe('Drag-reorder — realtime', () => {
	async function loggedInListPage(browser: Browser, user: (typeof USERS)[keyof typeof USERS]) {
		const ctx = await browser.newContext();
		const p = await ctx.newPage();
		await loginAs(p, user);
		await p.goto(SEED_LIST_PATH);
		await expect(p.getByPlaceholder(ADD_ITEM_PLACEHOLDER)).toBeVisible({ timeout: 15_000 });
		return { ctx, p };
	}

	test('RO-02: a reorder by Ana propagates to Borja without reload', async ({ browser }) => {
		const ana = await loggedInListPage(browser, USERS.ana);
		const borja = await loggedInListPage(browser, USERS.borja);

		try {
			const stamp = Date.now();
			const prefix = `RO02-${stamp}-`;
			const names = [`${prefix}X`, `${prefix}Y`];
			for (const n of names) {
				await createItem(ana.p, n);
			}

			// Both sides see the items.
			for (const n of names) {
				await expect(
					borja.p.locator(`[role="listitem"][data-name="${n}"]`).first()
				).toBeVisible({ timeout: 10_000 });
			}

			// Reverse the order on Ana's side.
			await reorderByName(ana.p, SEED_LIST_ID, [names[1], names[0]]);

			// Realtime feeds the new order to Borja within the suite's 10s window.
			await expect
				.poll(
					async () => orderedNames(borja.p, prefix),
					{ timeout: 10_000 }
				)
				.toEqual([names[1], names[0]]);
		} finally {
			await ana.ctx.close();
			await borja.ctx.close();
		}
	});
});
