import { createRef } from 'react';

import { ControlType, Type, Priority, Size } from '../common';
import { render, screen, userEvent } from '../test-utils';

import Button from '.';
import messages from './Button.messages';

const { ACCENT, POSITIVE, NEGATIVE } = ControlType;
const { PAY, LINK, DANGER } = Type;
const { PRIMARY, SECONDARY, TERTIARY } = Priority;
const { SMALL, MEDIUM, LARGE } = Size;

describe('Button', () => {
  // eslint-disable-next-line no-console
  const originalWarn = console.warn;
  let mockedWarn;

  const props = {
    onClick: jest.fn(),
    onFocus: jest.fn(),
    onBlur: jest.fn(),
    children: 'Send money',
  };

  beforeAll(() => {
    mockedWarn = jest.fn().mockImplementation((args) => {
      if (typeof args !== 'string' || !args.startsWith('Button has deprecated the')) {
        originalWarn(args);
      }
    });
    // eslint-disable-next-line no-console
    console.warn = mockedWarn;
  });

  beforeEach(jest.clearAllMocks);

  afterAll(() => {
    // eslint-disable-next-line no-console
    console.warn = originalWarn;
  });

  describe('by default', () => {
    it('renders the text', () => {
      render(<Button {...props} />);
      expect(screen.getByText(props.children)).toBeInTheDocument();
    });

    it('set `ref` to be true on Button', () => {
      const reference = createRef();

      expect(reference.current).toBeFalsy();
      render(<Button ref={reference}>Click me!</Button>);
      expect(reference.current).toBeTruthy();
    });

    it('is not disabled', () => {
      render(<Button {...props} />);
      expect(screen.getByRole('button')).toBeEnabled();
    });

    it('renders a medium button of type accent and priority primary', () => {
      expect(render(<Button {...props} />).container).toMatchSnapshot();
    });

    it('renders an anchor tag with button styles of type accent and priority primary', () => {
      expect(render(<Button {...props} as="a" href="#" />).container).toMatchSnapshot();
    });
  });

  describe('button attributes', () => {
    it('sets the htmlType if set', () => {
      render(<Button {...props} htmlType="submit" />);
      expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
    });

    it('passes through custom classes if set', () => {
      render(<Button {...props} className="donkeysarethebest" />);
      expect(screen.getByRole('button')).toHaveClass('donkeysarethebest');
    });

    it('passes through aria-label if set', () => {
      render(<Button {...props} aria-label="unique label" />);
      const loadingButton = screen.getByLabelText('unique label');
      expect(loadingButton).toBeInTheDocument();
    });
  });

  describe('onClick', () => {
    it('calls onClick when clicked', async () => {
      render(<Button {...props} />);
      await userEvent.click(screen.getByRole('button'));
      expect(props.onClick).toHaveBeenCalledTimes(1);
    });

    it('does not call onClick when clicked if disabled', async () => {
      render(<Button {...props} disabled />);
      await userEvent.click(screen.getByRole('button'));
      expect(props.onClick).toHaveBeenCalledTimes(0);
    });

    it('does not call onClick when clicked if loading', async () => {
      render(<Button {...props} loading />);
      await userEvent.click(screen.getByRole('button'));
      expect(props.onClick).toHaveBeenCalledTimes(0);
    });
  });

  describe('onFocus and onBlur', () => {
    it('calls both handlers by default', async () => {
      render(<Button {...props} />);
      await userEvent.tab();
      expect(props.onFocus).toHaveBeenCalledTimes(1);
      await userEvent.tab();
      expect(props.onFocus).toHaveBeenCalledTimes(1);
    });

    it('does not call either handler if disabled', async () => {
      render(<Button {...props} disabled />);
      await userEvent.tab();
      expect(props.onFocus).not.toHaveBeenCalled();
      await userEvent.tab();
      expect(props.onFocus).not.toHaveBeenCalled();
    });

    it('calls both handlers if loading', async () => {
      render(<Button {...props} loading />);
      await userEvent.tab();
      expect(props.onFocus).toHaveBeenCalledTimes(1);
      await userEvent.tab();
      expect(props.onFocus).toHaveBeenCalledTimes(1);
    });
  });

  describe('sizes', () => {
    it('renders small buttons', () => {
      expect(render(<Button {...props} size={SMALL} />).container).toMatchSnapshot();
    });

    it('renders medium buttons', () => {
      expect(render(<Button {...props} size={MEDIUM} />).container).toMatchSnapshot();
    });

    it('renders large buttons', () => {
      expect(render(<Button {...props} size={LARGE} />).container).toMatchSnapshot();
    });
  });

  describe('types', () => {
    it('renders accent buttons', () => {
      expect(render(<Button {...props} type={ACCENT} />).container).toMatchSnapshot();
    });

    it('renders positive buttons', () => {
      expect(render(<Button {...props} type={POSITIVE} />).container).toMatchSnapshot();
    });

    it('renders negative buttons', () => {
      expect(render(<Button {...props} type={NEGATIVE} />).container).toMatchSnapshot();
    });
  });

  describe('priorities', () => {
    it('renders primary buttons', () => {
      [ACCENT, POSITIVE, NEGATIVE].forEach((type) =>
        expect(
          render(<Button {...props} priority={PRIMARY} type={type} />).container,
        ).toMatchSnapshot(),
      );
    });

    it('renders secondary buttons', () => {
      [ACCENT, POSITIVE, NEGATIVE].forEach((type) =>
        expect(
          render(<Button {...props} priority={SECONDARY} type={type} />).container,
        ).toMatchSnapshot(),
      );
    });

    it('renders tertiary buttons', () => {
      expect(
        render(<Button {...props} priority={TERTIARY} type={ACCENT} />).container,
      ).toMatchSnapshot();
    });

    it('defaults tertiary buttons to secondary for positive buttons', () => {
      [POSITIVE, NEGATIVE].forEach((type) =>
        expect(
          render(<Button {...props} priority={TERTIARY} type={type} />).container,
        ).toMatchSnapshot(),
      );
    });
  });

  describe('other states', () => {
    it('renders as loading if `loading` is true', () => {
      render(<Button {...props} loading />);
      const button = screen.queryByRole('button', {
        name: messages.loadingAriaLabel.defaultMessage,
      });
      expect(button).toBeInTheDocument();
      expect(button).toBeEnabled();
      expect(button).toHaveClass('btn-loading');
      expect(button).toHaveAttribute('aria-disabled', 'true');
      expect(button).toHaveAttribute('aria-busy', 'true');
      expect(button).toHaveAttribute('aria-live', 'polite');
      expect(screen.getByTestId('ButtonProgressIndicator')).toBeInTheDocument();
    });

    it('disables the button', () => {
      render(<Button {...props} disabled />);
      const button = screen.queryByRole('button');
      expect(button).toBeDisabled();
      expect(button).toHaveClass('disabled');
      expect(button).toHaveAttribute('aria-disabled', 'false');
      expect(button).toHaveAttribute('aria-busy', 'false');
      expect(button).toHaveAttribute('aria-live', 'off');
    });

    it('renders as block if block is true', () => {
      expect(render(<Button {...props} block />).container).toMatchSnapshot();
    });
  });

  describe('deprecated types', () => {
    it('renders primary as accent buttons and logs a warning', () => {
      expect(render(<Button {...props} type={PRIMARY} />).container).toMatchSnapshot();
      expect(mockedWarn).toHaveBeenCalledTimes(1);
    });

    it('renders pay as positive buttons and logs a warning', () => {
      expect(render(<Button {...props} type={PAY} />).container).toMatchSnapshot();
      expect(mockedWarn).toHaveBeenCalledTimes(1);
    });

    it('renders danger as negative buttons with priority secondary and logs a warning', () => {
      expect(render(<Button {...props} type={DANGER} />).container).toMatchSnapshot();
      expect(mockedWarn).toHaveBeenCalledTimes(1);
    });

    it('renders link as accent buttons with priority tertiary and logs a warning', () => {
      expect(render(<Button {...props} type={LINK} />).container).toMatchSnapshot();
    });
  });
});
