import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { Search, SearchResult } from './Search';

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};

  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => {
      store[key] = value.toString();
    },
    removeItem: (key: string) => {
      delete store[key];
    },
    clear: () => {
      store = {};
    },
  };
})();

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
});

describe('Search Component', () => {
  const mockOnSearch = vi.fn();
  const mockOnResultSelect = vi.fn();

  const mockSuggestions: SearchResult[] = [
    { id: '1', type: 'track', title: 'Track 1', subtitle: 'Artist 1' },
    { id: '2', type: 'user', title: 'User 1', subtitle: '@user1' },
    { id: '3', type: 'playlist', title: 'Playlist 1', subtitle: '10 tracks' },
  ];

  const mockFetchSuggestions = vi.fn(async (query: string) => {
    return mockSuggestions.filter((s) =>
      s.title.toLowerCase().includes(query.toLowerCase()),
    );
  });

  beforeEach(() => {
    vi.clearAllMocks();
    localStorageMock.clear();
  });

  it('renders search input with placeholder', () => {
    render(<Search onSearch={mockOnSearch} placeholder="Search..." />);

    const input = screen.getByPlaceholderText('Search...');
    expect(input).toBeInTheDocument();
  });

  it('calls onSearch with debounced query', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        debounceDelay={300}
        fetchSuggestions={mockFetchSuggestions}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'test');

    // Attendre le debounce
    await waitFor(
      () => {
        expect(mockOnSearch).toHaveBeenCalledWith('test');
      },
      { timeout: 1000 },
    );
  });

  it('shows suggestions when fetchSuggestions is provided', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(mockFetchSuggestions).toHaveBeenCalled();
      },
      { timeout: 1000 },
    );

    await waitFor(
      () => {
        expect(screen.getByText('Track 1')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );
  });

  it('calls onResultSelect when suggestion is clicked', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        onResultSelect={mockOnResultSelect}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(screen.getByText('Track 1')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );

    const suggestion = screen.getByText('Track 1');
    await user.click(suggestion);

    expect(mockOnResultSelect).toHaveBeenCalledWith(mockSuggestions[0]);
  });

  it('shows history when input is empty and showHistory is true', async () => {
    localStorageMock.setItem(
      'veza_search_history',
      JSON.stringify(['previous search 1', 'previous search 2']),
    );

    render(<Search onSearch={mockOnSearch} showHistory={true} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    input.focus();

    await waitFor(() => {
      expect(screen.getByText('previous search 1')).toBeInTheDocument();
      expect(screen.getByText('previous search 2')).toBeInTheDocument();
    });
  });

  it('adds query to history when searching', async () => {
    const user = userEvent.setup();
    render(<Search onSearch={mockOnSearch} showHistory={true} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'new search');

    await waitFor(
      () => {
        const history = JSON.parse(
          localStorageMock.getItem('veza_search_history') || '[]',
        );
        expect(history).toContain('new search');
      },
      { timeout: 1000 },
    );
  });

  it('limits history items to maxHistoryItems', async () => {
    const user = userEvent.setup();
    render(
      <Search onSearch={mockOnSearch} showHistory={true} maxHistoryItems={3} />,
    );

    // Ajouter plus de 3 items
    const input = screen.getByPlaceholderText('Rechercher...');
    for (let i = 1; i <= 4; i++) {
      await user.clear(input);
      await user.type(input, `search ${i}`);
      await new Promise((resolve) => setTimeout(resolve, 350));
    }

    await waitFor(
      () => {
        const history = JSON.parse(
          localStorageMock.getItem('veza_search_history') || '[]',
        );
        expect(history.length).toBeLessThanOrEqual(3);
      },
      { timeout: 2000 },
    );
  });

  it('clears history when clear button is clicked', async () => {
    localStorageMock.setItem(
      'veza_search_history',
      JSON.stringify(['search 1', 'search 2']),
    );

    const user = userEvent.setup();
    render(<Search onSearch={mockOnSearch} showHistory={true} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    input.focus();

    const clearButton = screen.getByText('Effacer');
    await user.click(clearButton);

    await waitFor(() => {
      const history = localStorageMock.getItem('veza_search_history');
      expect(history).toBeNull();
    });
  });

  it('shows clear button when query has value', async () => {
    const user = userEvent.setup();
    render(<Search onSearch={mockOnSearch} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'test');

    await waitFor(() => {
      const clearButton = screen.getByLabelText('Clear search');
      expect(clearButton).toBeInTheDocument();
    });
  });

  it('clears input when clear button is clicked', async () => {
    const user = userEvent.setup({ delay: null });
    render(<Search onSearch={mockOnSearch} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'test');

    const clearButton = screen.getByLabelText('Clear search');
    await user.click(clearButton);

    expect(input).toHaveValue('');
  });

  it('navigates suggestions with arrow keys', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(screen.getByText('Track 1')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );

    await user.keyboard('{ArrowDown}');
    await user.keyboard('{ArrowDown}');

    // Le deuxième élément devrait être actif
    await waitFor(() => {
      const secondSuggestion = screen.getByText('User 1');
      expect(secondSuggestion.closest('button')).toHaveClass('bg-accent');
    });
  });

  it('selects suggestion with Enter key', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        onResultSelect={mockOnResultSelect}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(screen.getByText('Track 1')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );

    await user.keyboard('{ArrowDown}');
    await user.keyboard('{Enter}');

    await waitFor(() => {
      expect(mockOnResultSelect).toHaveBeenCalledWith(mockSuggestions[0]);
    });
  });

  it('closes dropdown with Escape key', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(screen.getByText('Track 1')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );

    await user.keyboard('{Escape}');

    await waitFor(() => {
      expect(screen.queryByText('Track 1')).not.toBeInTheDocument();
    });
  });

  it('shows loading state when fetching suggestions', async () => {
    const slowFetchSuggestions = vi.fn(
      async (_query: string): Promise<SearchResult[]> => {
        await new Promise((resolve) => setTimeout(resolve, 100));
        return mockSuggestions;
      },
    );

    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={slowFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    await waitFor(
      () => {
        expect(screen.getByText('Recherche en cours...')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );
  });

  it('shows no results message when suggestions are empty', async () => {
    const emptyFetchSuggestions = vi.fn(async () => []);

    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={emptyFetchSuggestions}
        showSuggestions={true}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'nonexistent');

    await waitFor(
      () => {
        expect(screen.getByText('Aucun résultat trouvé')).toBeInTheDocument();
      },
      { timeout: 1000 },
    );
  });

  it('does not show suggestions when showSuggestions is false', async () => {
    const user = userEvent.setup();
    render(
      <Search
        onSearch={mockOnSearch}
        fetchSuggestions={mockFetchSuggestions}
        showSuggestions={false}
      />,
    );

    const input = screen.getByPlaceholderText('Rechercher...');
    await user.type(input, 'track');

    // Attendre un peu pour s'assurer que les suggestions ne s'affichent pas
    await new Promise((resolve) => setTimeout(resolve, 400));

    expect(screen.queryByText('Track 1')).not.toBeInTheDocument();
  });

  it('does not show history when showHistory is false', () => {
    localStorageMock.setItem(
      'veza_search_history',
      JSON.stringify(['previous search']),
    );

    render(<Search onSearch={mockOnSearch} showHistory={false} />);

    const input = screen.getByPlaceholderText('Rechercher...');
    input.focus();

    expect(screen.queryByText('previous search')).not.toBeInTheDocument();
  });

  it('applies custom className', () => {
    const { container } = render(
      <Search onSearch={mockOnSearch} className="custom-class" />,
    );

    const searchContainer = container.firstChild;
    expect(searchContainer).toHaveClass('custom-class');
  });
});
