import type { Router as RemixRouter } from '@remix-run/router';
import { screen, waitFor, within } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { CREATE_BLUEPRINT, EDIT_BLUEPRINT } from '../../../../../constants';
import { mockBlueprintIds } from '../../../../fixtures/blueprints';
import { usersCreateBlueprintRequest } from '../../../../fixtures/editMode';
import {
  blueprintRequest,
  clickBack,
  clickNext,
  clickRegisterLater,
  enterBlueprintName,
  getNextButton,
  interceptBlueprintRequest,
  interceptEditBlueprintRequest,
  openAndDismissSaveAndBuildModal,
  renderCreateMode,
  renderEditMode,
  selectGuestImageTarget,
  verifyCancelButton,
} from '../../wizardTestUtils';

let router: RemixRouter | undefined = undefined;

const validUserName = 'best';
const validSshKey = 'ssh-rsa d';
const validPassword = 'validPassword';
const invalidPassword = 'inval';
const rachelPasswd = 'rachelPass';
const chandlerPasswd = 'chandlerPass';

const goToUsersStep = async () => {
  await clickNext(); // Registration
  await clickRegisterLater();
  await clickNext(); // OpenSCAP
  await clickNext(); // File system configuration
  await clickNext(); // Snapshots
  await clickNext(); // Custom repositories
  await clickNext(); // Additional packages
  await clickNext(); // Users
};

const goToReviewStep = async () => {
  await clickNext(); // Timezone
  await clickNext(); // Locale
  await clickNext(); // Hostname
  await clickNext(); // Kernel
  await clickNext(); // Firewall
  await clickNext(); // Services
  await clickNext(); // First boot
  await clickNext(); // Details
  await enterBlueprintName();
  await clickNext(); // Review
};

const addAzureTarget = async () => {
  const user = userEvent.setup();
  await waitFor(() =>
    user.click(screen.getByRole('button', { name: /Microsoft Azure/i }))
  );
  await clickNext();

  const azureSourceDropdown = await screen.findByPlaceholderText(
    /select source/i
  );
  await waitFor(() => user.click(azureSourceDropdown));
  const azureSource = await screen.findByRole('option', {
    name: /azureSource1/i,
  });
  await waitFor(() => user.click(azureSource));

  const resourceGroupDropdown = await screen.findByPlaceholderText(
    /select resource group/i
  );
  await waitFor(() => user.click(resourceGroupDropdown));
  await waitFor(async () =>
    user.click(await screen.findByLabelText('Resource group myResourceGroup1'))
  );
};

const clickRevisitButton = async () => {
  const user = userEvent.setup();
  const expandable = await screen.findByTestId('users-expandable');
  const revisitButton = await within(expandable).findByTestId('revisit-users');
  await waitFor(() => user.click(revisitButton));
};

const clickAddUser = async () => {
  const user = userEvent.setup();
  const addUser = await screen.findByRole('button', { name: /add a user/i });
  expect(addUser).toBeEnabled();
  await waitFor(() => user.click(addUser));
};

const addAnotherUser = async () => {
  const user = userEvent.setup();
  const addUser = await screen.findByRole('button', { name: /add tab/i });
  expect(addUser).toBeEnabled();
  await waitFor(() => user.click(addUser));
};

const addAndFillThreeUsers = async () => {
  await clickAddUser();
  await addUserName('rachel');
  await addSshKey('ssh-rsa rachel');
  await addPasswordByUserIndex('rachelPass', 0);
  await checkAdminCheckbox();
  await addUserGroupByUserIndex('users', 0);
  await addUserGroupByUserIndex('widget', 0);

  await addAnotherUser();
  await switchToNewUser();
  await addUserName('monica');

  await addAnotherUser();
  await switchToNewUser();
  await addUserName('chandler');
  await addSshKey('ssh-rsa chandler');
  await addPasswordByUserIndex('chandlerPass', 2);
  await addUserGroupByUserIndex('group', 2);
};

const switchToNewUser = async () => {
  const user = userEvent.setup();
  const newUserButton = await screen.findByRole('tab', { name: /user tab/i });
  await waitFor(() => user.click(newUserButton));
};

const closeNthTab = async (index: number) => {
  const user = userEvent.setup();
  const tabs = await screen.findAllByRole('presentation');
  const closeTabButton = await within(tabs[index]).findByRole('button');
  await waitFor(() => user.click(closeTabButton));
  await clickRemoveUser();
};

const clickRemoveUser = async () => {
  const user = userEvent.setup();
  const removeUserModalButton = await screen.findByRole('button', {
    name: /Remove user/,
  });
  await waitFor(() => user.click(removeUserModalButton));
};

const addSshKey = async (sshKey: string) => {
  const user = userEvent.setup();
  const enterSshKey = await screen.findByRole('textbox', {
    name: /public SSH key/i,
  });
  await waitFor(() => user.type(enterSshKey, sshKey));
  await waitFor(() => expect(enterSshKey).toHaveValue(sshKey));
};

const addUserName = async (userName: string) => {
  const user = userEvent.setup();
  const enterUserName = screen.getByRole('textbox', {
    name: /blueprint user name/i,
  });
  await waitFor(() => user.type(enterUserName, userName));
  await waitFor(() => expect(enterUserName).toHaveValue(userName));
};

const addPasswordByUserIndex = async (value: string, index: number) => {
  const user = userEvent.setup();
  const passwordInputs = screen.getAllByPlaceholderText(/enter password/i);
  await waitFor(() => user.type(passwordInputs[index], value));
};

const getAdminCheckbox = async () => {
  const adminCheckbox = await screen.findByRole('checkbox', {
    name: /administrator/i,
  });
  return adminCheckbox;
};

const checkAdminCheckbox = async () => {
  const user = userEvent.setup();
  const adminCheckbox = await getAdminCheckbox();
  await waitFor(() => user.click(adminCheckbox));
};

const addUserGroupByUserIndex = async (group: string, index: number) => {
  const user = userEvent.setup();
  const userGroupInputs = await screen.findAllByPlaceholderText(
    'Add user group'
  );
  await waitFor(() => user.click(userGroupInputs[index]));
  await waitFor(() => user.type(userGroupInputs[index], group));
  const addGroup = await screen.findByRole('button', {
    name: /Add user group/,
  });
  await waitFor(() => user.click(addGroup));
};

const removeUserGroup = async (group: string) => {
  const user = userEvent.setup();

  const removeGroupButton = await screen.findByRole('button', {
    name: `Close ${group}`,
  });
  await waitFor(() => user.click(removeGroupButton));
};

describe('Step Users', () => {
  beforeEach(async () => {
    vi.clearAllMocks();
    router = undefined;
  });

  test('clicking Next loads Timezone', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickNext();
    await screen.findByRole('heading', {
      name: 'Timezone',
    });
  });

  test('clicking Back loads Additional packages', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickBack();
    await screen.findByRole('heading', { name: 'Additional packages' });
  });

  test('clicking Cancel loads landing page', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await verifyCancelButton(router);
  });

  test('with invalid name', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName('.');
    await clickNext();
    await waitFor(() => expect(screen.getByText(/invalid user name/i)));
  });

  test('with invalid SSH key', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addSshKey('ssh');
    await clickNext();
    await waitFor(() => expect(screen.getByText(/invalid ssh key/i)));
  });

  test('Azure target with invalid password', async () => {
    await renderCreateMode();
    await addAzureTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName(validUserName);
    await addPasswordByUserIndex(invalidPassword, 0);

    const invalidUserMessage = screen.getByText(
      /Password must be at least 6 characters long/i
    );
    const warningUserMessage = screen.getByText(
      /Must include at least 3 of the following: lowercase letters, uppercase letters, numbers, symbols/i
    );
    await waitFor(() => expect(invalidUserMessage));
    await waitFor(() => expect(warningUserMessage));

    const nextButton = await getNextButton();
    await waitFor(() => expect(nextButton).toBeDisabled());
  });

  test('with invalid password', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName(validUserName);
    await addPasswordByUserIndex(invalidPassword, 0);

    const invalidUserMessage = screen.getByText(
      /Password must be at least 6 characters long/i
    );
    await waitFor(() => expect(invalidUserMessage));

    const nextButton = await getNextButton();
    await waitFor(() => expect(nextButton).toBeDisabled());
  });

  test('user groups can be added and removed', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserGroupByUserIndex('users', 0);
    await addUserGroupByUserIndex('widget', 0);
    await removeUserGroup('users');
    expect(screen.queryByText('users')).not.toBeInTheDocument();
  });

  test('adding wheel group checks Administrator checkbox', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    const adminCheckbox = await getAdminCheckbox();

    // Adding wheel group via groups input
    await addUserGroupByUserIndex('wheel', 0);
    expect(adminCheckbox).toBeChecked();

    await removeUserGroup('wheel');
    expect(adminCheckbox).not.toBeChecked();

    // Adding wheel group via Admin checkbox
    await checkAdminCheckbox();
    expect(adminCheckbox).toBeChecked();
    await screen.findByText('wheel');

    await checkAdminCheckbox();
    expect(adminCheckbox).not.toBeChecked();
    expect(screen.queryByText('wheel')).not.toBeInTheDocument();
  });

  test('one valid and one invalid user', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName(validUserName);

    const nextButton = await getNextButton();
    expect(nextButton).toBeEnabled();

    await addAnotherUser();
    await switchToNewUser();

    await addUserName('s');
    expect(nextButton).toBeDisabled();

    // remove invalid user and expect Next to get enabled
    await closeNthTab(1);
    expect(nextButton).toBeEnabled();
  });

  test('revisit step button on Review works', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName(validUserName);
    await goToReviewStep();
    await clickRevisitButton();
    await screen.findByRole('heading', { name: /Users/ });
  });
});

describe('User request generated correctly', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  test('add a user', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName(validUserName);
    await addSshKey(validSshKey);
    await addPasswordByUserIndex(validPassword, 0);
    await checkAdminCheckbox();
    await addUserGroupByUserIndex('users', 0);
    await addUserGroupByUserIndex('widget', 0);
    await goToReviewStep();
    // informational modal pops up in the first test only as it's tied
    // to a 'imageBuilder.saveAndBuildModalSeen' variable in localStorage
    await openAndDismissSaveAndBuildModal();
    const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);

    const expectedRequest = {
      ...blueprintRequest,
      customizations: {
        users: [
          {
            name: validUserName,
            ssh_key: validSshKey,
            password: validPassword,
            groups: ['wheel', 'users', 'widget'],
            hasPassword: true,
          },
        ],
      },
    };

    await waitFor(() => {
      expect(receivedRequest).toEqual(expectedRequest);
    });
  });

  test('remove a user', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();
    await clickAddUser();
    await addUserName('test');
    await addSshKey('ssh-rsa');
    await addUserGroupByUserIndex('users', 0);
    await addUserGroupByUserIndex('widget', 0);
    await closeNthTab(0);
    await screen.findByText(/add a user to your image/i);
    await goToReviewStep();
    const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);

    const expectedRequest = {
      ...blueprintRequest,
      customizations: {},
    };

    await waitFor(() => {
      expect(receivedRequest).toEqual(expectedRequest);
    });
  });

  test('add multiple users', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();

    await addAndFillThreeUsers();

    await goToReviewStep();
    const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);

    const expectedRequest = {
      ...blueprintRequest,
      customizations: {
        users: [
          {
            name: 'rachel',
            ssh_key: 'ssh-rsa rachel',
            password: rachelPasswd,
            groups: ['wheel', 'users', 'widget'],
            hasPassword: true,
          },
          {
            name: 'monica',
            hasPassword: false,
          },
          {
            name: 'chandler',
            ssh_key: 'ssh-rsa chandler',
            password: chandlerPasswd,
            groups: ['group'],
            hasPassword: true,
          },
        ],
      },
    };

    await waitFor(() => {
      expect(receivedRequest).toEqual(expectedRequest);
    });
  });

  test('remove multiple users', async () => {
    await renderCreateMode();
    await selectGuestImageTarget();
    await goToUsersStep();

    await addAndFillThreeUsers();
    await closeNthTab(2);
    await closeNthTab(1);
    await closeNthTab(0);

    await goToReviewStep();
    const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);

    const expectedRequest = {
      ...blueprintRequest,
      customizations: {},
    };

    await waitFor(() => {
      expect(receivedRequest).toEqual(expectedRequest);
    });
  });
});

describe('Users edit mode', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  test('edit mode works', async () => {
    const id = mockBlueprintIds['users'];
    await renderEditMode(id);

    // starts on review step
    const receivedRequest = await interceptEditBlueprintRequest(
      `${EDIT_BLUEPRINT}/${id}`
    );
    const expectedRequest = usersCreateBlueprintRequest;
    expect(receivedRequest).toEqual(expectedRequest);
  });
});
