import { fireEvent, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import Button from '..';
import messages from '../../../i18n/commonMessages/Button.messages';
import { render } from '../../../test-utils';
import { Freeze } from '@transferwise/icons';

beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: jest.fn().mockImplementation((query: string) => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: jest.fn(), // deprecated
      removeListener: jest.fn(), // deprecated
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
      dispatchEvent: jest.fn(),
    })),
  });
});

describe('Button', () => {
  const defaultProps = {
    children: 'Click me',
  };

  const renderButton = (
    props?: Partial<typeof defaultProps>,
    locale = 'en',
    localeMessages = messages,
  ) => {
    return render(
      <Button {...defaultProps} {...props} />,
      // @ts-expect-error: props be missing properties from type
      { locale, messages: localeMessages },
    );
  };

  it('renders a button by default', () => {
    renderButton();
    expect(screen.getByRole('button')).toBeInTheDocument();
    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('applies the correct classes based on props', () => {
    const props = {
      ...defaultProps,
      className: 'custom-class',
    };

    renderButton(props);

    const button = screen.getByRole('button');
    expect(button).toHaveClass('wds-Button');
    expect(button).toHaveClass('custom-class');
  });

  it('disables the button when disabled is true', () => {
    const props = {
      ...defaultProps,
      disabled: true,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    expect(button).toHaveClass('wds-Button--disabled');
    expect(button).toBeDisabled();
  });

  it('calls onClick when button is clicked', async () => {
    const handleClick = jest.fn();
    const props = {
      ...defaultProps,
      onClick: handleClick,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    await userEvent.click(button);
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('calls onFocus when button is focused', () => {
    const handleFocus = jest.fn();
    const props = {
      ...defaultProps,
      onFocus: handleFocus,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    fireEvent.focus(button);
    expect(handleFocus).toHaveBeenCalledTimes(1);
  });

  it('calls onKeyDown when a key is pressed', async () => {
    const handleKeyDown = jest.fn();
    const props = {
      ...defaultProps,
      onKeyDown: handleKeyDown,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    await userEvent.type(button, '{enter}');
    expect(handleKeyDown).toHaveBeenCalledTimes(1);
  });

  it('calls onBlur when button loses focus', () => {
    const handleBlur = jest.fn();
    const props = {
      ...defaultProps,
      onBlur: handleBlur,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    fireEvent.blur(button);
    expect(handleBlur).toHaveBeenCalledTimes(1);
  });

  it('calls onMouseEnter when mouse enters', async () => {
    const handleMouseEnter = jest.fn();
    const props = {
      ...defaultProps,
      onMouseEnter: handleMouseEnter,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    await userEvent.hover(button);
    expect(handleMouseEnter).toHaveBeenCalledTimes(1);
  });

  it('calls onMouseLeave when mouse leaves', async () => {
    const handleMouseLeave = jest.fn();
    const props = {
      ...defaultProps,
      onMouseLeave: handleMouseLeave,
    };

    renderButton(props);

    const button = screen.getByRole('button');
    await userEvent.unhover(button);
    expect(handleMouseLeave).toHaveBeenCalledTimes(1);
  });

  it('sets data-testid attribute', () => {
    const props = {
      ...defaultProps,
      testId: 'custom-id',
    };

    renderButton(props);

    const button = screen.getByTestId('custom-id');
    expect(button).toBeInTheDocument();
  });

  it('sets the type attribute to submit', () => {
    const props = {
      ...defaultProps,
      type: 'submit',
    };

    renderButton(props);

    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('type', 'submit');
  });

  it('sets the type attribute to reset', () => {
    const props = {
      ...defaultProps,
      type: 'reset',
    };

    renderButton(props);

    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('type', 'reset');
  });

  it('sets the type attribute to button by default', () => {
    renderButton();
    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('type', 'button');
  });

  it('displays the aria-label in Spanish when loading', () => {
    const props = {
      ...defaultProps,
      loading: true,
    };

    const spanishMessages = {
      ...messages,
      'neptune.Button.loadingAriaLabel': 'cargando',
    };

    renderButton(props, 'es', spanishMessages);

    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('aria-label', 'cargando');
  });

  it('displays with one avatar', () => {
    const props = {
      ...defaultProps,
      avatars: [{ asset: <Freeze /> }],
    };

    renderButton(props);

    const avatar = screen.getByTestId('freeze-icon');
    expect(avatar).toBeInTheDocument();
  });

  it('displays with one avatar and no left icon', () => {
    const props = {
      ...defaultProps,
      avatars: [{ asset: <Freeze /> }],
      iconLeft: 'ArrowLeft',
    };

    renderButton(props);

    const avatar = screen.getByTestId('freeze-icon');

    expect(avatar).toBeInTheDocument();
    expect(screen.queryByTestId('arrow-left-icon')).not.toBeInTheDocument();
  });

  it('displays with two avatars', () => {
    const props = {
      ...defaultProps,
      avatars: [{ asset: <Freeze /> }, { asset: <Freeze /> }],
    };

    renderButton(props);

    const avatars = screen.getAllByTestId('freeze-icon');
    avatars.forEach((avatar) => {
      expect(avatar).toBeInTheDocument();
    });
  });

  it('displays with avatar and icon', () => {
    const props = {
      ...defaultProps,
      avatars: [{ asset: <Freeze /> }],
      iconRight: 'ArrowRight',
    };

    renderButton(props);

    const iconRight = screen.getByTestId('arrow-right-icon');
    const avatar = screen.getByTestId('freeze-icon');

    expect(avatar).toBeInTheDocument();
    expect(iconRight).toHaveClass('wds-Button-icon');
    expect(iconRight).toHaveClass('wds-Button-icon--right');
  });

  it('displays with one icon', () => {
    const props = {
      ...defaultProps,
      iconRight: 'ArrowRight',
    };

    renderButton(props);

    const iconRight = screen.getByTestId('arrow-right-icon');
    expect(iconRight).toHaveClass('wds-Button-icon');
    expect(iconRight).toHaveClass('wds-Button-icon--right');
  });

  it('displays with two icons', () => {
    const props = {
      ...defaultProps,
      iconRight: 'ArrowRight',
      iconLeft: 'Freeze',
    };

    renderButton(props);

    const iconRight = screen.getByTestId('arrow-right-icon');
    const iconLeft = screen.getByTestId('freeze-icon');

    expect(iconRight).toHaveClass('wds-Button-icon');
    expect(iconRight).toHaveClass('wds-Button-icon--right');
    expect(iconLeft).toHaveClass('wds-Button-icon');
    expect(iconLeft).toHaveClass('wds-Button-icon--left');
  });

  it('displays as icon only', () => {
    const props = {
      ...defaultProps,
      iconOnly: 'Freeze',
    };

    renderButton(props);

    const button = screen.getByRole('button');
    const iconOnly = screen.getByTestId('freeze-icon');

    expect(button).toHaveClass('wds-Button--iconOnly');
    expect(iconOnly).toHaveClass('wds-Button-icon');
  });
});
