import React from 'react';

import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';

const { OTP } = Input;

describe('Input.OTP', () => {
  focusTest(Input.OTP, { refFocus: true });
  mountTest(Input.OTP);
  rtlTest(Input.OTP);

  const getText = (container: HTMLElement) => {
    const inputList = container.querySelectorAll<HTMLInputElement>('input');
    return Array.from(inputList)
      .map((input) => input.value || ' ')
      .join('')
      .replace(/\s*$/, '');
  };

  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('paste to fill all', async () => {
    const onChange = jest.fn();
    const { container } = render(<OTP onChange={onChange} />);

    fireEvent.input(container.querySelector('input')!, { target: { value: '123456' } });
    expect(onChange).toHaveBeenCalledTimes(1);
    expect(onChange).toHaveBeenCalledWith('123456');
  });

  it('fill step by step', () => {
    const CODE = 'BAMBOO';
    const onChange = jest.fn();
    render(<OTP onChange={onChange} autoFocus />);

    for (let i = 0; i < CODE.length; i += 1) {
      expect(onChange).not.toHaveBeenCalled();
      fireEvent.input(document.activeElement!, { target: { value: CODE[i] } });
    }

    expect(onChange).toHaveBeenCalledTimes(1);
    expect(onChange).toHaveBeenCalledWith(CODE);
  });

  it('backspace to delete', async () => {
    const CODE = 'LITTLE';

    const onChange = jest.fn();
    const { container } = render(<OTP defaultValue={CODE} onChange={onChange} />);
    expect(getText(container)).toBe(CODE);

    // Focus on the last cell
    const inputList = container.querySelectorAll('input');
    inputList[inputList.length - 1].focus();

    for (let i = 0; i < CODE.length; i += 1) {
      fireEvent.keyDown(document.activeElement!, { key: 'Backspace' });
      fireEvent.input(document.activeElement!, { target: { value: '' } });
      fireEvent.keyUp(document.activeElement!, { key: 'Backspace' });
    }

    expect(getText(container)).toBe('');

    // We do not trigger change if empty. It's safe to modify this logic if needed.
    expect(onChange).not.toHaveBeenCalled();
  });

  it('controlled', () => {
    const { container, rerender } = render(<OTP value="BAMBOO" />);
    expect(getText(container)).toBe('BAMBOO');

    rerender(<OTP value="LITTLE" />);
    expect(getText(container)).toBe('LITTLE');

    rerender(<OTP value="" />);
    expect(getText(container)).toBe('');

    rerender(<OTP value="EXCEED-RANGE" />);
    expect(getText(container)).toBe('EXCEED');

    rerender(<OTP value={null!} />);
    expect(getText(container)).toBe('');
  });

  it('focus to selection', async () => {
    const { container } = render(<OTP defaultValue="BAMBOO" />);

    const firstInput = container.querySelector('input')!;
    const selectSpy = jest.spyOn(firstInput, 'select');
    expect(selectSpy).not.toHaveBeenCalled();

    // Trigger focus
    firstInput.focus();
    await waitFakeTimer();

    expect(selectSpy).toHaveBeenCalled();
  });

  it('arrow key to switch', () => {
    const { container } = render(<OTP autoFocus />);

    const inputList = Array.from(container.querySelectorAll('input'));
    expect(document.activeElement).toEqual(inputList[0]);

    fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' });
    expect(document.activeElement).toEqual(inputList[1]);

    fireEvent.keyDown(document.activeElement!, { key: 'ArrowLeft' });
    expect(document.activeElement).toEqual(inputList[0]);
  });

  it('fill last cell', () => {
    const { container } = render(<OTP />);
    fireEvent.input(container.querySelectorAll('input')[5], { target: { value: '1' } });

    expect(getText(container)).toBe('     1');
  });

  it('formatter', () => {
    const { container } = render(
      <OTP defaultValue="bamboo" formatter={(val) => val.toUpperCase()} />,
    );
    expect(getText(container)).toBe('BAMBOO');

    // Type to trigger formatter
    fireEvent.input(container.querySelector('input')!, { target: { value: 'little' } });
    expect(getText(container)).toBe('LITTLE');
  });

  it('support mask prop', () => {
    // default
    const { container, rerender } = render(<OTP defaultValue="bamboo" />);
    expect(getText(container)).toBe('bamboo');

    // support string
    rerender(<OTP defaultValue="bamboo" mask="*" />);
    expect(getText(container)).toBe('******');

    // support emoji
    rerender(<OTP defaultValue="bamboo" mask="🔒" />);
    expect(getText(container)).toBe('🔒🔒🔒🔒🔒🔒');
  });

  it('should throw Error when mask.length > 1', () => {
    const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    render(<OTP mask="abc" />);
    expect(errSpy).toHaveBeenCalledWith(
      'Warning: [antd: Input.OTP] `mask` prop should be a single character.',
    );
    errSpy.mockRestore();
  });

  it('should not throw Error when mask.length <= 1', () => {
    const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    render(<OTP mask="x" />);
    expect(errSpy).not.toHaveBeenCalled();
    errSpy.mockRestore();
  });

  it('support type', () => {
    const { container } = render(<OTP type="number" />);
    expect(container.querySelector('input')).toHaveAttribute('type', 'number');
  });
});
