/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import React from 'react';
import {render, fireEvent, screen, waitFor} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import * as api from '../../src/api.js';
import Auth from '../../src/auth.js';
import Main from '../../src/components/main.js';

/**
 * Helper function -
 * Returns an array of items labeled as so: item1, item2...
 * @param {number} numItems Number of items to generate
 * @param {string} name The name of the items
 * @return {string[]} List of numbered items
 */
const generateItems = (numItems, name) => {
  const items = [];
  for (let i = 1; i <= numItems; i++) {
    items.push(name + i);
  }
  return items;
};

// Set up mock functions for api calls
jest.mock('../../src/api.js');
jest.mock('../../src/auth.js');

// Ensure the makeCancelable function is NOT mocked as it is needed
// for the viewer component to function properly
const {makeCancelable} = jest.requireActual('../../src/api.js');
api.makeCancelable = makeCancelable;

// Mock auth functions to prevent any use of the gapi client
Auth.initClient.mockReturnValue(Promise.resolve());
Auth.isSignedIn.mockReturnValue(true);
Auth.getAccessToken.mockReturnValue('FAKE_ACCESS_TOKEN');

// fetchProjects will resolve 5 mock projects
api.fetchProjects.mockResolvedValue(
    generateItems(5, 'project'),
);

// fetchLocations will resolve 5 fake locations
api.fetchLocations.mockResolvedValue(
    generateItems(5, 'location'),
);

// fetchDatasets will resolve 5 fake datasets
api.fetchDatasets.mockResolvedValue(
    generateItems(5, 'dataset'),
);

// fetchDicomStores will resolve 5 fake dicomStores
api.fetchDicomStores.mockResolvedValue(
    generateItems(5, 'dicomStore'),
);

// fetchStudies will resolve 1 fake study
api.fetchStudies.mockResolvedValue([{
  '00100020': {
    Value: ['study1'],
  },
  '0020000D': {
    Value: ['study1-UID'],
  },
}]);

// fetchSeries will resolve 1 fake series
api.fetchSeries.mockResolvedValue([{
  '00080060': {
    Value: ['series1'],
  },
  '0020000E': {
    Value: ['series1-UID'],
  },
}]);

// fetchMetadata will resolve 1 fake instance
api.fetchMetadata.mockResolvedValue([{
  '00080016': {
    vr: 'UI',
    Value: ['instance1-UID'],
  },
  '00200013': {
    vr: 'IS',
    Value: [1],
  },
}]);

// eslint-disable-next-line max-len
test('User can navigate between project, location, dataset, dicomStore, study, and series', async () => {
  render(
      <Main/>,
  );

  // Navigate through: project2 -> location5 -> dataset3 ->
  // dicomStore4 -> study1 -> series1

  // Ensure project1 through project5 are displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^project\d+$/)).toHaveLength(5));
  fireEvent.click(screen.getByText('project2'));

  // Ensure location1 through location5 are displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^location\d+$/)).toHaveLength(5));
  fireEvent.click(screen.getByText('location5'));

  // Ensure dataset1 through dataset5 are displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^dataset\d+$/)).toHaveLength(5));
  fireEvent.click(screen.getByText('dataset3'));

  // Ensure dicomStore1 through dicomStore5 are displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^dicomStore\d+$/)).toHaveLength(5));
  fireEvent.click(screen.getByText('dicomStore4'));

  // Ensure study1 is displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^study\d+$/)).toHaveLength(1));
  fireEvent.click(screen.getByText('study1'));

  // Ensure series1 is displayed
  await waitFor(() =>
    expect(screen.getAllByText(/^series\d+$/)).toHaveLength(1));
  fireEvent.click(screen.getByText('series1'));

  // Confirm that breadcrumbs are all displaying correctly
  expect(screen.queryByText('project2')).not.toBeNull();
  expect(screen.queryByText('location5')).not.toBeNull();
  expect(screen.queryByText('dataset3')).not.toBeNull();
  expect(screen.queryByText('dicomStore4')).not.toBeNull();
  expect(screen.queryByText('study1')).not.toBeNull();
  expect(screen.queryByText('series1')).not.toBeNull();

  // Confirm that clicking a breadcrumb resets state back to
  // search list
  fireEvent.click(screen.getByText('project2'));
  await waitFor(() =>
    expect(screen.queryByText('Select Project')).not.toBeNull());
  await waitFor(() =>
    expect(screen.getAllByText(/^project\d+$/)).toHaveLength(5));
}, 10000);