import { shallow } from 'enzyme';
import * as React from 'react';
import sinon from 'sinon';

import { Button, LoginForm } from '~/components';
import { mockUseState, mockUseRef } from '~/test/mocks';
import assert from '~/test/utils/assert';

describe('LoginForm', function () {
  const useStateStub = mockUseState();
  const useRef = mockUseRef();
  const setUsername = sinon.spy();
  const setPassword = sinon.spy();
  const setUsernameError = sinon.spy();
  const setPasswordError = sinon.spy();
  const setPasswordSecured = sinon.spy();
  const onLogin = sinon.spy();

  beforeEach(function () {
    useStateStub
      .onCall(0)
      .callsFake((init) => [init, setUsername])
      .onCall(1)
      .callsFake((init) => [init, setPassword])
      .onCall(2)
      .callsFake((init) => [init, setUsernameError])
      .onCall(3)
      .callsFake((init) => [init, setPasswordError])
      .onCall(4)
      .callsFake((init) => [init, setPasswordSecured])
      .resetHistory();

    setUsername.resetHistory();
    setPassword.resetHistory();
    setUsernameError.resetHistory();
    setPasswordError.resetHistory();
    setPasswordSecured.resetHistory();
    onLogin.resetHistory();
  });

  describe('renders', function () {
    it('with default values', function () {
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });

    it('with passwordSecured=true', function () {
      useStateStub.onCall(4).callsFake(() => [true, () => null]);
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });

    it('with passwordSecured=false', function () {
      useStateStub.onCall(4).callsFake(() => [false, () => null]);
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });

    it('with error on username', function () {
      useStateStub.onCall(2).callsFake(() => [true, () => null]);
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });

    it('with error on password', function () {
      useStateStub.onCall(3).callsFake(() => [true, () => null]);
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });

    it('with error on username & password', function () {
      useStateStub
        .onCall(2)
        .callsFake(() => [true, () => null])
        .onCall(3)
        .callsFake(() => [true, () => null]);
      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      expect(wrapper).toMatchSnapshot();
    });
  });

  describe('onChangeText', function () {
    it('event for username', function () {
      const wrapper = shallow(<LoginForm onLogin={() => null} />).dive();

      wrapper.find({ testID: 'username-field' }).prop('onChangeText')('lorem-ipsum');

      assert.calledOnceWithExactly(setUsername, 'lorem-ipsum');
      assert.calledOnceWithExactly(setUsernameError, false);
    });

    it('event for password', function () {
      const wrapper = shallow(<LoginForm onLogin={() => null} />).dive();

      wrapper.find({ testID: 'password-field' }).prop('onChangeText')('lorem-ipsum');

      assert.calledOnceWithExactly(setPassword, 'lorem-ipsum');
      assert.calledOnceWithExactly(setPasswordError, false);
    });

    it('event when usernameError is updated', function () {
      const wrapper = shallow(<LoginForm onLogin={() => null} />);

      wrapper.find({ testID: 'username-field' }).prop('onChangeText')('lorem-ipsum');

      assert.calledOnceWithExactly(setUsername, 'lorem-ipsum');
      assert.calledOnceWithExactly(setUsernameError, false);
    });

    it('event for password when userPassword is updated', function () {
      const wrapper = shallow(<LoginForm onLogin={() => null} />);

      wrapper.find({ testID: 'password-field' }).prop('onChangeText')('lorem-ipsum');

      assert.calledOnceWithExactly(setPassword, 'lorem-ipsum');
      assert.calledOnceWithExactly(setPasswordError, false);
    });
  });

  describe('press button', function () {
    it('with username and password', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['initial-username', () => null])
        .onCall(1)
        .callsFake(() => ['password', () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find(Button).simulate('press');

      assert.calledOnceWithExactly(onLogin, 'initial-username', 'password');
    });

    it('without username', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['', () => null])
        .onCall(1)
        .callsFake(() => ['xxx', () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find(Button).simulate('press');

      assert.notCalled(onLogin);
      assert.calledWithExactly(setUsernameError, true);
      assert.calledOnceWithExactly(setPasswordError, false);
    });

    it('without password', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['xxx', () => null])
        .onCall(1)
        .callsFake(() => ['', () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find(Button).simulate('press');

      assert.notCalled(onLogin);
      assert.calledOnceWithExactly(setUsernameError, false);
      assert.calledOnceWithExactly(setPasswordError, true);
    });
  });

  describe('password submit event', function () {
    it('with username and password', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['initial-username', () => null])
        .onCall(1)
        .callsFake(() => ['password', () => null]);
      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find({ testID: 'password-field' }).prop('onSubmitEditing')();

      assert.calledOnceWithExactly(onLogin, 'initial-username', 'password');
    });

    it('without username', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['', () => null])
        .onCall(1)
        .callsFake(() => ['xxx', () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find({ testID: 'password-field' }).prop('onSubmitEditing')();

      assert.notCalled(onLogin);
      assert.calledOnceWithExactly(setUsernameError, true);
      assert.calledOnceWithExactly(setPasswordError, false);
    });

    it('without password', function () {
      useStateStub
        .onCall(0)
        .callsFake(() => ['xxx', () => null])
        .onCall(1)
        .callsFake(() => ['', () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find({ testID: 'password-field' }).prop('onSubmitEditing')();

      assert.notCalled(onLogin);
      assert.calledOnceWithExactly(setUsernameError, false);
      assert.calledOnceWithExactly(setPasswordError, true);
    });
  });

  describe('submit', function () {
    it('when username field is focused calls passwordRef focus', function () {
      const focus = sinon.spy();
      useRef.returns({
        current: {
          focus,
        },
      });

      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      wrapper.find({ testID: 'username-field' }).prop('onSubmitEditing')();
      assert.calledOnce(focus);
    });

    it('when username field is focused with null password ref not crash', function () {
      useRef.returns(null);

      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      wrapper.find({ testID: 'username-field' }).prop('onSubmitEditing')();
    });

    it('when username field is focused without current password ref not crash', function () {
      useRef.returns({
        current: null,
      });

      const wrapper = shallow(<LoginForm onLogin={() => null} />);
      wrapper.find({ testID: 'username-field' }).prop('onSubmitEditing')();
    });

    it('username is trimmed before sending it to onLogin', function () {
      const username = '  with-spaces  ';
      const password = 'password';

      useStateStub
        .onCall(0)
        .callsFake(() => [username, () => null])
        .onCall(1)
        .callsFake(() => [password, () => null]);

      const wrapper = shallow(<LoginForm onLogin={onLogin} />);

      wrapper.find(Button).simulate('press');

      assert.calledOnceWithExactly(onLogin, username.trim(), 'password');
    });
  });

  it('toggle secured password', function () {
    useStateStub.onCall(4).callsFake(() => [false, setPasswordSecured]);
    const wrapper = shallow(<LoginForm onLogin={() => null} />);

    expect(wrapper.find({ testID: 'password-icon' })).toMatchSnapshot();

    const rightComponent = wrapper.find({ testID: 'password-field' }).prop('rightComponent');

    rightComponent.props.onPress();

    assert.calledOnceWithExactly(setPasswordSecured, true);
  });
});
