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

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: () => () => {}
}));

const { default: DropZone } = await import('./DropZone.svelte');

afterEach(cleanup);

describe('DropZone', () => {
	it('renders the drop hint and accepted types by default', async () => {
		render(DropZone, { props: {} });

		await expect.element(page.getByText(/einzeln oder mehrere/i)).toBeVisible();
		await expect.element(page.getByText('PDF, JPEG, PNG, TIFF')).toBeVisible();
	});

	it('does not render the progress bar by default', async () => {
		render(DropZone, { props: {} });

		expect(document.querySelector('.bg-primary.h-full')).toBeNull();
	});

	it('rejects files with unaccepted MIME types and shows an error message', async () => {
		render(DropZone, { props: {} });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const badFile = new File(['bad'], 'doc.docx', {
			type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
		});
		Object.defineProperty(input, 'files', { value: [badFile], writable: false });
		input.dispatchEvent(new Event('change', { bubbles: true }));

		await expect.element(page.getByText(/Dateiformat nicht unterstützt/i)).toBeVisible();
	});

	it('accepts a PDF file as a valid type and renders no "invalid type" message', async () => {
		const onUploadComplete = vi.fn();
		render(DropZone, { props: { onUploadComplete } });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const pdfFile = new File(['%PDF'], 'brief.pdf', { type: 'application/pdf' });
		Object.defineProperty(input, 'files', { value: [pdfFile], writable: false });
		input.dispatchEvent(new Event('change', { bubbles: true }));

		// The validation guard never raises an invalid-type error for application/pdf.
		expect(document.body.textContent).not.toMatch(/Dateiformat nicht unterstützt/i);
	});

	it('returns early when no files are selected', async () => {
		render(DropZone, { props: {} });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		Object.defineProperty(input, 'files', { value: [], writable: false });
		input.dispatchEvent(new Event('change', { bubbles: true }));

		const errors = document.querySelectorAll('.text-red-600');
		expect(errors.length).toBe(0);
	});

	it('opens the file input when the drop zone is clicked', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const clickSpy = vi.spyOn(input, 'click');
		dropZone.click();
		expect(clickSpy).toHaveBeenCalled();
	});

	it('opens the file input when Enter is pressed on the drop zone', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const clickSpy = vi.spyOn(input, 'click');
		dropZone.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
		expect(clickSpy).toHaveBeenCalled();
	});

	it('exposes file input as multi-file with accept whitelist', async () => {
		render(DropZone, { props: {} });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		expect(input.multiple).toBe(true);
		expect(input.accept).toContain('.pdf');
	});

	it('applies the dragging style on dragover', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		dropZone.dispatchEvent(new DragEvent('dragover', { bubbles: true }));

		// isDragging=true switches the class to border-primary bg-accent-bg — wait for the next paint.
		await vi.waitFor(() => {
			expect(dropZone.className).toMatch(/bg-accent-bg/);
		});
	});

	it('drops dragging style on dragleave', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		dropZone.dispatchEvent(new DragEvent('dragover', { bubbles: true }));
		await vi.waitFor(() => {
			expect(dropZone.className).toMatch(/bg-accent-bg/);
		});
		dropZone.dispatchEvent(new DragEvent('dragleave', { bubbles: true }));
		await vi.waitFor(() => {
			expect(dropZone.className).not.toMatch(/bg-accent-bg/);
		});
	});

	it('drop event with no files is a no-op (no error message)', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		const dropEvent = new DragEvent('drop', { bubbles: true });
		Object.defineProperty(dropEvent, 'dataTransfer', { value: { files: [] }, writable: false });
		dropZone.dispatchEvent(dropEvent);

		expect(document.querySelectorAll('.text-red-600')).toHaveLength(0);
	});

	it('rejects multiple invalid files and lists one error message per file', async () => {
		render(DropZone, { props: {} });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const f1 = new File(['x'], 'a.docx', { type: 'application/x-bad' });
		const f2 = new File(['y'], 'b.txt', { type: 'text/plain' });
		Object.defineProperty(input, 'files', { value: [f1, f2], writable: false });
		input.dispatchEvent(new Event('change', { bubbles: true }));

		await vi.waitFor(() => {
			expect(document.querySelectorAll('.text-red-600')).toHaveLength(2);
		});
	});

	it('mixed valid+invalid files raises an error only for the invalid one', async () => {
		render(DropZone, { props: {} });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const f1 = new File(['x'], 'a.docx', { type: 'application/x-bad' });
		const f2 = new File(['%PDF'], 'b.pdf', { type: 'application/pdf' });
		Object.defineProperty(input, 'files', { value: [f1, f2], writable: false });
		input.dispatchEvent(new Event('change', { bubbles: true }));

		await expect.element(page.getByText(/Dateiformat nicht unterstützt/i)).toBeVisible();
	});

	it('Enter handler ignores other keys', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;
		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const clickSpy = vi.spyOn(input, 'click');
		dropZone.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }));
		expect(clickSpy).not.toHaveBeenCalled();
	});

	it('responds to window-level dragenter only when dataTransfer.types includes "Files"', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;

		// Non-files dragenter should not trigger the windowDragging style.
		const evt1 = new DragEvent('dragenter', { bubbles: true });
		Object.defineProperty(evt1, 'dataTransfer', { value: { types: ['text/html'] } });
		window.dispatchEvent(evt1);
		expect(dropZone.className).not.toMatch(/bg-accent-bg/);

		// Files dragenter flips windowDragging=true → drop-zone gains the border-primary style.
		const evt2 = new DragEvent('dragenter', { bubbles: true });
		Object.defineProperty(evt2, 'dataTransfer', { value: { types: ['Files'] } });
		window.dispatchEvent(evt2);
		await vi.waitFor(() => {
			expect(dropZone.className).toMatch(/bg-accent-bg/);
		});

		// Trailing window drop to clean up.
		window.dispatchEvent(new DragEvent('drop', { bubbles: true }));
	});

	it('window-level dragleave without prior dragenter is safe (counter does not go negative)', async () => {
		render(DropZone, { props: {} });

		const dropZone = document.querySelector('div[role="button"]') as HTMLElement;

		// Two consecutive dragleaves on the window without dragenters should leave the drop-zone
		// in its idle (non-highlighted) state.
		window.dispatchEvent(new DragEvent('dragleave', { bubbles: true }));
		window.dispatchEvent(new DragEvent('dragleave', { bubbles: true }));
		expect(dropZone.className).not.toMatch(/bg-accent-bg/);
	});
});
