import React from 'react';
import { configure, fireEvent, render, screen } from '@testing-library/react';

import '__mocks__/matchMedia';
import App from 'App/App';
import { AppProvider, reducer } from 'App/AppContext';
import { Footer } from 'components';
import { themes } from 'appearance';

configure({ testIdAttribute: 'data-v2' });

describe('application tests', () => {
  beforeEach(() => {
    render(<App />);
  });

  /**
   * Check content element
   * @param {HTMLElement} element Element for the content
   * @param {RegExp} display Display value for the content
   * @param {string} link Optional link within the content
   */
  const checkContent = (
    element: HTMLElement,
    display: RegExp,
    link?: string
  ) => {
    expect(element).toBeVisible();
    expect(element).toHaveAccessibleName();
    expect(element).toHaveAccessibleDescription();
    expect(element).toHaveTextContent(display);
    if (link) expect(element).toHaveAttribute('href', link);
  };

  /**
   * Check button element
   * @param {HTMLElement} parent Parent element for the button
   * @param {HTMLElement} child Child element for the button
   * @param {RegExp} display Display value for the button
   * @param {string} link Link within the button
   */
  const checkButton = (
    parent: HTMLElement,
    child: HTMLElement,
    display: RegExp,
    link: string
  ) => {
    expect(child).toHaveTextContent(display);

    expect(parent).toBeVisible();
    expect(parent).toHaveAccessibleName();
    expect(parent).toHaveAccessibleDescription();
    expect(parent).toHaveAttribute('href', link);
  };

  it('should render name: Adam Alston', () => {
    const element = screen.getByTestId('name');

    checkContent(element, /^Adam Alston$/);
  });

  it('should render title: Software Engineer', () => {
    const element = screen.getByTestId('title');

    checkContent(element, /^Software Engineer$/);
  });

  it('should render creator', () => {
    const element = screen.getByTestId('creator');

    checkContent(element, /^Adam Alston$/, 'https://www.adamalston.com');
  });

  it('should render link to source code', () => {
    const element = screen.getByTestId('source');

    checkContent(element, /^Source$/, 'https://github.com/adamalston/v2/');
  });

  it('should render GitHub button', () => {
    const parent = screen.getByTestId('button-GitHub');
    const child = screen.getByTestId('GitHub');

    checkButton(parent, child, /^GitHub$/, 'https://github.com/adamalston/');
  });

  it('should render LinkedIn button', () => {
    const parent = screen.getByTestId('button-LinkedIn');
    const child = screen.getByTestId('LinkedIn');

    checkButton(
      parent,
      child,
      /^LinkedIn$/,
      'https://www.linkedin.com/in/adam-alston/'
    );
  });

  it('should render Resume button', () => {
    const parent = screen.getByTestId('button-Resume');
    const child = screen.getByTestId('Resume');

    checkButton(
      parent,
      child,
      /^Resume$/,
      'https://drive.google.com/drive/folders/10k8NWflSYQ5laPzuWtK3bzUKzuOeas8i/'
    );
  });

  it('should render Email button', () => {
    const parent = screen.getByTestId('button-Email');
    const child = screen.getByTestId('Email');

    checkButton(parent, child, /^Email$/, 'mailto:[email protected]');
  });

  it('should toggle between the dark and light themes', () => {
    const toggle = screen.getByTestId('toggle');
    const particles = screen.getByTestId('particles');

    expect(toggle).toBeVisible();
    expect(toggle).toHaveAccessibleName();
    expect(toggle).toHaveAccessibleDescription();

    expect(particles).toBeVisible();
    expect(particles).toHaveAccessibleName();

    // site should default to the dark theme
    expect(toggle).toBeChecked();
    expect(particles).toHaveStyle({ backgroundColor: '#000' });

    // click the toggle
    fireEvent.click(toggle);

    // the light theme should be visible
    expect(toggle).not.toBeChecked();
    expect(particles).toHaveStyle({ backgroundColor: '#fff' });
  });

  it('should render full footer on desktop', () => {
    const footer = screen.getByTestId('footer');

    expect(footer).toHaveTextContent(
      /^Designed and built by Adam Alston \| Source$/
    );
  });
});

describe('app context tests', () => {
  it('should render partial footer on mobile', () => {
    render(
      <AppProvider config={{} as any} isMobile={true} children={<Footer />} />
    );

    // partial footer should now be visible
    const footer = screen.getByTestId('footer');

    expect(footer).toHaveTextContent(/^Designed and built by Adam Alston$/);
    expect(footer).not.toHaveTextContent(/Source/);
  });

  describe('reducer tests', () => {
    it('should return the initial state', () => {
      const state = reducer(undefined, {});

      expect(state).toEqual(undefined);
    });

    it('should return the dark theme', () => {
      const state = reducer(undefined, { type: 'SET_THEME', value: 'dark' });

      expect(state).toEqual({ theme: themes.dark });
    });

    it('should return the light theme', () => {
      const state = reducer(undefined, { type: 'SET_THEME', value: 'light' });

      expect(state).toEqual({ theme: themes.light });
    });
  });
});

describe('local storage tests', () => {
  beforeEach(() => {
    localStorage.clear();
  });

  it("should show the dark theme when 'theme' is set to 'true' in local storage", () => {
    // set local storage item and render the app
    localStorage.setItem('theme', 'true');
    render(<App />);

    // check that the local storage item has been updated correctly
    expect(localStorage.getItem('theme')).toEqual('dark');
    const particles = screen.getByTestId('particles');
    expect(particles).toHaveStyle({ backgroundColor: '#000' });
  });

  it("should show the light theme when 'theme' is set to 'false' in local storage", () => {
    // set local storage item and render the app
    localStorage.setItem('theme', 'false');
    render(<App />);

    // check that the local storage item has been updated correctly
    expect(localStorage.getItem('theme')).toEqual('light');

    const particles = screen.getByTestId('particles');
    expect(particles).toHaveStyle({ backgroundColor: '#fff' });
  });

  // https://testing-library.com/docs/react-testing-library/api/#rerender
  it('should persist the light theme through an app re-render', () => {
    const { rerender } = render(<App />);

    expect(localStorage.getItem('theme')).toBeNull();
    localStorage.setItem('theme', 'light');

    // re-render the app and check the theme
    rerender(<App />);
    const particles = screen.getByTestId('particles');

    expect(localStorage.getItem('theme')).toEqual('light');
    expect(particles).toHaveStyle({ backgroundColor: '#fff' });
  });

  it('should change local storage value when toggle is clicked', () => {
    // set local storage item and render the app
    localStorage.setItem('theme', 'light');
    render(<App />);

    // click the toggle
    const toggle = screen.getByTestId('toggle');
    fireEvent.click(toggle);

    // check that the local storage item has been changed
    expect(localStorage.getItem('theme')).not.toEqual('light');
  });
});