import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';

const mockNavigating = { to: null };

vi.mock('$app/navigation', () => ({
	beforeNavigate: () => {},
	afterNavigate: () => {},
	goto: vi.fn(),
	invalidate: vi.fn(),
	invalidateAll: vi.fn(),
	preloadCode: vi.fn(),
	preloadData: vi.fn(),
	pushState: vi.fn(),
	replaceState: vi.fn(),
	disableScrollHandling: vi.fn(),
	onNavigate: () => () => {}
}));

vi.mock('$app/state', () => ({
	get navigating() {
		return mockNavigating;
	},
	get page() {
		return { url: new URL('http://localhost/documents'), data: {} };
	}
}));

const { default: DocumentsListPage } = await import('./+page.svelte');
const { bulkSelectionStore } = await import('$lib/document/bulkSelection.svelte');

afterEach(() => {
	cleanup();
	bulkSelectionStore.clear();
});

const baseData = (overrides: Record<string, unknown> = {}) => ({
	items: [] as { id: string; title: string }[],
	totalElements: 0,
	totalPages: 1,
	pageNumber: 0,
	canWrite: false,
	error: null,
	q: '',
	from: '',
	to: '',
	senderId: '',
	receiverId: '',
	initialSenderName: '',
	initialReceiverName: '',
	tags: [] as string[],
	sort: 'DATE',
	dir: 'desc',
	tagQ: '',
	tagOp: 'AND',
	density: [],
	minDate: null,
	maxDate: null,
	zoomFrom: null,
	zoomTo: null,
	...overrides
});

describe('documents/+ page', () => {
	it('renders the screen-reader-only heading', async () => {
		render(DocumentsListPage, { props: { data: baseData() } });

		await expect
			.element(page.getByRole('heading', { name: /^dokumente$/i, level: 1 }))
			.toBeVisible();
	});

	it('omits the result count when totalElements is 0', async () => {
		render(DocumentsListPage, { props: { data: baseData() } });

		await expect.element(page.getByText(/0 Dokumente/i)).not.toBeInTheDocument();
	});

	it('renders the result count when totalElements is greater than 0', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ totalElements: 5 }) }
		});

		await expect.element(page.getByText(/5 Dokumente/i)).toBeVisible();
	});

	it('shows the new-document CTA when canWrite is true', async () => {
		render(DocumentsListPage, { props: { data: baseData({ canWrite: true }) } });

		await expect
			.element(page.getByRole('link', { name: /neues dokument/i }))
			.toHaveAttribute('href', '/documents/new');
	});

	it('hides the new-document CTA when canWrite is false', async () => {
		render(DocumentsListPage, { props: { data: baseData({ canWrite: false }) } });

		await expect
			.element(page.getByRole('link', { name: /neues dokument/i }))
			.not.toBeInTheDocument();
	});

	it('shows the bulk-edit-all CTA when canWrite is true and totalElements > 0', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ canWrite: true, totalElements: 12 }) }
		});

		await expect.element(page.getByRole('button', { name: /alle 12 editieren/i })).toBeVisible();
	});

	it('hides the bulk-edit-all CTA when totalElements is 0', async () => {
		render(DocumentsListPage, { props: { data: baseData({ canWrite: true }) } });

		await expect
			.element(page.getByRole('button', { name: /alle .* editieren/i }))
			.not.toBeInTheDocument();
	});

	it('hides the bulk-edit-all and new-doc CTAs when canWrite is false', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ canWrite: false, totalElements: 12 }) }
		});

		await expect
			.element(page.getByRole('link', { name: /neues dokument/i }))
			.not.toBeInTheDocument();
		await expect.element(page.getByRole('button', { name: /alle 12/i })).not.toBeInTheDocument();
	});

	it('displays the no-results state when items is empty (via DocumentList)', async () => {
		render(DocumentsListPage, { props: { data: baseData({ items: [], totalElements: 0 }) } });

		expect(document.body.textContent).toMatch(/keine|noch|empty/i);
	});

	it('renders the document list when items are present', async () => {
		render(DocumentsListPage, {
			props: {
				data: baseData({
					items: [
						{
							id: 'd1',
							title: 'Brief 1899',
							documentDate: '1899-04-14',
							originalFilename: 'b1.pdf',
							receivers: [],
							tags: [],
							matchData: {
								titleOffsets: [],
								senderMatched: false,
								matchedReceiverIds: [],
								matchedTagIds: [],
								snippetOffsets: [],
								summaryOffsets: []
							},
							completionPercentage: 80,
							contributors: []
						}
					],
					totalElements: 1
				})
			}
		});

		expect(document.body.textContent).toContain('Brief 1899');
	});

	it('preselects the search input from data.q', async () => {
		render(DocumentsListPage, { props: { data: baseData({ q: 'kurrent' }) } });

		const input = document.querySelector('input[type="text"]') as HTMLInputElement;
		expect(input?.value).toBe('kurrent');
	});

	it('opens the advanced-filter section when from/to date filters are preselected', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ from: '1899-01-01', to: '1950-12-31' }) }
		});

		// hasAdvancedFilters() returns true when from/to is set, which forces showAdvanced=true onMount,
		// revealing the advanced filter section. The "Von" / "Bis" date labels are part of it.
		await expect.element(page.getByText('Von')).toBeVisible();
		await expect.element(page.getByText('Bis')).toBeVisible();
	});

	it('opens the advanced-filter section when tag filters are preselected', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ tags: ['Kurrent', 'Familie'] }) }
		});

		// Tag filter pills are part of the advanced filter section.
		await expect.element(page.getByText('Schlagworte')).toBeVisible();
	});

	it('opens the advanced-filter section when sender/receiver filters are set', async () => {
		render(DocumentsListPage, {
			props: {
				data: baseData({
					senderId: 'p-1',
					initialSenderName: 'Anna Schmidt',
					receiverId: 'p-2',
					initialReceiverName: 'Bert Meier'
				})
			}
		});

		// PersonTypeahead's combobox input is labelled by the "Absender" / "Empfänger" <label>.
		await expect.element(page.getByRole('combobox', { name: /absender/i })).toBeVisible();
		await expect.element(page.getByRole('combobox', { name: /empfänger/i })).toBeVisible();
	});

	it('surfaces the backend error code in an alert when bulk-edit-all fails with 400', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
			new Response(JSON.stringify({ code: 'BULK_EDIT_TOO_MANY_IDS' }), {
				status: 400,
				headers: { 'Content-Type': 'application/json' }
			})
		);
		try {
			render(DocumentsListPage, {
				props: { data: baseData({ canWrite: true, totalElements: 5 }) }
			});

			await page.getByRole('button', { name: /alle 5 editieren/i }).click();

			// vi.waitFor polls the DOM until the alert appears — no setTimeout sleep needed.
			await expect.element(page.getByRole('alert')).toBeVisible();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('renders the alert when bulk-edit-all fetch throws a network error', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('network down'));
		try {
			render(DocumentsListPage, {
				props: { data: baseData({ canWrite: true, totalElements: 3 }) }
			});

			await page.getByRole('button', { name: /alle 3 editieren/i }).click();

			await expect.element(page.getByRole('alert')).toBeVisible();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('renders the document list error banner from data.error', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ error: 'Boom' }) }
		});

		await expect.element(page.getByText('Boom')).toBeVisible();
	});

	it('renders the result count line with the totalElements value', async () => {
		render(DocumentsListPage, { props: { data: baseData({ totalElements: 1 }) } });

		expect(document.body.textContent).toMatch(/\b1\b/);
	});

	it('renders the page heading when no sort/dir is provided (defaults applied)', async () => {
		render(DocumentsListPage, { props: { data: baseData({ sort: '', dir: '' }) } });

		// With sort/dir empty, the page initialises sort='DATE' and dir='desc' via untrack(... || 'DATE').
		// We just confirm the page mounts cleanly under those defaults.
		await expect
			.element(page.getByRole('heading', { name: /^dokumente$/i, level: 1 }))
			.toBeVisible();
	});

	it('renders the tag-filter section when tagOp is OR with multiple tags', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ tags: ['Brief', 'Familie'], tagOp: 'OR' }) }
		});

		await expect.element(page.getByText('Schlagworte')).toBeVisible();
	});

	it('renders the page heading when a zoom range is set', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ zoomFrom: '1899-01-01', zoomTo: '1950-12-31' }) }
		});

		// Zoom only affects TimelineDensityFilter (lg:block) — at unit-test viewport we just verify mount.
		await expect
			.element(page.getByRole('heading', { name: /^dokumente$/i, level: 1 }))
			.toBeVisible();
	});

	it('opens advanced filters with every filter set to a truthy value', async () => {
		render(DocumentsListPage, {
			props: {
				data: baseData({
					q: 'brief',
					from: '1899-01-01',
					to: '1950-12-31',
					senderId: 'p-sender',
					receiverId: 'p-receiver',
					initialSenderName: 'Anna',
					initialReceiverName: 'Bert',
					tags: ['Familie'],
					sort: 'TITLE',
					dir: 'asc',
					tagQ: 'Bri',
					tagOp: 'OR',
					zoomFrom: '1900-01-01',
					zoomTo: '1940-12-31'
				})
			}
		});

		// All advanced labels surface because hasAdvancedFilters() is true on mount.
		await expect.element(page.getByText('Schlagworte')).toBeVisible();
		await expect.element(page.getByRole('combobox', { name: /absender/i })).toBeVisible();
	});

	it('keeps advanced filters collapsed when every filter is at its falsy default', async () => {
		render(DocumentsListPage, {
			props: {
				data: baseData({
					q: '',
					from: '',
					to: '',
					senderId: '',
					receiverId: '',
					initialSenderName: '',
					initialReceiverName: '',
					tags: [],
					sort: '',
					dir: '',
					tagQ: '',
					tagOp: 'AND',
					zoomFrom: null,
					zoomTo: null
				})
			}
		});

		// hasAdvancedFilters() returns false → showAdvanced stays false → "Schlagworte" stays hidden.
		await expect.element(page.getByText('Schlagworte')).not.toBeInTheDocument();
	});

	it('reflects typed text into the search input value via two-way binding', async () => {
		render(DocumentsListPage, { props: { data: baseData() } });

		const input = document.querySelector('input[type="text"]') as HTMLInputElement;
		expect(input).not.toBeNull();
		input.value = 'kurrent';
		input.dispatchEvent(new Event('input', { bubbles: true }));
		expect(input.value).toBe('kurrent');
	});

	it('toggles qFocused on focus/blur of the search input (no crash)', async () => {
		render(DocumentsListPage, { props: { data: baseData() } });

		const input = document.querySelector('input[type="text"]') as HTMLInputElement;
		expect(input).not.toBeNull();
		input.dispatchEvent(new Event('focus', { bubbles: true }));
		input.dispatchEvent(new Event('blur', { bubbles: true }));
		// Search input still mounted and usable after focus/blur cycle.
		expect(document.body.contains(input)).toBe(true);
	});

	it('renders the BulkSelectionBar items count when the bulk-selection store has entries', async () => {
		bulkSelectionStore.toggle('doc-x');
		bulkSelectionStore.toggle('doc-y');

		render(DocumentsListPage, { props: { data: baseData({ canWrite: true }) } });

		// BulkSelectionBar surfaces the selection count once entries exist.
		expect(bulkSelectionStore.size).toBe(2);
		expect(document.body.textContent).toMatch(/2/);
	});

	it('bulk-edit-all populates the selection store with returned IDs on success', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
			new Response(JSON.stringify(['d-a', 'd-b', 'd-c']), {
				status: 200,
				headers: { 'Content-Type': 'application/json' }
			})
		);
		try {
			render(DocumentsListPage, {
				props: { data: baseData({ canWrite: true, totalElements: 3 }) }
			});

			await page.getByRole('button', { name: /alle 3 editieren/i }).click();

			await vi.waitFor(() => expect(bulkSelectionStore.size).toBe(3));
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('opens advanced filters when only senderId is set', async () => {
		render(DocumentsListPage, {
			props: { data: baseData({ senderId: 'p-1', initialSenderName: 'Anna' }) }
		});

		await expect.element(page.getByRole('combobox', { name: /absender/i })).toBeVisible();
	});

	it('opens advanced filters when only from-date is set', async () => {
		render(DocumentsListPage, { props: { data: baseData({ from: '1899-01-01' }) } });

		await expect.element(page.getByText('Von')).toBeVisible();
	});

	it('opens advanced filters when only to-date is set', async () => {
		render(DocumentsListPage, { props: { data: baseData({ to: '1950-12-31' }) } });

		await expect.element(page.getByText('Bis')).toBeVisible();
	});
});
