import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import PdfViewer from './PdfViewer.svelte';
import { makeFakeLibLoader } from './testHelpers';
import { m } from '$lib/paraglide/messages.js';

afterEach(cleanup);

// Document loads fine, but rendering the page rejects with a non-cancellation
// error — exactly the wasm-decode failure class from #708. Exercises the real
// renderCurrentPage path, not the load path.
function makeRenderFailingLibLoader() {
	const page = {
		getViewport: vi.fn().mockReturnValue({ width: 100, height: 100 }),
		render: vi.fn().mockReturnValue({
			promise: Promise.reject(new Error('JBig2 failed to initialize')),
			cancel: vi.fn()
		}),
		streamTextContent: vi.fn().mockReturnValue(new ReadableStream())
	};
	const lib = {
		GlobalWorkerOptions: { workerSrc: '' },
		getDocument: vi.fn().mockReturnValue({
			promise: Promise.resolve({ numPages: 1, getPage: vi.fn().mockResolvedValue(page) })
		}),
		TextLayer: class {
			render() {
				return Promise.resolve();
			}
			cancel() {}
		}
	} as unknown as typeof import('pdfjs-dist');
	return vi.fn().mockResolvedValue([lib, { default: '' }] as const);
}

describe('PdfViewer — render failure', () => {
	it('shows the localized failure message and a download link when the page render rejects', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			libLoader: makeRenderFailingLibLoader()
		});

		await expect.element(page.getByText(m.doc_render_failed())).toBeVisible();
		await expect.element(page.getByRole('link', { name: m.doc_download_link() })).toBeVisible();
		// Announced to assistive tech, not a silent visual-only failure.
		await expect.element(page.getByRole('alert')).toBeVisible();
	});

	it('does not show the failure message when the page renders successfully', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
		expect(document.body.textContent).not.toContain(m.doc_render_failed());
	});
});

describe('PdfViewer — empty / error states', () => {
	it('renders the no-file placeholder when url is empty', async () => {
		render(PdfViewer, { url: '', libLoader: makeFakeLibLoader() });

		await expect.element(page.getByText('Keine Datei vorhanden')).toBeVisible();
	});

	it('does not render the controls when url is empty', async () => {
		render(PdfViewer, { url: '', libLoader: makeFakeLibLoader() });

		const buttons = document.querySelectorAll('button');
		expect(buttons.length).toBe(0);
	});
});

describe('PdfViewer — loaded state', () => {
	it('renders the PDF navigation controls (Zurück/Weiter/Vergrößern/Verkleinern) when a url is provided', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			annotationReloadKey: 0,
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
		await expect.element(page.getByRole('button', { name: 'Weiter' })).toBeVisible();
		await expect.element(page.getByRole('button', { name: 'Vergrößern' })).toBeVisible();
		await expect.element(page.getByRole('button', { name: 'Verkleinern' })).toBeVisible();
	});

	it('renders the canvas background container when annotationsDimmed=true', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			annotationsDimmed: true,
			libLoader: makeFakeLibLoader()
		});

		await vi.waitFor(() => {
			expect(document.querySelector('.bg-pdf-bg')).not.toBeNull();
		});
	});

	it('forces the annotation toggle into "hide" mode when transcribeMode is true and annotations exist', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
			new Response(
				JSON.stringify([
					{
						id: 'a1',
						documentId: 'test',
						pageNumber: 1,
						x: 0.1,
						y: 0.1,
						width: 0.1,
						height: 0.1,
						color: '#000',
						createdAt: '2026-01-01T00:00:00Z',
						fileHash: 'match'
					}
				]),
				{ status: 200, headers: { 'Content-Type': 'application/json' } }
			)
		);
		try {
			render(PdfViewer, {
				url: '/api/documents/test/file',
				documentId: 'test',
				transcribeMode: true,
				documentFileHash: 'match',
				libLoader: makeFakeLibLoader()
			});

			await expect
				.element(page.getByRole('button', { name: /annotierungen verbergen/i }))
				.toBeVisible();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('makes the annotation surface drawable (crosshair) when transcribeMode and canAnnotate', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			transcribeMode: true,
			canAnnotate: true,
			libLoader: makeFakeLibLoader()
		});

		await vi.waitFor(() => {
			const surface = document.querySelector('[role="presentation"]');
			expect(surface).not.toBeNull();
			expect(surface?.getAttribute('style') ?? '').toContain('crosshair');
		});
	});

	it('does not make the annotation surface drawable when canAnnotate is false (read-only user)', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			transcribeMode: true,
			canAnnotate: false,
			libLoader: makeFakeLibLoader()
		});

		await vi.waitFor(() => {
			expect(document.querySelector('.bg-pdf-bg')).not.toBeNull();
		});
		const surface = document.querySelector('[role="presentation"]');
		expect(surface?.getAttribute('style') ?? '').not.toContain('crosshair');
	});

	it('renders the canvas region when documentFileHash is provided', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			documentFileHash: 'abc123',
			libLoader: makeFakeLibLoader()
		});

		await vi.waitFor(() => {
			expect(document.querySelector('.bg-pdf-bg')).not.toBeNull();
		});
	});

	it('renders the PDF controls when flashAnnotationId is set', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			flashAnnotationId: 'ann-flashing',
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
	});

	it('renders the PDF controls when blockNumbers map is provided', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			blockNumbers: { 'ann-1': 1, 'ann-2': 2 },
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
	});

	it('renders the PDF controls when activeAnnotationId is set', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			activeAnnotationId: 'ann-1',
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
	});

	it('renders the PDF nav controls in transcribeMode + activeAnnotationId combo', async () => {
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			transcribeMode: true,
			activeAnnotationId: 'ann-1',
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
		await expect.element(page.getByRole('button', { name: 'Weiter' })).toBeVisible();
	});

	it('renders the PDF controls when an onAnnotationClick callback is wired up', async () => {
		const onAnnotationClick = vi.fn();
		render(PdfViewer, {
			url: '/api/documents/test/file',
			documentId: 'test',
			onAnnotationClick,
			libLoader: makeFakeLibLoader()
		});

		await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
	});

	it('shows the outdated-annotation notice when annotations have non-matching fileHash', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
			new Response(
				JSON.stringify([
					{
						id: 'a1',
						documentId: 'test',
						pageNumber: 1,
						x: 0.1,
						y: 0.1,
						width: 0.1,
						height: 0.1,
						color: '#000',
						createdAt: '2026-01-01T00:00:00Z',
						fileHash: 'old-hash'
					}
				]),
				{ status: 200, headers: { 'Content-Type': 'application/json' } }
			)
		);
		try {
			render(PdfViewer, {
				url: '/api/documents/test/file',
				documentId: 'test',
				documentFileHash: 'new-hash',
				libLoader: makeFakeLibLoader()
			});

			await vi.waitFor(() => {
				expect(document.querySelector('[data-testid="annotation-outdated-notice"]')).not.toBeNull();
			});
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('does not show outdated-annotation notice when all annotations match', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
			new Response(
				JSON.stringify([
					{
						id: 'a1',
						documentId: 'test',
						pageNumber: 1,
						x: 0.1,
						y: 0.1,
						width: 0.1,
						height: 0.1,
						color: '#000',
						createdAt: '2026-01-01T00:00:00Z',
						fileHash: 'matching-hash'
					}
				]),
				{ status: 200, headers: { 'Content-Type': 'application/json' } }
			)
		);
		try {
			render(PdfViewer, {
				url: '/api/documents/test/file',
				documentId: 'test',
				documentFileHash: 'matching-hash',
				libLoader: makeFakeLibLoader()
			});

			await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
			expect(document.querySelector('[data-testid="annotation-outdated-notice"]')).toBeNull();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('still renders the controls when the annotations fetch rejects', async () => {
		const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('network'));
		try {
			render(PdfViewer, {
				url: '/api/documents/test/file',
				documentId: 'test',
				libLoader: makeFakeLibLoader()
			});

			await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
			expect(document.querySelector('[data-testid="annotation-outdated-notice"]')).toBeNull();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('still renders the controls when the annotations fetch returns a non-OK status', async () => {
		const fetchSpy = vi
			.spyOn(globalThis, 'fetch')
			.mockResolvedValue(new Response('error', { status: 500 }));
		try {
			render(PdfViewer, {
				url: '/api/documents/test/file',
				documentId: 'test',
				libLoader: makeFakeLibLoader()
			});

			await expect.element(page.getByRole('button', { name: 'Zurück' })).toBeVisible();
			expect(document.querySelector('[data-testid="annotation-outdated-notice"]')).toBeNull();
		} finally {
			fetchSpy.mockRestore();
		}
	});

	it('shows previous and next page navigation buttons', async () => {
		render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
		await expect.element(page.getByRole('button', { name: /zurück/i })).toBeVisible();
		await expect.element(page.getByRole('button', { name: /weiter/i })).toBeVisible();
	});

	it('shows zoom controls', async () => {
		render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
		await expect.element(page.getByRole('button', { name: /vergrößern/i })).toBeVisible();
		await expect.element(page.getByRole('button', { name: /verkleinern/i })).toBeVisible();
	});

	it('displays the page counter once the PDF has loaded', async () => {
		render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
		await expect.element(page.getByText(/1\s*\/\s*2/)).toBeVisible();
	});
});
