/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

/* eslint-disable qunit/require-expect */
import { currentURL } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
import Clients from 'nomad-ui/tests/pages/jobs/job/clients';
import setPolicy from 'nomad-ui/tests/utils/set-policy';

let job;
let clients;

const makeSearchableClients = (server, job) => {
  Array(10)
    .fill(null)
    .map((_, index) => {
      const node = server.create('node', {
        id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`,
        datacenter: 'dc1',
        status: 'ready',
      });
      server.create('allocation', { jobId: job.id, nodeId: node.id });
    });
};

module('Acceptance | job clients', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);

  hooks.beforeEach(function () {
    setPolicy({
      id: 'node-read',
      name: 'node-read',
      rulesJSON: {
        Node: {
          Policy: 'read',
        },
      },
    });

    server.createList('node-pool', 5);
    clients = server.createList('node', 12, {
      datacenter: 'dc1',
      status: 'ready',
    });
    // Job with 1 task group.
    job = server.create('job', {
      status: 'running',
      datacenters: ['dc1'],
      type: 'sysbatch',
      resourceSpec: ['M: 256, C: 500'],
      createAllocations: false,
    });
    clients.forEach((c) => {
      server.create('allocation', { jobId: job.id, nodeId: c.id });
    });

    // Create clients without allocations to have some 'not scheduled' job status.
    clients = clients.concat(
      server.createList('node', 3, {
        datacenter: 'dc1',
        status: 'ready',
      })
    );
  });

  test('it passes an accessibility audit', async function (assert) {
    await Clients.visit({ id: job.id });
    await a11yAudit(assert);
  });

  test('lists all clients for the job', async function (assert) {
    await Clients.visit({ id: job.id });
    assert.equal(Clients.clients.length, 15, 'Clients are shown in a table');

    const clientIDs = clients.sortBy('id').map((c) => c.id);
    const clientsInTable = Clients.clients.map((c) => c.id).sort();
    assert.deepEqual(clientsInTable, clientIDs);

    assert.equal(document.title, `Job ${job.name} clients - Nomad`);
  });

  test('dates have tooltip', async function (assert) {
    await Clients.visit({ id: job.id });

    Clients.clients.forEach((clientRow, index) => {
      const jobStatus = Clients.clientFor(clientRow.id).status;

      ['createTime', 'modifyTime'].forEach((col) => {
        if (jobStatus === 'not scheduled') {
          /* eslint-disable-next-line qunit/no-conditional-assertions */
          assert.equal(
            clientRow[col].text,
            '-',
            `row ${index} doesn't have ${col} tooltip`
          );
          /* eslint-disable-next-line qunit/no-early-return */
          return;
        }

        const hasTooltip = clientRow[col].tooltip.isPresent;
        const tooltipText = clientRow[col].tooltip.text;
        assert.true(hasTooltip, `row ${index} has ${col} tooltip`);
        assert.ok(
          tooltipText,
          `row ${index} has ${col} tooltip content ${tooltipText}`
        );
      });
    });
  });

  test('clients table is sortable', async function (assert) {
    await Clients.visit({ id: job.id });
    await Clients.sortBy('node.name');

    assert.equal(
      currentURL(),
      `/jobs/${job.id}/clients?desc=true&sort=node.name`,
      'the URL persists the sort parameter'
    );

    const sortedClients = clients.sortBy('name').reverse();
    Clients.clients.forEach((client, index) => {
      const shortId = sortedClients[index].id.split('-')[0];
      assert.equal(
        client.shortId,
        shortId,
        `Client ${index} is ${shortId} with name ${sortedClients[index].name}`
      );
    });
  });

  test('clients table is searchable', async function (assert) {
    makeSearchableClients(server, job);

    await Clients.visit({ id: job.id });
    await Clients.search('ffffff');

    assert.equal(Clients.clients.length, 5, 'List is filtered by search term');
  });

  test('when a search yields no results, the search box remains', async function (assert) {
    makeSearchableClients(server, job);

    await Clients.visit({ id: job.id });
    await Clients.search('^nothing will ever match this long regex$');

    assert.equal(
      Clients.emptyState.headline,
      'No Matches',
      'List is empty and the empty state is about search'
    );

    assert.ok(Clients.hasSearchBox, 'Search box is still shown');
  });

  test('when the job for the clients is not found, an error message is shown, but the URL persists', async function (assert) {
    await Clients.visit({ id: 'not-a-real-job' });

    assert.equal(
      server.pretender.handledRequests
        .filter((request) => !request.url.includes('policy'))
        .findBy('status', 404).url,
      '/v1/job/not-a-real-job',
      'A request to the nonexistent job is made'
    );
    assert.equal(
      currentURL(),
      '/jobs/not-a-real-job/clients',
      'The URL persists'
    );
    assert.ok(Clients.error.isPresent, 'Error message is shown');
    assert.equal(Clients.error.title, 'Not Found', 'Error message is for 404');
  });

  test('clicking row goes to client details', async function (assert) {
    const client = clients[0];

    await Clients.visit({ id: job.id });
    await Clients.clientFor(client.id).click();
    assert.equal(currentURL(), `/clients/${client.id}`);

    await Clients.visit({ id: job.id });
    await Clients.clientFor(client.id).visit();
    assert.equal(currentURL(), `/clients/${client.id}`);

    await Clients.visit({ id: job.id });
    await Clients.clientFor(client.id).visitRow();
    assert.equal(currentURL(), `/clients/${client.id}`);
  });

  testFacet('Job Status', {
    facet: Clients.facets.jobStatus,
    paramName: 'jobStatus',
    expectedOptions: [
      'Queued',
      'Not Scheduled',
      'Starting',
      'Running',
      'Complete',
      'Degraded',
      'Failed',
      'Lost',
      'Unknown',
    ],
    async beforeEach() {
      await Clients.visit({ id: job.id });
    },
  });

  function testFacet(label, { facet, paramName, beforeEach, expectedOptions }) {
    test(`the ${label} facet has the correct options`, async function (assert) {
      await beforeEach();
      await facet.toggle();

      let expectation;
      if (typeof expectedOptions === 'function') {
        expectation = expectedOptions();
      } else {
        expectation = expectedOptions;
      }

      assert.deepEqual(
        facet.options.map((option) => option.label.trim()),
        expectation,
        `Options for facet ${paramName} are as expected`
      );
    });

    // TODO: add facet tests for actual list filtering
  }
});
