import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { Tags } from './Tags';

function selectFirstOption(container: HTMLElement) {
  const input = container.querySelector('[role="combobox"]') as HTMLElement;
  fireEvent.mouseDown(input);
  const firstOption = document.querySelector('[role="option"]') as HTMLElement;
  if (firstOption) {
    fireEvent.click(firstOption);
  }
}

describe('Tags', () => {
  it('renders the component without crashing', () => {
    const { container } = render(<Tags name={'test-name'} />);

    expect(container).toBeTruthy();
  });

  it('displays a label', () => {
    const mockLabel = 'mockLabel';
    const { container } = render(<Tags name={'test-name'} label={mockLabel} />);

    const label = container.querySelector('label');

    expect(label?.textContent).toBe(mockLabel);
  });

  it('displays a tag for each currently selected tag', () => {
    const mockValue: string[] = ['1', '2', '3'];
    const { container } = render(<Tags name={'test-name'} value={mockValue} />);

    const tags = Array.from(container.querySelectorAll('span > span')).map(
      (node) => node.textContent,
    );

    expect(tags).toHaveLength(3);
    expect(tags).toEqual(mockValue);
  });

  it(`each Button component should have type of 'button'`, () => {
    const mockValue = ['1', '2', '3'];
    const { container } = render(<Tags name={'test-name'} value={mockValue} />);

    const buttons = container.querySelectorAll('button');

    buttons.forEach((button) => {
      expect(button.type).toBe('button');
    });
  });

  it('raises onChange when selecting a tag along with the new value', () => {
    const changeSpy = vi.fn();
    const mockValue: string[] = ['1'];
    const { container } = render(
      <Tags
        name={'test-name'}
        onChange={changeSpy}
        value={mockValue}
        tagsOptions={['1', '2']}
      />,
    );

    selectFirstOption(container);

    expect(changeSpy).toHaveBeenCalledTimes(1);
    expect(changeSpy).toHaveBeenCalledWith(
      expect.objectContaining({
        currentTarget: expect.objectContaining({ value: ['1', '2'] }),
      }),
    );
  });

  it('raises onChange when removing a tag along with the new value', () => {
    const changeSpy = vi.fn();
    const mockValue: string[] = ['1'];
    const { container } = render(
      <Tags
        name={'test-name'}
        onChange={changeSpy}
        value={mockValue}
        tagsOptions={['1', '2']}
      />,
    );

    const x = container.querySelector(
      '[data-test-id="tags-delete"]',
    ) as HTMLElement;

    fireEvent.click(x);

    expect(changeSpy).toHaveBeenCalledTimes(1);
    expect(changeSpy).toHaveBeenCalledWith(
      expect.objectContaining({
        currentTarget: expect.objectContaining({ value: [] }),
      }),
    );
  });

  it('shows select element when current selected tags and optional tags are the not same length', () => {
    const { container } = render(
      <Tags name={'test-name'} value={['1']} tagsOptions={['1', '2']} />,
    );

    const select = container.querySelector('[role="combobox"]');

    expect(select).toBeInTheDocument();
  });

  it('hides select element when current selected tags and optional tags are the same length', () => {
    const { container, rerender } = render(
      <Tags name={'test-name'} value={['1']} tagsOptions={['1', '2']} />,
    );

    selectFirstOption(container);

    rerender(
      <Tags name={'test-name'} value={['1', '2']} tagsOptions={['1', '2']} />,
    );

    const select = container.querySelector('[role="combobox"]');

    expect(select).not.toBeInTheDocument();
  });

  it('raises blur and focus events', () => {
    const blurSpy = vi.fn();
    const focusSpy = vi.fn();
    const { container } = render(
      <Tags
        name={'test-name'}
        onBlur={blurSpy}
        onFocus={focusSpy}
        tagsOptions={['1']}
      />,
    );

    const select = container.querySelector('[role="combobox"]') as HTMLElement;

    fireEvent.blur(select);
    expect(blurSpy).toHaveBeenCalledTimes(1);

    fireEvent.focus(select);
    expect(focusSpy).toHaveBeenCalledTimes(1);
  });

  it('uses the displayValue for existing tags when tagOptions are objects', () => {
    const { container } = render(
      <Tags
        name={'test-name'}
        value={['1', '3']}
        tagsOptions={[
          { key: 'Test1', value: '1' },
          { key: 'Test2', value: '2' },
          { key: 'Test3', value: '3' },
          { key: 'Test4', value: '4' },
        ]}
        displayKey="key"
        valueKey="value"
      />,
    );

    const tags = Array.from(container.querySelectorAll('span > span')).map(
      (node) => node.textContent,
    );

    expect(tags).toHaveLength(2);

    expect(tags).toEqual(['Test1', 'Test3']);
  });

  it('uses the displayValue for available tags when tagOptions are objects', () => {
    const { container } = render(
      <Tags
        name={'test-name'}
        value={['1', '3']}
        tagsOptions={[
          { key: 'Test1', value: '1' },
          { key: 'Test2', value: '2' },
          { key: 'Test3', value: '3' },
          { key: 'Test4', value: '4' },
        ]}
        displayKey="key"
        valueKey="value"
      />,
    );

    const input = container.querySelector('[role="combobox"]') as HTMLElement;
    fireEvent.mouseDown(input);

    const popper = document.querySelector('[role="tooltip"]');

    const options = popper?.querySelectorAll('[role="option"]');

    expect(options).toHaveLength(2);

    expect([options?.[0].innerHTML, options?.[1].innerHTML]).toEqual([
      'Test2',
      'Test4',
    ]);
  });

  it('applies error styling and renders error message when an error is passed', () => {
    const mockErrorMessage = 'test-error-message';
    const { container } = render(
      <Tags name={'test-name'} error={mockErrorMessage} tagsOptions={['1']} />,
    );

    const errorMsg = container.querySelector('small');
    // The hasError class is applied to the Select component's container, not the combobox itself
    const selectContainer = container.querySelector('.hasError');

    expect(errorMsg?.textContent).toBe(mockErrorMessage);
    expect(selectContainer).toBeInTheDocument();
  });
});
