import { h } from 'preact';
import { deep } from 'preact-render-spy';
import fetch from 'jest-fetch-mock';
import { axe, toHaveNoViolations } from 'jest-axe';

import Onboarding from '../Onboarding';
import ProfileForm from '../components/ProfileForm';
import FollowTags from '../components/FollowTags';
import FollowUsers from '../components/FollowUsers';

global.fetch = fetch;

// Corresponds to the order of slides declared in the Onboarding component.
const slides = [
  'IntroSlide',
  'FollowTags',
  'ProfileForm',
  'FollowUsers',
  'EmailPreferencesForm',
];

function flushPromises() {
  return new Promise((resolve) => setImmediate(resolve));
}

function initializeSlides(currentSlide, userData = null, mockData = null) {
  document.body.setAttribute('data-user', userData);
  const onboardingSlides = deep(<Onboarding />);

  if (mockData) {
    fetch.once(mockData);
  }

  onboardingSlides.setState({ currentSlide });

  return onboardingSlides;
}

function expectStepperToRender(onboardingSlides, activeDotCount) {
  // We do not show the stepper on the IntroSlide.
  const stepperDotCount = slides.length - 1;

  expect(onboardingSlides.find('.stepper').exists()).toEqual(true);
  expect(onboardingSlides.find('.dot').length).toBe(stepperDotCount);
  expect(onboardingSlides.find('.active').length).toBe(activeDotCount);
}

describe('<Onboarding />', () => {
  beforeAll(() => {
    expect.extend(toHaveNoViolations);
  });
  beforeEach(() => {
    fetch.resetMocks();
  });

  // Use this to fetch mock response data before trying to render the `followTags` component.
  const fakeTagsResponse = JSON.stringify([
    {
      bg_color_hex: '#000000',
      id: 715,
      name: 'discuss',
      text_color_hex: '#ffffff',
    },
    {
      bg_color_hex: '#f7df1e',
      id: 6,
      name: 'javascript',
      text_color_hex: '#000000',
    },
    {
      bg_color_hex: '#2a2566',
      id: 630,
      name: 'career',
      text_color_hex: '#ffffff',
    },
  ]);

  // Use this to fetch mock response data before trying to render the `followUsers` component.
  const fakeUsersResponse = JSON.stringify([
    {
      id: 1,
      name: 'Ben Halpern',
      profile_image_url: 'ben.jpg',
    },
    {
      id: 2,
      name: 'Krusty the Clown',
      profile_image_url: 'clown.jpg',
    },
    {
      id: 3,
      name: 'dev.to staff',
      profile_image_url: 'dev.jpg',
    },
  ]);
  const getUserData = () =>
    JSON.stringify({
      followed_tag_names: ['javascript'],
      profile_image_90: 'mock_url_link',
      name: 'firstname lastname',
      username: 'username',
    });

  describe('IntroSlide', () => {
    const slideIndex = slides.indexOf('IntroSlide');
    let onboardingSlides;
    const codeOfConductCheckEvent = {
      target: {
        value: 'checked_code_of_conduct',
        name: 'checked_code_of_conduct',
      },
    };
    const termsAndConditionsCheckEvent = {
      target: {
        value: 'checked_terms_and_conditions',
        name: 'checked_terms_and_conditions',
      },
    };

    const updateCodeOfConduct = () => {
      onboardingSlides
        .find('#checked_code_of_conduct')
        .simulate('change', codeOfConductCheckEvent);
    };
    const updateTermsAndConditions = () => {
      onboardingSlides
        .find('#checked_terms_and_conditions')
        .simulate('change', termsAndConditionsCheckEvent);
    };

    beforeEach(() => {
      onboardingSlides = initializeSlides(slideIndex, getUserData());
    });

    test('renders properly', () => {
      expect(onboardingSlides).toMatchSnapshot();
    });

    test('it does not render a stepper', () => {
      expect(onboardingSlides.find('.stepper').length).toBe(0);
    });

    test('should advance if required boxes are checked', async () => {
      fetch.once({});
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex);

      updateCodeOfConduct();
      updateTermsAndConditions();

      onboardingSlides.find('.next-button').simulate('click');

      // Fetch the fakeTagsResponse before trying to render the next slide (followTags).
      fetch.once(fakeTagsResponse);
      await flushPromises();
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
    });

    test('should not have basic a11y violations', async () => {
      const results = await axe(onboardingSlides.toString());

      expect(results).toHaveNoViolations();
    });
  });

  describe('FollowTags', () => {
    let onboardingSlides;
    const slideIndex = slides.indexOf('FollowTags');

    beforeEach(async () => {
      onboardingSlides = initializeSlides(
        slideIndex,
        getUserData(),
        fakeTagsResponse,
      );
      await flushPromises();
    });

    test('renders properly', () => {
      expect(onboardingSlides).toMatchSnapshot();
    });

    test('renders a stepper', () => {
      expectStepperToRender(onboardingSlides, slideIndex);
    });

    test('should render three tags', async () => {
      expect(onboardingSlides.find('.onboarding-tags__item').length).toBe(3);
    });

    test('should allow a user to add a tag and advance', async () => {
      fetch.once({});
      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Skip for now',
      );

      const firstButton = onboardingSlides
        .find('.onboarding-tags__button')
        .first();

      firstButton.simulate('click');
      const followTags = onboardingSlides.find(<FollowTags />);
      expect(followTags.state('selectedTags').length).toBe(1);

      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Continue',
      );

      onboardingSlides.find('.next-button').simulate('click');
      fetch.once(fakeUsersResponse);
      await flushPromises();
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
    });

    it('should step backward', () => {
      onboardingSlides.find('.back-button').simulate('click');
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
    });
  });

  describe('ProfileForm', () => {
    let onboardingSlides;
    const slideIndex = slides.indexOf('ProfileForm');
    const meta = document.createElement('meta');

    meta.setAttribute('name', 'csrf-token');
    document.body.appendChild(meta);

    beforeEach(() => {
      onboardingSlides = initializeSlides(slideIndex, getUserData());
    });

    test('renders properly', () => {
      expect(onboardingSlides).toMatchSnapshot();
    });

    test('renders a stepper', () => {
      expectStepperToRender(onboardingSlides, slideIndex);
    });

    test('should allow user to fill forms and advance', async () => {
      fetch.once({});
      const summaryEvent = { target: { value: 'my bio', name: 'summary' } };
      const locationEvent = {
        target: { value: 'my location', name: 'location' },
      };
      const titleEvent = {
        target: { value: 'my title', name: 'employment_title' },
      };
      const employerEvent = {
        target: { value: 'my employer name', name: 'employer_name' },
      };

      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Skip for now',
      );

      onboardingSlides.find('textarea').simulate('change', summaryEvent);
      onboardingSlides.find('#location').simulate('change', locationEvent);
      onboardingSlides.find('#employment_title').simulate('change', titleEvent);
      onboardingSlides.find('#employer_name').simulate('change', employerEvent);
      const profileForm = onboardingSlides.find(<ProfileForm />);
      expect(profileForm.state().formValues.summary).toBe(
        summaryEvent.target.value,
      );
      expect(profileForm.state().formValues.location).toBe(
        locationEvent.target.value,
      );
      expect(profileForm.state().formValues.employment_title).toBe(
        titleEvent.target.value,
      );
      expect(profileForm.state().formValues.employer_name).toBe(
        employerEvent.target.value,
      );

      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Continue',
      );

      profileForm.find('.next-button').simulate('click');
      fetch.once(fakeTagsResponse);
      await flushPromises();
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
    });

    it('should step backward', () => {
      // Fetch the fakeTagsResponse before trying to render the previous slide (followTags).
      fetch.once(fakeTagsResponse);
      onboardingSlides.find('.back-button').simulate('click');
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
    });
  });

  describe('FollowUsers', () => {
    let onboardingSlides;
    const slideIndex = slides.indexOf('FollowUsers');

    beforeEach(async () => {
      onboardingSlides = initializeSlides(
        slideIndex,
        getUserData(),
        fakeUsersResponse,
      );
      await flushPromises();
    });

    test('renders properly', () => {
      expect(onboardingSlides).toMatchSnapshot();
    });

    test('renders a stepper', () => {
      expectStepperToRender(onboardingSlides, slideIndex);
    });

    test('should render three users', async () => {
      expect(onboardingSlides.find('.user').length).toBe(3);
    });

    test('should allow a user to select and advance', async () => {
      fetch.once({});

      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Skip for now',
      );

      onboardingSlides.find('.user').first().simulate('click');
      expect(onboardingSlides.find('p').last().text()).toBe(
        "You're following 1 person",
      );
      onboardingSlides.find('.user').last().simulate('click');
      expect(onboardingSlides.find('p').last().text()).toBe(
        "You're following 2 people",
      );

      expect(onboardingSlides.find('.next-button').text()).toContain(
        'Continue',
      );

      const followUsers = onboardingSlides.find(<FollowUsers />);
      expect(followUsers.state('selectedUsers').length).toBe(2);
      onboardingSlides.find('.next-button').simulate('click');
      await flushPromises();
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex + 1);
    });

    test('should have a functioning select-all toggle', async () => {
      fetch.once({});

      expect(onboardingSlides.find('button').last().text()).toBe(
        'Select all 3 people',
      );
      onboardingSlides.find('button').last().simulate('click');
      expect(onboardingSlides.find('button').last().text()).toBe(
        'Deselect all',
      );
      const followUsers = onboardingSlides.find(<FollowUsers />);
      expect(followUsers.state('selectedUsers').length).toBe(3);
    });

    it('should step backward', async () => {
      onboardingSlides.find('.back-button').simulate('click');
      await flushPromises();
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
    });
  });

  describe('EmailPreferencesForm', () => {
    let onboardingSlides;
    const { location } = window;
    const slideIndex = slides.indexOf('EmailPreferencesForm');

    beforeEach(() => {
      onboardingSlides = initializeSlides(slideIndex, getUserData());
    });

    afterEach(() => {
      window.location = location;
    });

    test('renders properly', () => {
      expect(onboardingSlides).toMatchSnapshot();
    });

    test('renders a stepper', () => {
      expectStepperToRender(onboardingSlides, slideIndex);
    });

    test('should redirect user when finished', async () => {
      fetch.once({});

      // Setup: Enable window.location to be writable.
      const url = 'https://dummy.com/onboarding';
      Object.defineProperty(window, 'location', {
        value: { href: url },
        writable: true,
      });

      expect(window.location.href).toBe(url);
      onboardingSlides.find('.next-button').simulate('click');
      await flushPromises();
      // No longer advance slide.
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex);
      expect(window.location.href).toBe('/');
    });

    it('should step backward', () => {
      onboardingSlides.find('.back-button').simulate('click');
      expect(onboardingSlides.state().currentSlide).toBe(slideIndex - 1);
    });
  });
});
