import wrapper from '@cloudscape-design/components/test-utils/dom';
import '@testing-library/jest-dom';
import { act, fireEvent, getByRole, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { fail } from 'assert';
import Axios from 'axios';
import { auditLogLabels, caseDetailLabels, commonLabels } from '../../src/common/labels';
import { NotificationsProvider } from '../../src/context/NotificationsContext';
import CaseDetailsPage from '../../src/pages/case-detail';

const push = jest.fn();
const CASE_ID = '100';
jest.mock('next/router', () => ({
  useRouter: jest.fn().mockImplementation(() => ({
    query: { caseId: CASE_ID },
    push,
  })),
}));

global.fetch = jest.fn(() => Promise.resolve({ blob: () => Promise.resolve('foo') }));
global.window.URL.createObjectURL = jest.fn(() => {});
HTMLAnchorElement.prototype.click = jest.fn();

jest.mock('axios');
const mockedAxios = Axios as jest.Mocked<typeof Axios>;

const mockFilesRoot = {
  files: [
    {
      ulid: '01GV3NH93AX2GYHBQNVR27NEMJ',
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      fileName: 'food',
      contentType: 'application/octet-stream',
      createdBy: '01GV13XRYZE1VKY7TY88Y7RPH0',
      filePath: '/',
      fileSizeMb: 50,
      uploadId:
        'aiV5S5CTX6FSWerMkYsVw9vXDp8Xes2gyEDQPPxI4b7LZjk8fKPOFrX7cn_bOupAE.xZ9M0d2HkB_075dVSYsAOKhixAN987_YXJbyUulyWgW8ORp93oRO1U0WMwhmxTE7o5gWoiJlHuVIZptds8Thgpac.4K9ChEeh2Sac35kNGH43XoSFadbzzcWCB9ZKFGP9P0gxfxRlSg4YdTyXIaw--',
      sha256Hash: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9',
      versionId: '8pXrPWVxZoRiCszVYB2lShmCoxWH9Fca',
      status: 'ACTIVE',
      created: '2023-03-09T17:08:40.682Z',
      updated: '2023-03-09T17:08:40.682Z',
      isFile: false,
      ttl: 1678385311,
    },
    {
      ulid: '01GV4J7C6D18WQVCBA7RAPXTT1',
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      fileName: 'rootFile',
      contentType: 'application/octet-stream',
      createdBy: '01GV13XRYZE1VKY7TY88Y7RPH0',
      filePath: '/',
      fileSizeMb: 50,
      uploadId:
        'OEQeZM6D6jYzdHm6nnpXggWDlDhSZbZ1mcUe7JKdpfHP5zSnWlQ3kU.iz5v6zyOiQVvPqS9BfFixgBQgxRaa242L6XQyT2MwzUw7Nizk1pvXQJR_anulhvuvjGH_hpQ1x7ciO5yEDWEKTaxBF5vtxSryncXAFpHfBWBzSQi01Eou9I8PzadqnirZU0PBlN.3DxFuJP8pG2FTMHlBQSlEcA--',
      sha256Hash: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9',
      versionId: 'mwXq6KDGPfw8qD3oUeDNjh2dsSGWWHad',
      status: 'ACTIVE',
      created: '2023-03-10T01:30:04.877Z',
      updated: '2023-03-10T01:30:14.326Z',
      isFile: true,
    },
    {
      ulid: '01GV4J7C6D18WQVCBA7RAPXTT2',
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      fileName: 'rootFile2',
      contentType: 'application/octet-stream',
      createdBy: '01GV13XRYZE1VKY7TY88Y7RPH0',
      filePath: '/',
      fileSizeMb: 50,
      uploadId:
        'OEQeZM6D6jYzdHm6nnpXggWDlDhSZbZ1mcUe7JKdpfHP5zSnWlQ3kU.iz5v6zyOiQVvPqS9BfFixgBQgxRaa242L6XQyT2MwzUw7Nizk1pvXQJR_anulhvuvjGH_hpQ1x7ciO5yEDWEKTaxBF5vtxSryncXAFpHfBWBzSQi01Eou9I8PzadqnirZU0PBlN.3DxFuJP8pG2FTMHlBQSlEcB--',
      sha256Hash: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9',
      versionId: 'mwXq6KDGPfw8qD3oUeDNjh2dsSGWWHad',
      status: 'INACTIVE',
      created: '2023-03-10T01:30:04.877Z',
      updated: '2023-03-10T01:30:14.326Z',
      isFile: true,
    },
  ],
  total: 2,
};

const mockFilesFood = {
  files: [
    {
      ulid: '01GV3NH93AX2GYHBQNVR27NEMJ',
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      fileName: 'sushi.png',
      contentType: 'application/octet-stream',
      createdBy: '01GV13XRYZE1VKY7TY88Y7RPH0',
      filePath: '/food',
      fileSizeMb: 50,
      uploadId:
        'aiV5S5CTX6FSWerMkYsVw9vXDp8Xes2gyEDQPPxI4b7LZjk8fKPOFrX7cn_bOupAE.xZ9M0d2HkB_075dVSYsAOKhixAN987_YXJbyUulyWgW8ORp93oRO1U0WMwhmxTE7o5gWoiJlHuVIZptds8Thgpac.4K9ChEeh2Sac35kNGH43XoSFadbzzcWCB9ZKFGP9P0gxfxRlSg4YdTyXIaw--',
      sha256Hash: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9',
      versionId: '8pXrPWVxZoRiCszVYB2lShmCoxWH9Fca',
      status: 'ACTIVE',
      created: '2023-03-09T17:08:40.682Z',
      updated: '2023-03-09T17:08:40.682Z',
      isFile: true,
      ttl: 1678385311,
    },
  ],
  total: 1,
};

const mockedCaseDetail = {
  ulid: 'abc',
  name: 'mocked case',
  status: 'ACTIVE',
};

const mockedCaseActions = {
  caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
  userUlid: '01GVHP0HP5V2A80XJZTHJH4QGD',
  userFirstName: 'John',
  userLastName: 'Doe',
  caseName: 'Investigation One',
  actions: [
    'VIEW_CASE_DETAILS',
    'UPDATE_CASE_DETAILS',
    'UPLOAD',
    'DOWNLOAD',
    'VIEW_FILES',
    'CASE_AUDIT',
    'INVITE',
  ],
  created: '2023-03-23T15:38:26.955Z',
  updated: '2023-03-23T15:38:26.955Z',
};

const mockedUsers = {
  users: [
    {
      ulid: '01GVHP0HP5V2A80XJZTHJH4QGD',
      firstName: 'Alejandro',
      lastName: 'Rosalez',
      created: '2023-03-15T03:46:23.045Z',
      updated: '2023-03-15T03:46:23.045Z',
    },
    {
      ulid: '01GVHP0HP5V2A80XJZTHJH4QGE',
      firstName: 'Carlos',
      lastName: 'Salazar',
      created: '2023-03-14T03:46:23.045Z',
      updated: '2023-03-14T03:46:23.045Z',
    },
  ],
};

const mockedCaseUsers = {
  caseUsers: [
    {
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      userUlid: '01GVHP0HP5V2A80XJZTHJH4QGE',
      caseName: 'Investigation One',
      actions: ['VIEW_CASE_DETAILS'],
      userFirstName: 'Carlos',
      userLastName: 'Salazar',
      created: '2023-03-15T03:46:23.045Z',
      updated: '2023-03-15T03:46:23.045Z',
    },
    {
      caseUlid: '01GV15BH762P6MW1QH8EQDGBFQ',
      userUlid: '01GVHP0HP5V2A80XJZTHJH4QGD',
      caseName: 'Investigation One',
      actions: ['INVITE'],
      userFirstName: 'Alejandro',
      userLastName: 'Rosalez',
      created: '2023-03-15T03:46:23.045Z',
      updated: '2023-03-15T03:46:23.045Z',
    },
  ],
};

let csvCall = -1;

const csvResult = [{ status: 'Running' }, { status: 'Running' }, 'csvresults'];

const ACTIVE_USER_ID = mockedUsers.users[0].ulid;
const OTHER_USER_ID = mockedUsers.users[1].ulid;

mockedAxios.create.mockReturnThis();
mockedAxios.request.mockImplementation((eventObj) => {
  if (eventObj.url?.endsWith(`${CASE_ID}/details`)) {
    return Promise.resolve({
      data: mockedCaseDetail,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.endsWith('100/actions')) {
    return Promise.resolve({
      data: mockedCaseActions,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.endsWith(`${CASE_ID}/userMemberships`)) {
    if (eventObj.method === 'POST') {
      return Promise.resolve({
        data: {},
        status: 200,
        statusText: 'Ok',
        headers: {},
        config: {},
      });
    }
    return Promise.resolve({
      data: mockedCaseUsers,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.endsWith(`${CASE_ID}/users/${ACTIVE_USER_ID}/memberships`)) {
    if (eventObj.method === 'DELETE') {
      return Promise.resolve({
        data: {},
        status: 200,
        statusText: 'Ok',
        headers: {},
        config: {},
      });
    }
    // put
    return Promise.resolve({
      data: {},
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.includes('files?filePath=/food/')) {
    return Promise.resolve({
      data: mockFilesFood,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.includes('files?filePath=/')) {
    return Promise.resolve({
      data: mockFilesRoot,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.endsWith('audit')) {
    return Promise.resolve({
      data: { auditId: '11111111-1111-1111-1111-111111111111' },
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else if (eventObj.url?.endsWith('csv')) {
    return Promise.resolve({
      data: csvResult[++csvCall],
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  } else {
    // get users
    return Promise.resolve({
      data: mockedUsers,
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });
  }
});

describe('CaseDetailsPage', () => {
  it('renders a case details page', async () => {
    const page = render(<CaseDetailsPage />);
    expect(page).toBeTruthy();

    const mockedCaseInfo = await screen.findByText('mocked case');
    expect(mockedCaseInfo).toBeTruthy();

    const table = await screen.findByTestId('file-table');
    const tableWrapper = wrapper(table);
    // the table exists
    expect(table).toBeTruthy();

    const folderEntry = await screen.findByTestId('food-button');
    const fileEntry = await screen.findByTestId('rootFile-file-button');
    // the folder button exists
    expect(folderEntry).toBeTruthy();
    // the file exists as a box
    expect(fileEntry).toBeTruthy();

    const textFilter = tableWrapper.findTextFilter();
    if (!textFilter) {
      fail();
    }
    const textFilterInput = textFilter.findInput();
    textFilterInput.setInputValue('food');

    // after filtering, rootFile will not be visible
    await waitFor(() => expect(screen.queryByTestId('rootFile-file-button')).toBeNull());

    // clear the filter
    textFilterInput.setInputValue('');
    await waitFor(() => expect(screen.queryByTestId('rootFile-file-button')).toBeDefined());

    // click on the folder to navigate
    fireEvent.click(folderEntry);

    // we should now see the new file "sushi.png"
    await waitFor(() => expect(screen.getByTestId('sushi.png-file-button')).toBeDefined());

    const breadcrumb = wrapper(page.container).findBreadcrumbGroup();

    await waitFor(() => expect(breadcrumb?.findBreadcrumbLinks().length).toEqual(2));

    // click the breadcrumb to return to the root
    const rootLink = await screen.findByText('/');
    fireEvent.click(rootLink);

    // should find the original rows again
    await screen.findByTestId('food-button');
    await screen.findByTestId('rootFile-file-button');
  });

  it('navigates to manage access page', async () => {
    const page = render(
      <NotificationsProvider>
        <CaseDetailsPage />
      </NotificationsProvider>
    );
    expect(page).toBeTruthy();

    const tab = await screen.findByText(caseDetailLabels.manageAccessLabel);
    fireEvent.click(tab);

    // assert autosuggest component
    const searchUserInput = await screen.findByTestId('user-search-input');
    const searchUserInputWrapper = wrapper(page.container).findAutosuggest()!;
    expect(searchUserInput).toBeTruthy();

    searchUserInputWrapper.focus();

    for (let index = 0; index < mockedUsers.users.length; index++) {
      const user = mockedUsers.users[index];
      const optionValue = `${user.firstName} ${user.lastName}`;
      expect(searchUserInputWrapper.findDropdown().findOptionByValue(optionValue)!.getElement()).toBeTruthy();
    }

    const textToInput = 'Carlos Salazar';
    const searchInput = await screen.findByRole('combobox', {
      description:
        'Members added or removed will be notified by email. Their access to case details will be based on permissions set.',
    });
    await act(async () => {
      await userEvent.type(searchInput, textToInput);
      searchUserInputWrapper.selectSuggestionByValue(textToInput);
    });

    const addCaseMemberButton = await screen.findByRole('button', { name: 'Add' });
    await act(async () => {
      addCaseMemberButton.click();
    });

    // assert multiselect component
    const permissionsWrapper = wrapper(page.container).findMultiselect()!;
    expect(permissionsWrapper).toBeTruthy();
    expect(permissionsWrapper.findTokens()).toHaveLength(1);
    permissionsWrapper.openDropdown();
    permissionsWrapper.selectOption(1);

    //assert save button
    const saveButton = await screen.findByRole('button', { name: commonLabels.saveUpdatesButton });
    expect(saveButton).toBeTruthy();
    await act(async () => {
      saveButton.click();
    });

    // assert remove button
    await waitFor(() => expect(screen.queryByTestId(`${ACTIVE_USER_ID}-remove-button`)).toBeDisabled());
    const removeButton = await screen.queryByTestId(`${OTHER_USER_ID}-remove-button`);
    expect(removeButton).toBeEnabled();
    await act(async () => {
      removeButton.click();
    });

    // assert remove modal
    const removeModalWrapper = wrapper(document.body).findModal()!;
    expect(removeModalWrapper).toBeTruthy();
    expect(removeModalWrapper.isVisible()).toBeTruthy();
    // click on dismiss button should close the modal.
    await act(async () => {
      removeModalWrapper.findDismissButton().click();
    });
    expect(removeModalWrapper.isVisible()).toBeFalsy();
    // asert modal submit button
    await act(async () => {
      removeButton.click();
    });
    const modalSubmitButton = await getByRole(removeModalWrapper.getElement(), 'button', { name: 'Remove' });
    await act(async () => {
      modalSubmitButton.click();
    });
    expect(removeModalWrapper.isVisible()).toBeFalsy();

    //assert notifications
    const notificationsWrapper = wrapper(page.container).findFlashbar()!;
    expect(notificationsWrapper).toBeTruthy();
    waitFor(() => expect(notificationsWrapper.findItems().length).toEqual(3));
    const item = notificationsWrapper.findItems()[0];
    await act(async () => {
      item.findDismissButton()!.click();
    });
  }, 15000);

  it('navigates to upload files page', async () => {
    const page = render(<CaseDetailsPage />);
    expect(page).toBeTruthy();

    const uploadButton = await page.findByTestId('upload-file-button');
    fireEvent.click(uploadButton);

    expect(push).toHaveBeenCalledWith(`/upload-files?caseId=${CASE_ID}&filePath=/`);
  });

  it('downloads a case audit', async () => {
    const page = render(<CaseDetailsPage />);
    expect(page).toBeTruthy();

    const downloadCsvButton = await screen.findByText(auditLogLabels.downloadCSVLabel);
    fireEvent.click(downloadCsvButton);

    // upload button will be disabled while in progress and then re-enabled when done
    await waitFor(() => expect(screen.queryByTestId('download-case-audit-csv-button')).toBeDisabled());
    await waitFor(() => expect(screen.queryByTestId('download-case-audit-csv-button')).toBeEnabled(), {
      timeout: 4000,
    });
  });

  it('navigates to file details page', async () => {
    const page = render(<CaseDetailsPage />);
    expect(page).toBeTruthy();

    const fileEntry = await screen.findByTestId('rootFile-file-button');
    fireEvent.click(fileEntry);

    expect(push).toHaveBeenCalledWith(
      `/file-detail?caseId=${mockFilesRoot.files[1].caseUlid}&fileId=${mockFilesRoot.files[1].ulid}`
    );
  });
});
