import { test, expect } from '@playwright/test';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

/**
 * Document management E2E tests.
 * Assumes auth setup has run (storageState is applied by playwright.config.ts).
 * Assumes the backend has at least one document in the database.
 */
test.describe('Document list', () => {
	test.beforeEach(async ({ page }) => {
		await page.goto('/');
		// Wait for SvelteKit hydration to complete so onclick/oninput handlers are active.
		await page.waitForSelector('[data-hydrated]');
	});

	test('renders the search bar and document list', async ({ page }) => {
		await expect(page.getByPlaceholder('Suche in Titel, Inhalt, Ort...')).toBeVisible();
		await expect(page.getByRole('link', { name: /Neues Dokument/i })).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/documents-home.png' });
	});

	test('navigation bar shows active state for Dokumente', async ({ page }) => {
		const navLink = page.getByRole('navigation').getByRole('link', { name: 'Dokumente' });
		await expect(navLink).toHaveClass(/bg-nav-active/);
	});

	test('text search filters the document list', async ({ page }) => {
		// Navigate directly with the query param — tests that search results are filtered
		// correctly without depending on the debounced oninput → goto chain in CI.
		await page.goto('/?q=zzz_unlikely_to_match_anything');
		await expect(page.getByText('Keine Dokumente gefunden')).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/documents-search-no-results.png' });
	});

	test('clearing the search returns all documents', async ({ page }) => {
		// Navigate with an active query first, then click the reset link.
		await page.goto('/?q=xyz_unlikely');
		await page.getByTitle('Filter zurücksetzen').click();
		await page.waitForURL('/');
		await expect(page).toHaveURL('/');
		await page.screenshot({ path: 'test-results/e2e/documents-reset-search.png' });
	});

	test('advanced filters panel opens and closes', async ({ page }) => {
		const btn = page.getByRole('button', { name: 'Filter', exact: true });
		await btn.click();
		await expect(page.getByLabel('Von')).toBeVisible();
		await expect(page.getByLabel('Bis')).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/documents-filters-open.png' });
		await btn.click();
		await expect(page.getByLabel('Von')).not.toBeVisible();
	});

	test('date range filter triggers a new search', async ({ page }) => {
		await page.getByRole('button', { name: 'Filter', exact: true }).click();
		await page.getByLabel('Von').fill('2000-01-01');
		await page.waitForURL(/from=2000-01-01/);
		await expect(page).toHaveURL(/from=2000-01-01/);
		await page.screenshot({ path: 'test-results/e2e/documents-date-filter.png' });
	});
});

test.describe('Document detail', () => {
	test('clicking a document opens the detail page', async ({ page }) => {
		await page.goto('/');
		// Click the first document link in the list
		const firstDoc = page.locator('ul li a').first();
		const href = await firstDoc.getAttribute('href');
		await firstDoc.click();
		await expect(page).toHaveURL(href!);
		await page.screenshot({ path: 'test-results/e2e/document-detail.png' });
	});
});

test.describe('New document', () => {
	test('renders the upload form with file input first', async ({ page }) => {
		await page.goto('/documents/new');
		await page.waitForSelector('[data-hydrated]');
		await expect(page.getByRole('heading', { name: /Neues Dokument/i })).toBeVisible();
		// File input comes before the title field in DOM order
		const fileInput = page.locator('input[type="file"]');
		const titleInput = page.getByLabel('Titel');
		await expect(fileInput).toBeVisible();
		await expect(titleInput).toBeVisible();
		const fileBox = await fileInput.boundingBox();
		const titleBox = await titleInput.boundingBox();
		expect(fileBox!.y).toBeLessThan(titleBox!.y);
		await page.screenshot({ path: 'test-results/e2e/document-new.png' });
	});

	test('title field is pre-filled from filename when a file is selected', async ({ page }) => {
		await page.goto('/documents/new');
		await page.waitForSelector('[data-hydrated]');
		const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
		const fileInput = page.locator('input[type="file"]');
		await fileInput.setInputFiles({
			name: 'Brief_1965.pdf',
			mimeType: 'application/pdf',
			buffer: fs.readFileSync(PDF_FIXTURE)
		});
		await expect(page.getByLabel('Titel')).toHaveValue('Brief_1965');
		await page.screenshot({ path: 'test-results/e2e/document-new-filename-prefill.png' });
	});

	test('typed title is not overwritten when a file is selected', async ({ page }) => {
		await page.goto('/documents/new');
		await page.waitForSelector('[data-hydrated]');
		await page.getByLabel('Titel').fill('Weihnachtsbrief 1965');
		const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
		const fileInput = page.locator('input[type="file"]');
		await fileInput.setInputFiles({
			name: 'Brief_1965.pdf',
			mimeType: 'application/pdf',
			buffer: fs.readFileSync(PDF_FIXTURE)
		});
		await expect(page.getByLabel('Titel')).toHaveValue('Weihnachtsbrief 1965');
		await page.screenshot({ path: 'test-results/e2e/document-new-title-not-overwritten.png' });
	});
});

test.describe('Document creation', () => {
	test('user fills in a title and lands on the new document detail page', async ({ page }) => {
		await page.goto('/documents/new');
		await page.waitForSelector('[data-hydrated]');

		await page.getByLabel('Titel').fill('E2E Testbrief');
		await page.getByRole('button', { name: 'Speichern', exact: true }).click();

		await expect(page).toHaveURL(/\/documents\/[^/]+$/);
		await expect(page.getByRole('heading', { name: 'E2E Testbrief' })).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/document-create.png' });
	});

	test('user saves a document with only a file — title comes from filename', async ({ page }) => {
		await page.goto('/documents/new');
		await page.waitForSelector('[data-hydrated]');
		const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
		await page.locator('input[type="file"]').setInputFiles({
			name: 'Brief_1965.pdf',
			mimeType: 'application/pdf',
			buffer: fs.readFileSync(PDF_FIXTURE)
		});
		await page.getByRole('button', { name: 'Speichern', exact: true }).click();
		await expect(page).toHaveURL(/\/documents\/[^/]+$/);
		await expect(page.getByRole('heading', { name: 'Brief_1965' })).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/document-create-file-only.png' });
	});
});

test.describe('Document editing', () => {
	test('user opens an existing document, changes the title, and sees the update', async ({
		page
	}) => {
		// Find the document created in the previous describe
		await page.goto('/?q=E2E+Testbrief');
		await page.waitForSelector('[data-hydrated]');
		const docLink = page.getByRole('link', { name: 'E2E Testbrief' }).first();
		const href = await docLink.getAttribute('href');
		await page.goto(`${href}/edit`);
		await page.waitForSelector('[data-hydrated]');

		await page.getByLabel('Titel').fill('E2E Testbrief (überarbeitet)');
		await page.getByRole('button', { name: 'Speichern', exact: true }).click();

		await expect(page).toHaveURL(/\/documents\/[^/]+$/);
		await expect(page.getByRole('heading', { name: 'E2E Testbrief (überarbeitet)' })).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/document-edit-save.png' });
	});
});

test.describe('Document edit', () => {
	test('renders the edit form with pre-filled data', async ({ page }) => {
		// Navigate to home, find first document, go to its edit page
		await page.goto('/');
		const firstDocLink = page.locator('ul li a').first();
		const href = await firstDocLink.getAttribute('href');
		await page.goto(`${href}/edit`);
		await expect(page.getByRole('heading', { name: /Bearbeiten/i })).toBeVisible();
		await expect(page.getByLabel('Titel')).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/document-edit.png' });
	});

	test('shows a validation error for an invalid date format', async ({ page }) => {
		await page.goto('/');
		const firstDocLink = page.locator('ul li a').first();
		const href = await firstDocLink.getAttribute('href');
		await page.goto(`${href}/edit`);
		// Wait for hydration so oninput={handleDateInput} is registered.
		await page.waitForSelector('[data-hydrated]');
		const dateInput = page.getByLabel('Datum');
		// Type partial digits: '99' → dateDisplay='99', dateIso='' → dateInvalid=true
		await dateInput.fill('');
		await dateInput.pressSequentially('99');
		await expect(page.getByText(/TT\.MM\.JJJJ/i)).toBeVisible();
		await page.screenshot({ path: 'test-results/e2e/document-edit-date-error.png' });
	});
});

// ─── PDF Viewer ───────────────────────────────────────────────────────────────

const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');

test.describe('PDF viewer', () => {
	let pdfDocHref: string;
	let noFileDocHref: string;

	test.beforeAll(async ({ request }) => {
		// Create a document with a PDF file.
		const createRes = await request.post('/api/documents', {
			multipart: { title: 'E2E PDF Viewer Test' }
		});
		if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
		const doc = await createRes.json();

		const uploadRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: {
					name: 'minimal.pdf',
					mimeType: 'application/pdf',
					buffer: fs.readFileSync(PDF_FIXTURE)
				}
			}
		});
		if (!uploadRes.ok()) throw new Error(`Upload PDF failed: ${uploadRes.status()}`);
		pdfDocHref = `/documents/${doc.id}`;

		// Create a document WITHOUT a file — used to verify no canvas is rendered.
		const noFileRes = await request.post('/api/documents', {
			multipart: { title: 'E2E No-File Test' }
		});
		if (!noFileRes.ok()) throw new Error(`Create no-file document failed: ${noFileRes.status()}`);
		const noFileDoc = await noFileRes.json();
		noFileDocHref = `/documents/${noFileDoc.id}`;
	});

	test('PDF renders in the custom viewer — canvas is present instead of iframe', async ({
		page
	}) => {
		await page.goto(pdfDocHref);
		await page.waitForSelector('[data-hydrated]');

		// There must be NO iframe — we replaced it with PDF.js canvas rendering.
		await expect(page.locator('iframe')).not.toBeAttached();

		// At least one canvas element must be visible (one per rendered page).
		await expect(page.locator('canvas').first()).toBeVisible({ timeout: 15000 });

		await page.screenshot({ path: 'test-results/e2e/pdf-viewer-canvas.png' });
	});

	test('page navigation controls are visible', async ({ page }) => {
		await page.goto(pdfDocHref);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 15000 });

		await expect(page.getByRole('button', { name: /prev|previous|zurück|vorige/i })).toBeVisible();
		await expect(page.getByRole('button', { name: /next|weiter|nächste/i })).toBeVisible();

		await page.screenshot({ path: 'test-results/e2e/pdf-viewer-nav.png' });
	});

	test('document without a file has no canvas', async ({ page }) => {
		// A document with no file attached must not render a PDF canvas.
		await page.goto(noFileDocHref);
		await page.waitForSelector('[data-hydrated]');

		// No canvas — this document has no file
		await expect(page.locator('canvas')).not.toBeAttached();

		await page.screenshot({ path: 'test-results/e2e/pdf-viewer-image-fallback.png' });
	});
});

// ─── PDF Annotations (admin) ──────────────────────────────────────────────────

// Shared with the read-only user describe block below
let sharedAnnotationDocId: string;

test.describe('PDF annotations — admin', () => {
	let annotationDocHref: string;

	test.beforeAll(async ({ request }) => {
		// Create a document with a PDF via API — much faster than UI automation.
		const createRes = await request.post('/api/documents', {
			multipart: { title: 'E2E Annotations Test' }
		});
		if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
		const doc = await createRes.json();

		const uploadRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: {
					name: 'minimal.pdf',
					mimeType: 'application/pdf',
					buffer: fs.readFileSync(PDF_FIXTURE)
				}
			}
		});
		if (!uploadRes.ok()) throw new Error(`Upload PDF failed: ${uploadRes.status()}`);

		annotationDocHref = `/documents/${doc.id}`;
		sharedAnnotationDocId = doc.id;
	});

	test('admin user sees an active Annotieren button on a PDF', async ({ page }) => {
		test.setTimeout(60_000);
		await page.goto(annotationDocHref);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		// Admin has ANNOTATE_ALL — button must be enabled
		const annotateBtn = page.getByRole('button', { name: /^annotieren$/i });
		await expect(annotateBtn).toBeVisible();
		await expect(annotateBtn).not.toBeDisabled();

		await page.screenshot({ path: 'test-results/e2e/annotations-button-admin.png' });
	});

	test('admin can draw an annotation and it appears on the page', async ({ page }) => {
		test.setTimeout(60_000);
		await page.goto(annotationDocHref);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		// Enable annotate mode
		await page.getByRole('button', { name: /^annotieren$/i }).click();

		// Color picker must appear
		await expect(page.getByLabel(/farbe/i)).toBeVisible();

		// Draw on the annotation layer overlay
		const annotationLayer = page.locator('[role="presentation"]').last();
		const box = await annotationLayer.boundingBox();
		if (!box) throw new Error('Annotation layer not found');

		const startX = box.x + box.width * 0.3;
		const startY = box.y + box.height * 0.3;
		const endX = box.x + box.width * 0.55;
		const endY = box.y + box.height * 0.55;

		await page.mouse.move(startX, startY);
		await page.mouse.down();
		await page.mouse.move(endX, endY);
		await page.mouse.up();

		await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
			timeout: 8000
		});

		await page.screenshot({ path: 'test-results/e2e/annotation-drawn.png' });
	});

	test('annotation persists after page reload', async ({ page }) => {
		test.setTimeout(60_000);
		await page.goto(annotationDocHref);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		// Annotation from the previous test must be loaded from the API
		await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
			timeout: 8000
		});

		await page.screenshot({ path: 'test-results/e2e/annotation-persisted.png' });
	});

	test('admin can delete an annotation', async ({ page }) => {
		test.setTimeout(60_000);
		await page.goto(annotationDocHref);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		// Ensure at least one annotation is visible before enabling annotate mode
		await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
			timeout: 8000
		});
		// Record count now — the draw test may have created more than one annotation
		const countBefore = await page.locator('[data-testid^="annotation-"]').count();
		// Guard against a missing seed: without this, a count of 0 would turn the
		// post-delete assertion into toHaveCount(-1) and fail with a misleading timeout.
		expect(countBefore).toBeGreaterThan(0);

		// Enable annotate mode — deletion is only available while annotating
		await page.getByRole('button', { name: /^annotieren$/i }).click();

		// The on-canvas delete button was removed (issue #722). Delete via the
		// kept keyboard shortcut: focus an annotation, press Delete, confirm.
		const annotation = page.locator('[data-testid^="annotation-"]').first();
		// Capture the identity of the specific annotation we delete so we can assert
		// that exact element is gone afterwards — a count drop alone is identity-blind.
		const deletedTestId = await annotation.getAttribute('data-testid');
		expect(deletedTestId).toBeTruthy();
		await annotation.click();
		await annotation.press('Delete');

		await page.getByRole('button', { name: /^bestätigen$/i }).click();

		await expect(page.locator('[data-testid^="annotation-"]')).toHaveCount(countBefore - 1, {
			timeout: 8000
		});
		// Identity check: the specific annotation we deleted must no longer exist.
		await expect(page.locator(`[data-testid="${deletedTestId}"]`)).toHaveCount(0, {
			timeout: 8000
		});

		await page.screenshot({ path: 'test-results/e2e/annotation-deleted.png' });
	});
});

// ─── PDF Annotations — file hash (version awareness) ─────────────────────────

test.describe('PDF annotations — file hash versioning', () => {
	const PDF_FIXTURE2 = path.resolve(__dirname, 'fixtures/minimal2.pdf');

	test('annotations are hidden after a different file is uploaded', async ({ page, request }) => {
		test.setTimeout(90_000);

		// 1. Create document and upload original PDF
		const createRes = await request.post('/api/documents', {
			multipart: { title: 'E2E Hash Test — version' }
		});
		if (!createRes.ok()) throw new Error(`Create failed: ${createRes.status()}`);
		const doc = await createRes.json();

		const uploadRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: {
					name: 'minimal.pdf',
					mimeType: 'application/pdf',
					buffer: fs.readFileSync(PDF_FIXTURE)
				}
			}
		});
		if (!uploadRes.ok()) throw new Error(`Upload failed: ${uploadRes.status()}`);

		// 2. Create an annotation via API
		const annotRes = await request.post(`/api/documents/${doc.id}/annotations`, {
			data: { pageNumber: 1, x: 0.1, y: 0.1, width: 0.2, height: 0.2, color: '#ff0000' }
		});
		if (!annotRes.ok()) throw new Error(`Create annotation failed: ${annotRes.status()}`);

		// 3. Verify annotation appears before re-upload
		await page.goto(`/documents/${doc.id}`);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });
		await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
			timeout: 8000
		});

		// 4. Upload a different file (different hash)
		const reuploadRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: {
					name: 'minimal2.pdf',
					mimeType: 'application/pdf',
					buffer: fs.readFileSync(PDF_FIXTURE2)
				}
			}
		});
		if (!reuploadRes.ok()) throw new Error(`Re-upload failed: ${reuploadRes.status()}`);

		// 5. Reload — annotation must be hidden and notice shown
		await page.reload();
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		// Use :not() to exclude the outdated-notice element whose testid also starts with "annotation-"
		await expect(
			page.locator('[data-testid^="annotation-"]:not([data-testid="annotation-outdated-notice"])')
		).toHaveCount(0, { timeout: 8000 });
		await expect(page.locator('[data-testid="annotation-outdated-notice"]')).toBeVisible({
			timeout: 5000
		});

		await page.screenshot({ path: 'test-results/e2e/annotation-hidden-after-reupload.png' });
	});

	test('annotations reappear after re-uploading the original file', async ({ page, request }) => {
		test.setTimeout(90_000);

		// 1. Create document and upload original PDF
		const createRes = await request.post('/api/documents', {
			multipart: { title: 'E2E Hash Test — restore' }
		});
		if (!createRes.ok()) throw new Error(`Create failed: ${createRes.status()}`);
		const doc = await createRes.json();

		const originalBytes = fs.readFileSync(PDF_FIXTURE);
		const uploadRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: { name: 'minimal.pdf', mimeType: 'application/pdf', buffer: originalBytes }
			}
		});
		if (!uploadRes.ok()) throw new Error(`Upload failed: ${uploadRes.status()}`);

		// 2. Create annotation
		const annotRes = await request.post(`/api/documents/${doc.id}/annotations`, {
			data: { pageNumber: 1, x: 0.1, y: 0.1, width: 0.2, height: 0.2, color: '#0000ff' }
		});
		if (!annotRes.ok()) throw new Error(`Create annotation failed: ${annotRes.status()}`);

		// 3. Replace with different file
		const replaceRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: {
					name: 'minimal2.pdf',
					mimeType: 'application/pdf',
					buffer: fs.readFileSync(PDF_FIXTURE2)
				}
			}
		});
		if (!replaceRes.ok()) throw new Error(`Replace failed: ${replaceRes.status()}`);

		// 4. Re-upload original file (restoring the hash)
		const restoreRes = await request.put(`/api/documents/${doc.id}`, {
			multipart: {
				title: doc.title,
				file: { name: 'minimal.pdf', mimeType: 'application/pdf', buffer: originalBytes }
			}
		});
		if (!restoreRes.ok()) throw new Error(`Restore failed: ${restoreRes.status()}`);

		// 5. Verify annotation reappears and notice is gone
		await page.goto(`/documents/${doc.id}`);
		await page.waitForSelector('[data-hydrated]');
		await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });

		await expect(page.locator('[data-testid^="annotation-"]').first()).toBeVisible({
			timeout: 8000
		});
		await expect(page.locator('[data-testid="annotation-outdated-notice"]')).not.toBeVisible();

		await page.screenshot({ path: 'test-results/e2e/annotation-restored.png' });
	});
});

// ─── PDF Annotations (read-only user) ─────────────────────────────────────────

test.describe('PDF annotations — read-only user', () => {
	// Isolated session — does not share the admin storage state
	test.use({ storageState: { cookies: [], origins: [] } });

	test('read-only user does not see the Annotieren button', async ({ page }) => {
		test.setTimeout(60_000);
		await page.goto('/login');
		await page.getByLabel('Benutzername').fill('reader');
		await page.getByLabel('Passwort').fill('reader123');
		await page.getByRole('button', { name: 'Anmelden' }).click();
		await page.waitForURL('/');

		// Navigate directly to the PDF document created by the admin beforeAll.
		await page.goto(`/documents/${sharedAnnotationDocId}`);
		await page.waitForSelector('[data-hydrated]');

		// Reader users do not have ANNOTATE_ALL permission — the button must not be shown at all.
		const annotateBtn = page.getByRole('button', { name: /annotieren/i });
		await expect(annotateBtn).not.toBeVisible({ timeout: 5000 });

		await page.screenshot({ path: 'test-results/e2e/annotations-button-reader.png' });
	});
});

// ── J3: Edit document — add an existing tag ────────────────────────────────
//
// Verifies that a user can open a document's edit page and assign a tag using
// the TagInput component, then save and see the tag link on the detail page.
// Seeds a unique tag via a throwaway document so the test never depends on the
// seeded "Familie" tag (which admin tests rename during their lifecycle).

test.describe('Document editing — tags (J3)', () => {
	let tagDocId: string;
	let seedDocId: string;
	let seededTagName: string;

	test.beforeAll(async ({ request }) => {
		const stamp = Date.now().toString(36);
		seededTagName = `E2E-J3-Tag-${stamp}`;

		// Create a throwaway document and associate the unique tag with it so it
		// exists in the system for the TagInput suggestion list.
		const seederRes = await request.post('/api/documents', {
			multipart: { title: `E2E J3 Tag Seeder ${stamp}` }
		});
		if (!seederRes.ok()) throw new Error(`Create seeder failed: ${seederRes.status()}`);
		const seeder = await seederRes.json();
		seedDocId = seeder.id;

		const seedTagRes = await request.put(`/api/documents/${seedDocId}`, {
			multipart: { title: seeder.title, tags: seededTagName }
		});
		if (!seedTagRes.ok()) throw new Error(`Seed tag failed: ${seedTagRes.status()}`);

		// Create the test document without the tag — the test will add it.
		const createRes = await request.post('/api/documents', {
			multipart: { title: `E2E Tag Edit Test ${stamp}` }
		});
		if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
		const doc = await createRes.json();
		tagDocId = doc.id;
	});

	test.afterAll(async ({ request }) => {
		if (tagDocId) await request.delete(`/api/documents/${tagDocId}`);
		if (seedDocId) await request.delete(`/api/documents/${seedDocId}`);
	});

	test('user adds an existing tag and sees it on the detail page', async ({ page }) => {
		await page.goto(`/documents/${tagDocId}/edit`);
		await page.waitForSelector('[data-hydrated]');

		// TagInput has placeholder "Schlagworte hinzufügen..." when empty.
		const tagInput = page.getByPlaceholder('Schlagworte hinzufügen...');
		await expect(tagInput).toBeVisible();

		// Type the seeded tag name and wait for the suggestion.
		await tagInput.fill(seededTagName);
		const suggestion = page.getByRole('option', { name: seededTagName }).first();
		await expect(suggestion).toBeVisible({ timeout: 5_000 });
		await suggestion.click();

		// Save the document.
		await page.getByRole('button', { name: 'Speichern', exact: true }).click();

		// Redirected to detail page — the tag link must be visible in the metadata section.
		await expect(page).toHaveURL(/\/documents\/[^/]+$/);
		await expect(page.locator('a[href*="?tag="]', { hasText: seededTagName })).toBeVisible({
			timeout: 5_000
		});
		await page.screenshot({ path: 'test-results/e2e/document-edit-tag.png' });
	});
});

// ── J4: Create a brand-new tag via TagInput ────────────────────────────────
//
// Types a tag name that does not exist yet, confirms creation with Enter, and
// verifies the tag chip persists after save AND after a full page reload.

test.describe('Document editing — new tag creation (J4)', () => {
	let newTagDocId: string;
	const stamp = Date.now().toString(36);
	const newTagName = `E2E-Tag-${stamp}`;

	test.beforeAll(async ({ request }) => {
		const createRes = await request.post('/api/documents', {
			multipart: { title: `E2E New Tag Test ${stamp}` }
		});
		if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
		const doc = await createRes.json();
		newTagDocId = doc.id;
	});

	test.afterAll(async ({ request }) => {
		if (newTagDocId) await request.delete(`/api/documents/${newTagDocId}`);
	});

	test('user types a new tag name, presses Enter, saves, and tag persists after reload', async ({
		page
	}) => {
		await page.goto(`/documents/${newTagDocId}/edit`);
		await page.waitForSelector('[data-hydrated]');

		const tagInput = page.getByPlaceholder('Schlagworte hinzufügen...');
		await expect(tagInput).toBeVisible();

		await tagInput.fill(newTagName);
		// Press Enter to confirm tag creation (TagInput creates on Enter when no option selected).
		await tagInput.press('Enter');

		// The chip for the new tag should appear inside the TagInput immediately.
		await expect(page.getByText(newTagName)).toBeVisible({ timeout: 5_000 });

		await page.getByRole('button', { name: 'Speichern', exact: true }).click();

		// Detail page after redirect — tag link must be visible.
		await expect(page).toHaveURL(/\/documents\/[^/]+$/);
		await expect(page.locator('a[href*="?tag="]', { hasText: newTagName })).toBeVisible({
			timeout: 5_000
		});

		// Reload to verify the tag survived the round-trip (not just client-side state).
		await page.reload();
		await page.waitForSelector('[data-hydrated]');
		await expect(page.locator('a[href*="?tag="]', { hasText: newTagName })).toBeVisible({
			timeout: 5_000
		});

		await page.screenshot({ path: 'test-results/e2e/document-new-tag-created.png' });
	});
});

// ── J6: Multi-filter search (text + tag) ──────────────────────────────────
//
// Verifies that combining a text query with a tag filter narrows results
// correctly on the document search page.

test.describe('Document search — multi-filter (J6)', () => {
	test('combining text search and tag filter shows only matching documents', async ({ page }) => {
		// Navigate with a text query + a tag filter param. Using an unlikely text string and
		// a nonexistent tag name confirms that the AND combination of both filters returns no
		// results without relying on seeded data. Note: the correct URL param is "tag" (tag name),
		// not "tagId".
		await page.goto('/?q=zzz_unlikely&tag=zzz-nonexistent-tag-name');
		await page.waitForSelector('[data-hydrated]');

		await expect(page.getByText('Keine Dokumente gefunden')).toBeVisible({ timeout: 5_000 });

		// Now navigate with just the text query — should also have no results for the noise string.
		await page.goto('/?q=zzz_unlikely');
		await expect(page.getByText('Keine Dokumente gefunden')).toBeVisible({ timeout: 5_000 });

		await page.screenshot({ path: 'test-results/e2e/document-multi-filter.png' });
	});

	test('date range + text query combination triggers a filtered search', async ({ page }) => {
		// Use two filter params together from the URL — both must appear in the URL
		// and the search must run without errors.
		await page.goto('/?q=E2E&from=2000-01-01');
		await page.waitForSelector('[data-hydrated]');

		// The URL must contain both params (confirming SvelteKit preserves them).
		await expect(page).toHaveURL(/q=E2E/);
		await expect(page).toHaveURL(/from=2000-01-01/);

		await page.screenshot({ path: 'test-results/e2e/document-multi-filter-date-text.png' });
	});
});
