import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';

/**
 * Minimal Geschichten coverage. The deeper a11y / visual-regression suite is
 * tracked separately; this file proves the core writer + reader journey works
 * end-to-end against the real stack.
 *
 * Pre-requisite: V59 has granted BLOG_WRITE to the Administrators group, so
 * the seeded admin user can author. The auth.setup project handles login.
 */

const stamp = () => new Date().toISOString().replace(/[^0-9]/g, '');

test.describe('Geschichten — writer + reader journey', () => {
	test('admin can create a draft, publish it, and see it on the index', async ({ page }) => {
		const title = `E2E story ${stamp()}`;

		// Land on the index — empty state or pre-existing demo data is fine
		await page.goto('/geschichten');
		await page.waitForSelector('[data-hydrated]');
		await expect(page.getByRole('heading', { name: 'Geschichten', level: 1 })).toBeVisible();

		// Click "Neue Geschichte" — visible because admin has BLOG_WRITE
		await page.getByRole('link', { name: 'Neue Geschichte' }).click();
		await page.waitForURL('/geschichten/new');

		// Fill in title — the body editor is Tiptap and harder to script reliably
		await page.getByPlaceholder('Titel der Geschichte').fill(title);

		// Save as draft and verify we land on the detail page
		await page.getByRole('button', { name: 'Entwurf speichern' }).click();
		await page.waitForURL(/\/geschichten\/[^/]+$/);

		// Capture the new id from the URL
		const detailUrl = page.url();
		const id = detailUrl.split('/').pop();
		expect(id).toBeTruthy();

		// Publish from the edit page
		await page.getByRole('link', { name: 'Bearbeiten' }).click();
		await page.waitForURL(/\/edit$/);
		await page.getByRole('button', { name: 'Veröffentlichen' }).click();
		await page.waitForURL(detailUrl);

		// Index now shows the published story
		await page.goto('/geschichten');
		await expect(page.getByRole('link', { name: title })).toBeVisible();
	});

	test('reader is taken to a story detail when clicking a card', async ({ page }) => {
		await page.goto('/geschichten');
		await page.waitForSelector('[data-hydrated]');

		// Use the first story link in the list (demo data exists; if not, the
		// previous test seeded one). The link wraps the whole card.
		const firstStory = page.locator('a[href^="/geschichten/"]').filter({ hasText: /.+/ }).first();
		await expect(firstStory).toBeVisible();
		await firstStory.click();

		await page.waitForURL(/\/geschichten\/[^/]+$/);
		await expect(page.locator('article')).toBeVisible();
	});

	test('multi-person filter: chips, URL params, and AND removal work end-to-end', async ({
		page
	}) => {
		await page.goto('/geschichten');
		await page.waitForSelector('[data-hydrated]');

		// We need two distinct persons to filter by, but we don't want to couple this
		// test to specific seed names. Strategy: type a single broadly-occurring vowel
		// ("e" is present in the vast majority of German names), open the listbox,
		// and pick whichever option matches the predicate.
		//
		// option DOM ids encode the person id as `${listboxId}-option-${personId}`,
		// so we can identify the *first different* option without knowing the seed.
		const PROBE = 'e';

		async function openPicker() {
			await page.getByRole('button', { name: /Person wählen/ }).click();
			const input = page.getByRole('combobox', { name: /Person wählen/ });
			await input.fill(PROBE);
			// Wait for the listbox to be populated.
			await expect(page.getByRole('option').first()).toBeVisible();
		}

		async function pickFirstOption(): Promise<string> {
			const opt = page.getByRole('option').first();
			const optId = (await opt.getAttribute('id')) ?? '';
			const personId = optId.split('-option-')[1] ?? '';
			expect(personId).not.toEqual('');
			await opt.click();
			return personId;
		}

		async function pickFirstOptionDifferentFrom(excludeId: string): Promise<string> {
			// Iterate through visible options and return the first whose person id != excludeId.
			const optionCount = await page.getByRole('option').count();
			for (let i = 0; i < optionCount; i++) {
				const candidate = page.getByRole('option').nth(i);
				const optId = (await candidate.getAttribute('id')) ?? '';
				const personId = optId.split('-option-')[1] ?? '';
				if (personId && personId !== excludeId) {
					await candidate.click();
					return personId;
				}
			}
			throw new Error(
				`Expected at least two distinct persons matching "${PROBE}" in the seed, found only one.`
			);
		}

		await openPicker();
		const firstId = await pickFirstOption();
		await page.waitForURL(/personId=/);
		const firstIds = new URL(page.url()).searchParams.getAll('personId');
		expect(firstIds).toEqual([firstId]);

		await openPicker();
		const secondId = await pickFirstOptionDifferentFrom(firstId);
		await page.waitForURL((url) => url.searchParams.getAll('personId').length === 2);
		const secondIds = new URL(page.url()).searchParams.getAll('personId');
		expect(secondIds).toEqual([firstId, secondId]);
		expect(secondId).not.toEqual(firstId);

		// Two chips visible — find them by their remove-aria-label pattern
		const chipButtons = page.getByRole('button', { name: /aus Filter entfernen/ });
		await expect(chipButtons).toHaveCount(2);

		// Remove the first chip — URL drops to one param, only the second id remains
		await chipButtons.first().click();
		await page.waitForURL((url) => url.searchParams.getAll('personId').length === 1);
		const finalIds = new URL(page.url()).searchParams.getAll('personId');
		expect(finalIds).toEqual([secondId]);
	});

	test('AxeBuilder finds no critical violations on the index', async ({ page }) => {
		await page.goto('/geschichten');
		await page.waitForSelector('[data-hydrated]');
		const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();

		// Filter to non-deferred severity. We don't gate the whole PR on a clean
		// AxeBuilder run yet — Sara's review tracks the broader a11y backlog —
		// but any "serious" or "critical" finding from this scan would block merge.
		const blocking = results.violations.filter(
			(v) => v.impact === 'serious' || v.impact === 'critical'
		);
		expect(blocking, JSON.stringify(blocking, null, 2)).toEqual([]);
	});
});
