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

afterEach(cleanup);

describe('MentionEditor', () => {
	let fetchSpy: ReturnType<typeof vi.spyOn>;

	beforeEach(() => {
		fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (url: RequestInfo | URL) => {
			const u = url.toString();
			if (u.includes('/api/users/search')) {
				return new Response(
					JSON.stringify([
						{ id: 'u1', firstName: 'Anna', lastName: 'Schmidt' },
						{ id: 'u2', firstName: 'Bertha', lastName: 'Müller' }
					]),
					{ status: 200, headers: { 'Content-Type': 'application/json' } }
				);
			}
			return new Response('not found', { status: 404 });
		});
	});

	afterEach(() => {
		fetchSpy?.mockRestore();
	});

	function fireAtMention(ta: HTMLTextAreaElement, text: string) {
		ta.focus();
		ta.value = text;
		ta.selectionStart = text.length;
		ta.selectionEnd = text.length;
		ta.dispatchEvent(new Event('input', { bubbles: true }));
	}

	it('renders the textarea with the placeholder', async () => {
		render(MentionEditor, {
			props: {
				value: '',
				mentionCandidates: [],
				placeholder: 'Schreibe etwas…'
			}
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		expect(ta).not.toBeNull();
		expect(ta.placeholder).toBe('Schreibe etwas…');
	});

	it('honours the rows prop', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [], rows: 7 }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		expect(ta.rows).toBe(7);
	});

	it('disables the textarea when disabled is true', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [], disabled: true }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		expect(ta.disabled).toBe(true);
	});

	it('does not show the popup initially', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const popup = document.querySelector('[role="listbox"]');
		expect(popup).toBeNull();
	});

	it('opens the popup when typing @ followed by a query', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, 'Hi @An');

		// Debounce fires (200ms), fetch resolves, popup opens — vi.waitFor polls until ready.
		await vi.waitFor(() => {
			expect(document.querySelector('[role="listbox"]')).not.toBeNull();
		});
	});

	it('renders the empty-popup label when fetch returns no results', async () => {
		fetchSpy.mockImplementationOnce(
			async () =>
				new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } })
		);

		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@Zzzz');

		await expect.element(page.getByText(/keine nutzer gefunden/i)).toBeVisible();
	});

	it('clears results when fetch is not OK', async () => {
		fetchSpy.mockImplementationOnce(async () => new Response('error', { status: 500 }));

		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@Anna');

		await expect.element(page.getByText(/keine nutzer gefunden/i)).toBeVisible();
	});

	it('calls onsubmit when Enter is pressed without a popup open', async () => {
		const onsubmit = vi.fn();
		render(MentionEditor, {
			props: { value: 'Hello', mentionCandidates: [], onsubmit }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		ta.focus();
		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));

		expect(onsubmit).toHaveBeenCalledOnce();
	});

	it('does not call onsubmit when Shift+Enter is pressed', async () => {
		const onsubmit = vi.fn();
		render(MentionEditor, {
			props: { value: 'Hello', mentionCandidates: [], onsubmit }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		ta.focus();
		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', shiftKey: true, bubbles: true }));

		expect(onsubmit).not.toHaveBeenCalled();
	});

	it('closes the popup when Escape is pressed', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@An');
		await vi.waitFor(() => {
			expect(document.querySelector('[role="listbox"]')).not.toBeNull();
		});

		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
		await vi.waitFor(() => {
			expect(document.querySelector('[role="listbox"]')).toBeNull();
		});
	});

	it('navigates results with ArrowDown and ArrowUp', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@An');
		await vi.waitFor(() => {
			expect(document.querySelectorAll('[role="option"]').length).toBeGreaterThan(1);
		});

		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
		await vi.waitFor(() => {
			const opts = document.querySelectorAll('[role="option"]');
			expect(opts[1]?.getAttribute('aria-selected')).toBe('true');
		});

		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
		await vi.waitFor(() => {
			const opts = document.querySelectorAll('[role="option"]');
			expect(opts[0]?.getAttribute('aria-selected')).toBe('true');
		});
	});

	it('keeps the popup open when Enter is hit and no results are present', async () => {
		fetchSpy.mockImplementationOnce(
			async () =>
				new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } })
		);
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@Zz');
		await expect.element(page.getByText(/keine nutzer gefunden/i)).toBeVisible();

		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
		// Enter with no results does not close the popup and does not submit.
		expect(document.querySelector('[role="listbox"]')).not.toBeNull();
	});

	it('selects a user via mousedown click and closes the popup', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@An');
		await vi.waitFor(() => {
			expect(document.querySelectorAll('[role="option"]').length).toBeGreaterThan(0);
		});

		const firstOption = document.querySelector('[role="option"]') as HTMLElement;
		firstOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));

		await vi.waitFor(() => {
			expect(document.querySelector('[role="listbox"]')).toBeNull();
		});
	});

	it('selects via Enter when results are present and closes the popup', async () => {
		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@An');
		await vi.waitFor(() => {
			expect(document.querySelectorAll('[role="option"]').length).toBeGreaterThan(0);
		});

		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
		ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));

		await vi.waitFor(() => {
			expect(document.querySelector('[role="listbox"]')).toBeNull();
		});
	});

	it('handles fetch network throw gracefully (empty popup label)', async () => {
		fetchSpy.mockImplementationOnce(async () => {
			throw new Error('network down');
		});

		render(MentionEditor, {
			props: { value: '', mentionCandidates: [] }
		});

		const ta = document.querySelector('textarea') as HTMLTextAreaElement;
		fireAtMention(ta, '@An');

		await expect.element(page.getByText(/keine nutzer gefunden/i)).toBeVisible();
	});
});
