import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';

vi.mock('$lib/api/config', () => ({
	getApiBaseUrl: () => 'http://test.classeo.local:18000/api'
}));

const mockAuthenticatedFetch = vi.fn();
vi.mock('$lib/auth', () => ({
	authenticatedFetch: (...args: unknown[]) => mockAuthenticatedFetch(...args)
}));

import ParentSearchInput from '$lib/components/molecules/ParentSearchInput/ParentSearchInput.svelte';

function mockApiResponse(members: Record<string, string>[]) {
	mockAuthenticatedFetch.mockResolvedValueOnce({
		ok: true,
		json: () => Promise.resolve({ member: members })
	});
}

const PARENT_DUPONT = { id: 'uuid-1', firstName: 'Jean', lastName: 'Dupont', email: 'jean@test.com' };
const PARENT_MARTIN = { id: 'uuid-2', firstName: 'Marie', lastName: 'Martin', email: 'marie@test.com' };
const PARENT_NO_NAME = { id: 'uuid-3', firstName: '', lastName: '', email: 'noname@test.com' };

describe('ParentSearchInput', () => {
	beforeEach(() => {
		vi.useFakeTimers();
		vi.clearAllMocks();
	});

	afterEach(() => {
		vi.useRealTimers();
	});

	it('renders with combobox role', () => {
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		expect(screen.getByRole('combobox')).toBeTruthy();
	});

	it('renders with default placeholder', () => {
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		expect(screen.getByPlaceholderText('Rechercher un parent par nom ou email...')).toBeTruthy();
	});

	it('renders with custom placeholder', () => {
		render(ParentSearchInput, {
			props: { onSelect: vi.fn(), placeholder: 'Chercher...' }
		});

		expect(screen.getByPlaceholderText('Chercher...')).toBeTruthy();
	});

	it('does not search with less than 2 characters', async () => {
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'a' } });
		vi.advanceTimersByTime(300);

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

	it('debounces and searches after 300ms with 2+ characters', async () => {
		mockApiResponse([PARENT_DUPONT]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });

		// Not called immediately
		expect(mockAuthenticatedFetch).not.toHaveBeenCalled();

		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		expect(mockAuthenticatedFetch).toHaveBeenCalledOnce();
		const url = mockAuthenticatedFetch.mock.calls[0]![0] as string;
		expect(url).toContain('role=ROLE_PARENT');
		expect(url).toContain('search=dup');
		expect(url).toContain('itemsPerPage=10');
	});

	it('displays results in dropdown', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		const options = screen.getAllByRole('option');
		expect(options).toHaveLength(2);
		expect(options[0]!.textContent).toContain('Jean Dupont');
		expect(options[0]!.textContent).toContain('jean@test.com');
		expect(options[1]!.textContent).toContain('Marie Martin');
	});

	it('shows "Aucun parent trouvé" when no results', async () => {
		mockApiResponse([]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'xyz' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		expect(screen.getByText('Aucun parent trouvé')).toBeTruthy();
	});

	it('calls onSelect when clicking a result', async () => {
		mockApiResponse([PARENT_DUPONT]);
		const onSelect = vi.fn();
		render(ParentSearchInput, { props: { onSelect } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		const option = screen.getAllByRole('option')[0]!;
		await fireEvent.click(option);

		expect(onSelect).toHaveBeenCalledOnce();
		expect(onSelect).toHaveBeenCalledWith(PARENT_DUPONT);
	});

	it('closes dropdown after selection', async () => {
		mockApiResponse([PARENT_DUPONT]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		await fireEvent.click(screen.getAllByRole('option')[0]!);

		expect(screen.queryByRole('listbox')).toBeNull();
	});

	it('updates input value with selected parent name', async () => {
		mockApiResponse([PARENT_DUPONT]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox') as HTMLInputElement;
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		await fireEvent.click(screen.getAllByRole('option')[0]!);

		expect(input.value).toBe('Jean Dupont');
	});

	it('shows email as fallback when names are empty', async () => {
		mockApiResponse([PARENT_NO_NAME]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'noname' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		const option = screen.getAllByRole('option')[0]!;
		expect(option.textContent).toContain('noname@test.com');
	});

	it('sets input to email when selecting parent without name', async () => {
		mockApiResponse([PARENT_NO_NAME]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox') as HTMLInputElement;
		await fireEvent.input(input, { target: { value: 'noname' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		await fireEvent.click(screen.getAllByRole('option')[0]!);

		expect(input.value).toBe('noname@test.com');
	});

	it('closes dropdown on Escape', async () => {
		mockApiResponse([PARENT_DUPONT]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		expect(screen.getByRole('listbox')).toBeTruthy();

		await fireEvent.keyDown(input, { key: 'Escape' });

		expect(screen.queryByRole('listbox')).toBeNull();
	});

	it('navigates options with ArrowDown', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		await fireEvent.keyDown(input, { key: 'ArrowDown' });

		const options = screen.getAllByRole('option');
		expect(options[0]!.getAttribute('aria-selected')).toBe('true');
		expect(options[1]!.getAttribute('aria-selected')).toBe('false');

		await fireEvent.keyDown(input, { key: 'ArrowDown' });

		expect(options[0]!.getAttribute('aria-selected')).toBe('false');
		expect(options[1]!.getAttribute('aria-selected')).toBe('true');
	});

	it('navigates options with ArrowUp', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		// ArrowUp from -1 wraps to last item
		await fireEvent.keyDown(input, { key: 'ArrowUp' });

		const options = screen.getAllByRole('option');
		expect(options[1]!.getAttribute('aria-selected')).toBe('true');

		await fireEvent.keyDown(input, { key: 'ArrowUp' });

		expect(options[0]!.getAttribute('aria-selected')).toBe('true');
		expect(options[1]!.getAttribute('aria-selected')).toBe('false');
	});

	it('selects option with Enter key', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		const onSelect = vi.fn();
		render(ParentSearchInput, { props: { onSelect } });

		const input = screen.getByRole('combobox') as HTMLInputElement;
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		// Navigate to first option and press Enter
		await fireEvent.keyDown(input, { key: 'ArrowDown' });
		await fireEvent.keyDown(input, { key: 'Enter' });

		expect(onSelect).toHaveBeenCalledOnce();
		expect(onSelect).toHaveBeenCalledWith(PARENT_DUPONT);
		expect(input.value).toBe('Jean Dupont');
		expect(screen.queryByRole('listbox')).toBeNull();
	});

	it('wraps ArrowDown from last to first option', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		// Navigate to last, then one more wraps to first
		await fireEvent.keyDown(input, { key: 'ArrowDown' }); // index 0
		await fireEvent.keyDown(input, { key: 'ArrowDown' }); // index 1
		await fireEvent.keyDown(input, { key: 'ArrowDown' }); // wraps to 0

		const options = screen.getAllByRole('option');
		expect(options[0]!.getAttribute('aria-selected')).toBe('true');
		expect(options[1]!.getAttribute('aria-selected')).toBe('false');
	});

	it('does not select with Enter when no option is active', async () => {
		mockApiResponse([PARENT_DUPONT]);
		const onSelect = vi.fn();
		render(ParentSearchInput, { props: { onSelect } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		// Press Enter without navigating (activeIndex = -1)
		await fireEvent.keyDown(input, { key: 'Enter' });

		expect(onSelect).not.toHaveBeenCalled();
		expect(screen.getByRole('listbox')).toBeTruthy();
	});

	it('filters out excluded IDs from results', async () => {
		mockApiResponse([PARENT_DUPONT, PARENT_MARTIN]);
		render(ParentSearchInput, {
			props: { onSelect: vi.fn(), excludeIds: ['uuid-1'] }
		});

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		const options = screen.getAllByRole('option');
		expect(options).toHaveLength(1);
		expect(options[0]!.textContent).toContain('Marie Martin');
	});

	it('calls onClear when user retypes after selection', async () => {
		mockApiResponse([PARENT_DUPONT]);
		const onClear = vi.fn();
		const onSelect = vi.fn();
		render(ParentSearchInput, { props: { onSelect, onClear } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		// Select a parent
		await fireEvent.click(screen.getAllByRole('option')[0]!);
		expect(onSelect).toHaveBeenCalledOnce();
		expect(onClear).not.toHaveBeenCalled();

		// Retype in the input — should trigger onClear
		mockApiResponse([PARENT_DUPONT]);
		await fireEvent.input(input, { target: { value: 'mar' } });
		expect(onClear).toHaveBeenCalledOnce();
	});

	it('does not call onClear when typing without prior selection', async () => {
		const onClear = vi.fn();
		render(ParentSearchInput, { props: { onSelect: vi.fn(), onClear } });

		const input = screen.getByRole('combobox');
		await fireEvent.input(input, { target: { value: 'test' } });

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

	it('sets aria-expanded correctly', async () => {
		mockApiResponse([PARENT_DUPONT]);
		render(ParentSearchInput, { props: { onSelect: vi.fn() } });

		const input = screen.getByRole('combobox');
		expect(input.getAttribute('aria-expanded')).toBe('false');

		await fireEvent.input(input, { target: { value: 'dup' } });
		vi.advanceTimersByTime(300);
		await vi.runAllTimersAsync();

		expect(input.getAttribute('aria-expanded')).toBe('true');
	});
});
