import React from 'react';
import { waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import renderWithTheme from '../../../testUtils/renderWithTheme';

import MultiSelect from '../MultiSelect';
import Icon from '../../Icon';

describe('rendering', () => {
  it('renders input and option list', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];
    const { getByText, getByPlaceholderText } = renderWithTheme(
      <MultiSelect
        options={options}
        placeholder="Select items"
        onChange={jest.fn()}
      />
    );

    await waitFor(() => {
      expect(getByPlaceholderText('Select items')).toBeInTheDocument();
      expect(getByText('Item 1')).not.toBeVisible();
      expect(getByText('Item 2')).not.toBeVisible();
    });

    fireEvent.click(getByPlaceholderText('Select items'));

    await waitFor(() => {
      expect(getByText('Item 1')).toBeVisible();
      expect(getByText('Item 2')).toBeVisible();
    });
  });

  it('renders input and option list with category names', async () => {
    const options = [
      {
        category: 'Teams',
        options: [{ value: 'team-1', text: 'Team 1', helpText: '5 members' }],
      },
      {
        category: 'Locations',
        options: [{ value: 'location-1', text: 'Location 1' }],
      },
      {
        category: 'Individual',
        options: [{ value: 'person-1', text: 'Person 1' }],
      },
    ];
    const { getByText, getByPlaceholderText } = renderWithTheme(
      <MultiSelect
        options={options}
        placeholder="Select items"
        onChange={jest.fn()}
      />
    );

    await waitFor(() => {
      expect(getByPlaceholderText('Select items')).toBeInTheDocument();
      expect(getByText('Teams')).not.toBeVisible();
      expect(getByText('Locations')).not.toBeVisible();
      expect(getByText('Individual')).not.toBeVisible();
      expect(getByText('Team 1')).not.toBeVisible();
      expect(getByText('Location 1')).not.toBeVisible();
      expect(getByText('Person 1')).not.toBeVisible();
      expect(getByText('5 members')).not.toBeVisible();
    });

    fireEvent.click(getByPlaceholderText('Select items'));

    await waitFor(() => {
      expect(getByText('Teams')).toBeVisible();
      expect(getByText('Locations')).toBeVisible();
      expect(getByText('Individual')).toBeVisible();
      expect(getByText('Team 1')).toBeVisible();
      expect(getByText('Location 1')).toBeVisible();
      expect(getByText('Person 1')).toBeVisible();
      expect(getByText('5 members')).toBeVisible();
    });
  });

  it('renders custom option renderer with additional option props', async () => {
    const options = [
      { value: 'item-1', text: 'Item 1', icon: 'add-person' } as const,
      { value: 'item-2', text: 'Item 2', icon: 'alignment' } as const,
    ];

    const { getByText, getByPlaceholderText } = renderWithTheme(
      <MultiSelect
        options={options}
        optionRenderer={({ option: { text, icon }, index }) => (
          <>
            <Icon icon={icon} />
            <span style={{ margin: 0, padding: 0 }}>{`${
              index + 1
            }: ${text}`}</span>
          </>
        )}
        onChange={jest.fn()}
        placeholder="Select an item"
      />
    );

    await waitFor(() => {
      expect(getByPlaceholderText('Select an item')).toBeInTheDocument();
    });

    fireEvent.click(getByPlaceholderText('Select an item'));

    await waitFor(() => {
      expect(getByText('1: Item 1')).toBeVisible();
      expect(getByText('2: Item 2')).toBeVisible();
    });

    expect(
      getByText('1: Item 1').parentElement?.querySelector('i')
    ).toHaveClass('hero-icon-add-person');
    expect(
      getByText('2: Item 2').parentElement?.querySelector('i')
    ).toHaveClass('hero-icon-alignment');
  });
});

describe('interaction', () => {
  it('allows to select multiple items', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];
    const onChange = jest.fn();
    const { getByText, getByRole } = renderWithTheme(
      <MultiSelect value={[1]} options={options} onChange={onChange} />
    );

    fireEvent.click(getByRole('combobox'));
    fireEvent.click(getByText('Item 2'));

    await waitFor(() => {
      expect(onChange).toHaveBeenCalledTimes(1);
      expect(onChange).toHaveBeenCalledWith([1, 2]);
    });
  });

  it('allows to select multiple items from grouped options', async () => {
    const options = [
      {
        category: 'Teams',
        options: [
          { value: 'team-1', text: 'Team 1', helpText: '5 members' },
          { value: 'team-2', text: 'Team 2', helpText: '3 members' },
        ],
      },
      {
        category: 'Locations',
        options: [{ value: 'location-1', text: 'Location 1' }],
      },
      {
        category: 'Individual',
        options: [{ value: 'person-1', text: 'Person 1' }],
      },
    ];
    const onChange = jest.fn();
    const { getByText, getByRole } = renderWithTheme(
      <MultiSelect value={[1]} options={options} onChange={onChange} />
    );

    fireEvent.click(getByRole('combobox'));
    fireEvent.click(getByText('Person 1'));

    await waitFor(() => {
      expect(onChange).toHaveBeenCalledTimes(1);
      expect(onChange).toHaveBeenCalledWith(['person-1']);
    });
  });

  it('allows to search for an item', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];
    const onChange = jest.fn();
    const onQueryChange = jest.fn();
    const { getByText, getByPlaceholderText, queryByText } = renderWithTheme(
      <MultiSelect
        options={options}
        placeholder="Select items"
        onChange={onChange}
        query="1"
        onQueryChange={onQueryChange}
      />
    );

    await waitFor(() => {
      expect(getByText('Item 1')).toBeInTheDocument();
      expect(queryByText('Item 2')).not.toBeInTheDocument();
    });

    fireEvent.change(getByPlaceholderText('Select items'), {
      target: { value: '2' },
    });

    await waitFor(() => {
      expect(onQueryChange).toHaveBeenCalledTimes(1);
      expect(onQueryChange).toHaveBeenCalledWith('2');
    });
  });

  it('allows to create new item', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];
    const onChange = jest.fn();
    const onQueryChange = jest.fn();
    const onCreateNewOption = jest.fn();
    const { getByText, queryByText } = renderWithTheme(
      <MultiSelect
        options={options}
        onChange={onChange}
        query="New Item"
        onQueryChange={onQueryChange}
        onCreateNewOption={onCreateNewOption}
      />
    );

    await waitFor(() => {
      expect(queryByText('Item 1')).not.toBeInTheDocument();
      expect(queryByText('Item 2')).not.toBeInTheDocument();
      expect(getByText('New Item')).toBeInTheDocument();
    });

    fireEvent.click(getByText('New Item'));

    await waitFor(() => {
      expect(onCreateNewOption).toHaveBeenCalledTimes(1);
      expect(onCreateNewOption).toHaveBeenCalledWith('New Item');
    });
  });

  it('allows to call callback when scrolling to bottom of the list', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
      { value: 3, text: 'Item 3' },
      { value: 4, text: 'Item 4' },
      { value: 5, text: 'Item 5' },
      { value: 6, text: 'Item 6' },
      { value: 7, text: 'Item 7' },
      { value: 8, text: 'Item 8' },
      { value: 9, text: 'Item 9' },
      { value: 10, text: 'Item 10' },
    ];
    const onChange = jest.fn();
    const onScrollListToBottom = jest.fn();
    const { getByText, getByRole, getByPlaceholderText } = renderWithTheme(
      <MultiSelect
        options={options}
        placeholder="Select items"
        onChange={onChange}
        onScrollListToBottom={onScrollListToBottom}
      />
    );

    fireEvent.click(getByPlaceholderText('Select items'));

    await waitFor(() => {
      expect(getByText('Item 10')).toBeVisible();
      expect(onScrollListToBottom).not.toHaveBeenCalled();
    });

    fireEvent.scroll(getByRole('listbox'), { y: 200 });

    await waitFor(() => {
      expect(onScrollListToBottom).toHaveBeenCalledTimes(1);
    });
  });

  it('calls onBlur and onFocus when they are not undefined', async () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];

    const onChange = jest.fn();
    const onBlur = jest.fn();
    const onFocus = jest.fn();

    const { getByPlaceholderText } = renderWithTheme(
      <MultiSelect
        options={options}
        placeholder="Select items"
        onChange={onChange}
        onBlur={onBlur}
        onFocus={onFocus}
      />
    );

    const input = getByPlaceholderText('Select items');

    // click on select input
    userEvent.click(input);

    await waitFor(() => {
      expect(onFocus).toHaveBeenCalledTimes(1);
      expect(onBlur).not.toHaveBeenCalled();
    });

    // click outside
    userEvent.click(document.body);
    expect(onBlur).toHaveBeenCalledTimes(1);
  });

  it('shows noResult text even when loading', async () => {
    const onChange = jest.fn();

    const { getByPlaceholderText, getByText } = renderWithTheme(
      <MultiSelect
        options={[]}
        placeholder="Select items"
        onChange={onChange}
        loading
        noResults="Not found"
      />
    );

    const input = getByPlaceholderText('Select items');

    // click on select input
    userEvent.click(input);

    await waitFor(() => {
      expect(getByText('Not found')).toBeInTheDocument();
    });
  });
});
