import { userEvent, fireEvent, render, screen, waitFor } from '../../test-utils';

import { AmountInput } from './AmountInput';

describe('AmountInput', () => {
  // TODO: "allows to delete the thousands separator and the preceding digit" test is flaky
  jest.retryTimes(3, { logErrorsBeforeRetry: true });

  it('formats the passed in value', () => {
    const onChange = jest.fn();
    const { rerender } = render(
      <AmountInput id="foo" currency="GBP" amount={123456.78} onChange={onChange} />,
    );

    const input = screen.getByRole<HTMLInputElement>('textbox');

    expect(input).toHaveValue('123,456.78');
    expect(onChange).not.toHaveBeenCalled();

    rerender(<AmountInput id="foo" currency="GBP" amount={4} onChange={onChange} />);
    expect(input).toHaveValue('4');
    expect(onChange).not.toHaveBeenCalled();

    rerender(<AmountInput id="foo" currency="GBP" amount={null} onChange={onChange} />);
    expect(input).toHaveValue('');
    expect(onChange).not.toHaveBeenCalled();
  });

  it('formats the value while the user is typing', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    expect(onChange).not.toHaveBeenCalled();

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, '123');
    expect(input).toHaveValue('123');
    expect(onChange).toHaveBeenLastCalledWith(123);

    await userEvent.type(input, '4');
    expect(input).toHaveValue('1,234');
    expect(onChange).toHaveBeenLastCalledWith(1234);

    onChange.mockClear();
    expect(onChange).not.toHaveBeenCalled();
    await userEvent.type(input, '.');
    expect(input).toHaveValue('1,234.');
    expect(onChange).not.toHaveBeenCalled();

    await userEvent.type(input, '5');
    expect(input).toHaveValue('1,234.5');
    expect(onChange).toHaveBeenLastCalledWith(1234.5);

    await userEvent.type(input, '6');
    expect(input).toHaveValue('1,234.56');
    expect(onChange).toHaveBeenLastCalledWith(1234.56);

    await userEvent.type(input, '{backspace}');
    expect(input).toHaveValue('1,234.5');
    expect(onChange).toHaveBeenLastCalledWith(1234.5);

    onChange.mockClear();
    fireEvent.blur(input);
    expect(input).toHaveValue('1,234.50');
    expect(onChange).not.toHaveBeenCalled();
  });

  it('allows to delete the thousands separator and the preceding digit', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, '1234');
    expect(input).toHaveValue('1,234');
    expect(onChange).toHaveBeenLastCalledWith(1234);

    await userEvent.type(input, '{backspace}', {
      initialSelectionStart: 2,
    });
    expect(input).toHaveValue('234');
    expect(onChange).toHaveBeenLastCalledWith(234);

    await waitFor(() => {
      expect(input.selectionStart).toBe(0);
    });
  });

  it('allows to paste poorly formatted values', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.click(input);
    await userEvent.paste('1234.5678');
    expect(input).toHaveValue('1,234.56');
    expect(onChange).toHaveBeenLastCalledWith(1234.56);
  });

  it('allows to paste values', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.click(input);
    await userEvent.paste('1000');
    expect(input).toHaveValue('1,000');
    expect(onChange).toHaveBeenLastCalledWith(1000);
  });

  it('only allows specific characters to be entered', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, 'abc');
    expect(input).toHaveValue('');
    expect(onChange).not.toHaveBeenCalled();

    await userEvent.type(input, '123');
    expect(input).toHaveValue('123');
    expect(onChange).toHaveBeenLastCalledWith(123);

    await userEvent.type(input, 'def');
    expect(input).toHaveValue('123');
    expect(onChange).toHaveBeenLastCalledWith(123);

    await userEvent.type(input, '{backspace}');
    expect(input).toHaveValue('12');
    expect(onChange).toHaveBeenLastCalledWith(12);
  });

  it('does not allow to enter too many decimals', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, '123.45');
    expect(input).toHaveValue('123.45');
    expect(onChange).toHaveBeenLastCalledWith(123.45);
    onChange.mockClear();

    await userEvent.type(input, '6');
    expect(input).toHaveValue('123.45');
    expect(onChange).not.toHaveBeenCalled();
  });

  it('does not allow to enter too many decimal separators', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, '123.');
    expect(input).toHaveValue('123.');
    expect(onChange).toHaveBeenLastCalledWith(123);
    onChange.mockClear();

    await userEvent.type(input, '.');
    expect(input).toHaveValue('123.');
    expect(onChange).not.toHaveBeenCalled();

    await userEvent.type(input, '45');
    expect(input).toHaveValue('123.45');
    expect(onChange).toHaveBeenLastCalledWith(123.45);
    onChange.mockClear();

    await userEvent.type(input, '.');
    expect(input).toHaveValue('123.45');
    expect(onChange).not.toHaveBeenCalled();
  });

  it('maintains cursor position after formatting', async () => {
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');
    await userEvent.type(input, '1234567');
    expect(input).toHaveValue('1,234,567');
    expect(onChange).toHaveBeenLastCalledWith(1234567);
    expect(input.selectionStart).toBe(9);

    await userEvent.type(input, '9', {
      initialSelectionStart: 4, // Place the cursor between "3" and "4"
    });
    expect(input).toHaveValue('12,394,567');
    expect(onChange).toHaveBeenLastCalledWith(12394567);

    await waitFor(() => {
      expect(input.selectionStart).toBe(5);
    });
  });

  it('adds decimal placeholders when the user stars typing decimals', async () => {
    const onChange = jest.fn();
    const { container } = render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);
    const input = screen.getByRole<HTMLInputElement>('textbox');
    await userEvent.type(input, '1234.');
    expect(input).toHaveValue('1,234.');

    expect(getInputAndAddonContents(container)).toBe('1,234.00');

    await userEvent.type(input, '5');
    expect(input).toHaveValue('1,234.5');
    expect(getInputAndAddonContents(container)).toBe('1,234.50');

    await userEvent.type(input, '6');
    expect(input).toHaveValue('1,234.56');
    expect(getInputAndAddonContents(container)).toBe('1,234.56');
  });

  it('adds decimal placeholders when the user stars typing decimals in Spansh', async () => {
    const onChange = jest.fn();
    const { container } = render(<AmountInput id="foo" currency="GBP" onChange={onChange} />, {
      locale: 'es',
    });
    const input = screen.getByRole<HTMLInputElement>('textbox');
    await userEvent.type(input, '1234,');
    expect(input).toHaveValue('1234,');

    expect(getInputAndAddonContents(container)).toBe('1234,00');

    await userEvent.type(input, '5');
    expect(input).toHaveValue('1234,5');
    expect(getInputAndAddonContents(container)).toBe('1234,50');

    await userEvent.type(input, '6');
    expect(input).toHaveValue('1234,56');
    expect(getInputAndAddonContents(container)).toBe('1234,56');
  });

  it('adds decimal placeholders when the input is blurred', async () => {
    const onChange = jest.fn();
    const { container } = render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);
    const input = screen.getByRole<HTMLInputElement>('textbox');
    await userEvent.type(input, '1234');
    expect(input).toHaveValue('1,234');

    fireEvent.blur(input);
    expect(getInputAndAddonContents(container)).toBe('1,234.00');
  });

  it('adds decimal placeholders when the input is blurred in Spanish', async () => {
    const onChange = jest.fn();
    const { container } = render(<AmountInput id="foo" currency="GBP" onChange={onChange} />, {
      locale: 'es',
    });
    const input = screen.getByRole<HTMLInputElement>('textbox');
    await userEvent.type(input, '1234');
    expect(input).toHaveValue('1234');

    fireEvent.blur(input);
    expect(getInputAndAddonContents(container)).toBe('1234,00');
  });

  it('does not allow to enter an amount greater than the maximum', async () => {
    const maxAmount = Number.MAX_SAFE_INTEGER - 1;
    const onChange = jest.fn();
    render(<AmountInput id="foo" currency="GBP" onChange={onChange} />);

    const input = screen.getByRole<HTMLInputElement>('textbox');

    await userEvent.type(input, String(maxAmount));
    expect(input).toHaveValue('9,007,199,254,740,990');
    expect(onChange).toHaveBeenLastCalledWith(maxAmount);

    onChange.mockClear();
    await userEvent.type(input, '1');
    expect(input).toHaveValue('9,007,199,254,740,990');
    expect(onChange).not.toHaveBeenCalled();
  });
});

const getInputAndAddonContents = (container: HTMLElement) => {
  const input = screen.getByRole<HTMLInputElement>('textbox');
  return `${input.value}${container.textContent}`;
};
