import { shallow } from 'enzyme';
import React from 'react';
import { Form } from 'semantic-ui-react';

import { validateEmail } from '../../lib';
import Context from './Context';
import { ResetRequestForm } from './ResetRequestForm';

jest.mock('../../lib');

describe('ResetRequestForm component', () => {
  const onResetRequest = jest.fn();

  const request = {
    setParams: jest.fn().mockReturnThis(),
    promise: jest.fn().mockReturnThis(),
    create: jest.fn().mockResolvedValue(),
  };

  const site = {
    req: jest.fn().mockReturnValue(request),
  };

  let wrapper;

  beforeEach(() => {
    request.create.mockResolvedValue();
    validateEmail.mockReturnValue(true);

    wrapper = shallow(
      <ResetRequestForm
        onResetRequest={ onResetRequest }
        site={ site }
      >
        Content
      </ResetRequestForm>
    );
  });

  const getProvider = () => wrapper.find(Context.Provider);

  const getContext = (field) => getProvider().prop('value')[field];

  const typeEmail = (value = 'test@email') =>
    getContext('handleChange')(null, { name: 'email', value });

  /**
   * Submits the form
   * We call the instance method directly so we can await the Promise
   * @returns {Promise<void>} resolves when complete
   */
  const doSubmit = () => wrapper.instance().handleSubmit();

  it('renders', () => {
    expect(wrapper).toExist();
  });

  it('renders a Provider to pass the context', () => {
    expect(getProvider()).toExist();
    expect(getProvider()).toHaveProp('value');
  });

  it('starts out with an empty email', () => {
    expect(getContext('email')).toBeDefined();
    expect(getContext('email')).toBe('');
  });

  it('starts out without any error state', () => {
    expect(getContext('error')).toBeDefined();
    expect(getContext('error')).toBeNull();
  });

  it('exposes a method to receive changes from fields', () => {
    expect(getContext('handleChange')).toBeDefined();
    expect(typeof getContext('handleChange')).toBe('function');
  });

  it('allows an email address to be entered', () => {
    typeEmail();
    expect(getContext('email')).toBe('test@email');
  });

  it('exposes a method to check whether the form is complete', () => {
    expect(getContext('isFormValid')).toBeDefined();
    expect(typeof getContext('isFormValid')).toBe('function');
  });

  it('starts out incomplete', () => {
    expect(getContext('isFormValid')()).toBe(false);
  });

  it('is complete when an email address is entered', () => {
    typeEmail();
    expect(getContext('isFormValid')()).toBe(true);
  });

  it('can be marked incomplete', () => {
    typeEmail();
    wrapper.setProps({ invalid: true });
    expect(getContext('isFormValid')()).toBe(false);
  });

  it('renders a Form', () => {
    expect(wrapper.find('Form')).toExist();
  });

  it('passes props down to the Form', () => {
    wrapper.setProps({ size: 'small' });
    expect(wrapper.find('Form').children()).toHaveText('Content');
    expect(wrapper.find('Form')).toHaveProp('size', 'small');
  });

  it('avoids passing internal props to the Form', () => {
    wrapper.setProps({ invalid: true });
    const form = wrapper.find('Form');
    expect(form).not.toHaveProp('identityProvider');
    expect(form).not.toHaveProp('invalid');
    expect(form).not.toHaveProp('onResetRequest');
    expect(form).not.toHaveProp('site');
  });

  it('copies propTypes from the Form', () => {
    // Check a representative prop
    expect(ResetRequestForm.propTypes.size).toBe(Form.propTypes.size);
  });

  it('starts out without the Form error flag', () => {
    expect(wrapper.find('Form')).toHaveProp('error', false);
  });

  it('adds the error flag when requested to by a prop', () => {
    wrapper.setProps({ error: true });
    expect(wrapper.find('Form')).toHaveProp('error', true);
  });

  it('starts out without the Form loader', () => {
    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });

  it('adds the loader when requested to by a prop', () => {
    wrapper.setProps({ loading: true });
    expect(wrapper.find('Form')).toHaveProp('loading', true);
  });

  it('listens to form submit events', () => {
    // We call the instance method directly in tests so need to check it's
    // bound to the correct event
    expect(wrapper.find('Form'))
      .toHaveProp('onSubmit', wrapper.instance().handleSubmit);
  });

  it('requests a password reset on submission', async () => {
    site.req.mockClear();
    request.setParams.mockClear();
    request.promise.mockClear();
    request.create.mockClear();
    typeEmail();

    await doSubmit();

    expect(site.req).toHaveBeenCalled();
    expect(site.req)
      .toHaveBeenCalledWith('identityprovider/email/resetrequest');

    expect(request.setParams).toHaveBeenCalled();
    expect(request.setParams).toHaveBeenCalledWith({
      email: 'test@email',
      return_to: 'http://localhost/',
    });
    expect(request.promise).toHaveBeenCalled();
    expect(request.create).toHaveBeenCalled();
  });

  it('does nothing if the form is marked incomplete', () => {
    site.req.mockClear();
    typeEmail();
    wrapper.setProps({ invalid: true });

    doSubmit();

    expect(site.req).not.toHaveBeenCalled();
  });

  it('adds a loader to the form when submitting', () => {
    typeEmail();
    doSubmit();
    expect(wrapper.find('Form')).toHaveProp('loading', true);
  });

  it('removes the loader when submission is completed', async () => {
    typeEmail();
    await doSubmit();
    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });

  it('validates the email address before submitting', () => {
    validateEmail.mockClear();
    typeEmail();

    doSubmit();

    expect(validateEmail).toHaveBeenCalled();
    expect(validateEmail).toHaveBeenCalledWith('test@email');
  });

  it('trims the email address before validating', () => {
    validateEmail.mockClear();
    typeEmail('  test@email  ');

    doSubmit();

    expect(validateEmail).toHaveBeenCalledWith('test@email');
  });

  it('avoids submitting invalid email addresses', async () => {
    site.req.mockClear();
    validateEmail.mockReturnValue(false);
    typeEmail();

    await doSubmit();

    expect(site.req).not.toHaveBeenCalled();
  });

  it('adds an error state if the email is invalid', () => {
    validateEmail.mockReturnValue(false);
    typeEmail();

    doSubmit();

    expect(getContext('error')).toBe('email');
  });

  it('adds the error flag if the email is invalid', () => {
    validateEmail.mockReturnValue(false);
    typeEmail();

    doSubmit();

    expect(wrapper.find('Form')).toHaveProp('error', true);
  });

  it('removes the loader if the email is invalid', () => {
    validateEmail.mockReturnValue(false);
    typeEmail();

    doSubmit();

    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });

  it('does nothing if the form is incomplete', () => {
    site.req.mockClear();
    validateEmail.mockClear();

    doSubmit();

    expect(validateEmail).not.toHaveBeenCalled();
    expect(site.req).not.toHaveBeenCalled();
  });

  it('trims the email address before submitting', () => {
    request.setParams.mockClear();
    typeEmail('  test@email  ');

    doSubmit();

    expect(request.setParams).toHaveBeenCalled();
    expect(request.setParams).toHaveBeenCalledWith({
      email: 'test@email',
      return_to: 'http://localhost/',
    });
  });

  it('allows the IDP to be customised', async () => {
    site.req.mockClear();
    wrapper.setProps({ identityProvider: 'test-idp' });
    typeEmail();

    await doSubmit();

    expect(site.req)
      .toHaveBeenCalledWith('identityprovider/test-idp/resetrequest');
  });

  it('calls the event handler when submission is completed', async () => {
    onResetRequest.mockClear();
    typeEmail();

    await doSubmit();

    expect(onResetRequest).toHaveBeenCalled();
  });

  it('copes if there is no reset request event handler', async () => {
    wrapper.setProps({ onResetRequest: undefined });
    typeEmail();

    await doSubmit();

    // Test succeeds if no errors
  });

  it('adds an error state if an error occurs', async () => {
    request.create.mockRejectedValue();
    typeEmail();

    await doSubmit();

    expect(getContext('error')).toBe('error');
  });

  it('adds the error flag if an error occurs', async () => {
    request.create.mockRejectedValue();
    typeEmail();

    await doSubmit();

    expect(wrapper.find('Form')).toHaveProp('error', true);
  });

  it('removes the loader if an error occurs', async () => {
    request.create.mockRejectedValue();
    typeEmail();

    await doSubmit();

    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });
});
