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

/* eslint-disable qunit/require-expect */
/* eslint-disable qunit/no-conditional-assertions */
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 Versions from 'nomad-ui/tests/pages/jobs/job/versions';
import Layout from 'nomad-ui/tests/pages/layout';
import moment from 'moment';

let job;
let namespace;
let versions;

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

  hooks.beforeEach(async function () {
    server.create('node-pool');
    server.create('namespace');
    namespace = server.create('namespace');

    job = server.create('job', {
      namespaceId: namespace.id,
      createAllocations: false,
    });
    versions = server.db.jobVersions.where({ jobId: job.id });

    const managementToken = server.create('token');
    window.localStorage.nomadTokenSecret = managementToken.secretId;

    await Versions.visit({ id: `${job.id}@${namespace.id}` });
  });

  test('it passes an accessibility audit', async function (assert) {
    await a11yAudit(assert);
  });

  test('/jobs/:id/versions should list all job versions', async function (assert) {
    assert.equal(
      Versions.versions.length,
      versions.length,
      'Each version gets a row in the timeline'
    );
    assert.equal(document.title, `Job ${job.name} versions - Nomad`);
  });

  test('each version mentions the version number, the stability, and the submitted time', async function (assert) {
    const version = versions.sortBy('submitTime').reverse()[0];
    const formattedSubmitTime = moment(version.submitTime / 1000000).format(
      "MMM DD, 'YY HH:mm:ss ZZ"
    );
    const versionRow = Versions.versions.objectAt(0);

    assert.ok(
      versionRow.text.includes(`Version #${version.version}`),
      'Version #'
    );
    assert.equal(versionRow.stability, version.stable.toString(), 'Stability');
    assert.equal(versionRow.submitTime, formattedSubmitTime, 'Submit time');
  });

  test('all versions but the current one have a button to revert to that version', async function (assert) {
    let versionRowToRevertTo;

    Versions.versions.forEach((versionRow) => {
      if (versionRow.number === job.version) {
        assert.ok(versionRow.revertToButton.isHidden);
      } else {
        assert.ok(versionRow.revertToButton.isPresent);

        versionRowToRevertTo = versionRow;
      }
    });

    if (versionRowToRevertTo) {
      const versionNumberRevertingTo = versionRowToRevertTo.number;
      await versionRowToRevertTo.revertToButton.idle();
      await versionRowToRevertTo.revertToButton.confirm();

      const revertRequest = this.server.pretender.handledRequests.find(
        (request) => request.url.includes('revert')
      );

      assert.equal(
        revertRequest.url,
        `/v1/job/${job.id}/revert?namespace=${namespace.id}`
      );

      assert.deepEqual(JSON.parse(revertRequest.requestBody), {
        JobID: job.id,
        JobVersion: versionNumberRevertingTo,
      });

      assert.equal(currentURL(), `/jobs/${job.id}@${namespace.id}`);
    }
  });

  test('when reversion fails, the error message from the API is piped through to the alert', async function (assert) {
    const versionRowToRevertTo = Versions.versions.filter(
      (versionRow) => versionRow.revertToButton.isPresent
    )[0];

    if (versionRowToRevertTo) {
      const message = 'A plaintext error message';
      server.pretender.post('/v1/job/:id/revert', () => [500, {}, message]);

      await versionRowToRevertTo.revertToButton.idle();
      await versionRowToRevertTo.revertToButton.confirm();

      assert.ok(Layout.inlineError.isShown);
      assert.ok(Layout.inlineError.isDanger);
      assert.ok(Layout.inlineError.title.includes('Could Not Revert'));
      assert.equal(Layout.inlineError.message, message);

      await Layout.inlineError.dismiss();

      assert.notOk(Layout.inlineError.isShown);
    } else {
      assert.expect(0);
    }
  });

  test('when reversion has no effect, the error message explains', async function (assert) {
    const versionRowToRevertTo = Versions.versions.filter(
      (versionRow) => versionRow.revertToButton.isPresent
    )[0];

    if (versionRowToRevertTo) {
      // The default Mirage implementation updates the job version as passed in, this does nothing
      server.pretender.post('/v1/job/:id/revert', () => [200, {}, '{}']);

      await versionRowToRevertTo.revertToButton.idle();
      await versionRowToRevertTo.revertToButton.confirm();

      assert.ok(Layout.inlineError.isShown);
      assert.ok(Layout.inlineError.isWarning);
      assert.ok(Layout.inlineError.title.includes('Reversion Had No Effect'));
      assert.equal(
        Layout.inlineError.message,
        'Reverting to an identical older version doesn’t produce a new version'
      );
    } else {
      assert.expect(0);
    }
  });

  test('when the job for the versions is not found, an error message is shown, but the URL persists', async function (assert) {
    await Versions.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/versions',
      'The URL persists'
    );
    assert.ok(Versions.error.isPresent, 'Error message is shown');
    assert.equal(Versions.error.title, 'Not Found', 'Error message is for 404');
  });
});

module('Acceptance | job versions (with client token)', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);

  hooks.beforeEach(async function () {
    server.create('node-pool');
    job = server.create('job', { createAllocations: false });
    versions = server.db.jobVersions.where({ jobId: job.id });

    server.create('token');
    const clientToken = server.create('token');
    window.localStorage.nomadTokenSecret = clientToken.secretId;

    await Versions.visit({ id: job.id });
  });

  test('reversion buttons are disabled when the token lacks permissions', async function (assert) {
    const versionRowWithReversion = Versions.versions.filter(
      (versionRow) => versionRow.revertToButton.isPresent
    )[0];

    if (versionRowWithReversion) {
      assert.ok(versionRowWithReversion.revertToButtonIsDisabled);
    } else {
      assert.expect(0);
    }

    window.localStorage.clear();
  });

  test('reversion buttons are available when the client token has permissions', async function (assert) {
    const REVERT_NAMESPACE = 'revert-namespace';
    window.localStorage.clear();
    const clientToken = server.create('token');

    server.create('namespace', { id: REVERT_NAMESPACE });

    const job = server.create('job', {
      groupCount: 0,
      createAllocations: false,
      shallow: true,
      noActiveDeployment: true,
      namespaceId: REVERT_NAMESPACE,
    });

    const policy = server.create('policy', {
      id: 'something',
      name: 'something',
      rulesJSON: {
        Namespaces: [
          {
            Name: REVERT_NAMESPACE,
            Capabilities: ['submit-job'],
          },
        ],
      },
    });

    clientToken.policyIds = [policy.id];
    clientToken.save();

    window.localStorage.nomadTokenSecret = clientToken.secretId;

    versions = server.db.jobVersions.where({ jobId: job.id });
    await Versions.visit({ id: job.id, namespace: REVERT_NAMESPACE });
    const versionRowWithReversion = Versions.versions.filter(
      (versionRow) => versionRow.revertToButton.isPresent
    )[0];

    if (versionRowWithReversion) {
      assert.ok(versionRowWithReversion.revertToButtonIsDisabled);
    } else {
      assert.expect(0);
    }
  });
});
