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

import { run } from '@ember/runloop';
import { copy } from 'ember-copy';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX } from 'vault/services/auth';
import Pretender from 'pretender';

function storage() {
  return {
    items: {},
    getItem(key) {
      var item = this.items[key];
      return item && JSON.parse(item);
    },

    setItem(key, val) {
      return (this.items[key] = JSON.stringify(val));
    },

    removeItem(key) {
      delete this.items[key];
    },

    keys() {
      return Object.keys(this.items);
    },
  };
}

const ROOT_TOKEN_RESPONSE = {
  request_id: 'e6674d7f-c96f-d51f-4463-cc95f0ad307e',
  lease_id: '',
  renewable: false,
  lease_duration: 0,
  data: {
    accessor: '1dd25306-fdb9-0f43-8169-48ad702041b0',
    creation_time: 1477671134,
    creation_ttl: 0,
    display_name: 'root',
    explicit_max_ttl: 0,
    id: '',
    meta: null,
    num_uses: 0,
    orphan: true,
    path: 'auth/token/root',
    policies: ['root'],
    ttl: 0,
  },
  wrap_info: null,
  warnings: null,
  auth: null,
};

const TOKEN_NON_ROOT_RESPONSE = function () {
  return {
    request_id: '3ca32cd9-fd40-891d-02d5-ea23138e8642',
    lease_id: '',
    renewable: false,
    lease_duration: 0,
    data: {
      accessor: '4ef32471-a94c-79ee-c290-aeba4d63bdc9',
      creation_time: Math.floor(Date.now() / 1000),
      creation_ttl: 2764800,
      display_name: 'token',
      explicit_max_ttl: 0,
      id: '6d83e912-1b21-9df9-b51a-d201b709f3d5',
      meta: null,
      num_uses: 0,
      orphan: false,
      path: 'auth/token/create',
      policies: ['default', 'userpass'],
      renewable: true,
      ttl: 2763327,
    },
    wrap_info: null,
    warnings: null,
    auth: null,
  };
};

const USERPASS_RESPONSE = {
  request_id: '7e5e8d3d-599e-6ef7-7570-f7057fc7c53d',
  lease_id: '',
  renewable: false,
  lease_duration: 0,
  data: null,
  wrap_info: null,
  warnings: null,
  auth: {
    client_token: '5313ff81-05cb-699f-29d1-b82b4e2906dc',
    accessor: '5c5303e7-56d6-ea13-72df-d85411bd9a7d',
    policies: ['default'],
    metadata: {
      username: 'matthew',
    },
    lease_duration: 2764800,
    renewable: true,
  },
};

const GITHUB_RESPONSE = {
  request_id: '4913f9cd-a95f-d1f9-5746-4c3af4e15660',
  lease_id: '',
  renewable: false,
  lease_duration: 0,
  data: null,
  wrap_info: null,
  warnings: null,
  auth: {
    client_token: '0d39b535-598e-54d9-96e3-97493492a5f7',
    accessor: 'd8cd894f-bedf-5ce3-f1b5-98f7c6cf8ab4',
    policies: ['default'],
    metadata: {
      org: 'hashicorp',
      username: 'meirish',
    },
    lease_duration: 2764800,
    renewable: true,
  },
};

module('Integration | Service | auth', function (hooks) {
  setupTest(hooks);

  hooks.beforeEach(function () {
    this.owner.lookup('service:flash-messages').registerTypes(['warning']);
    this.store = storage();
    this.memStore = storage();
    this.server = new Pretender(function () {
      this.get('/v1/auth/token/lookup-self', function (request) {
        const resp = copy(ROOT_TOKEN_RESPONSE, true);
        resp.id = request.requestHeaders['X-Vault-Token'];
        resp.data.id = request.requestHeaders['X-Vault-Token'];
        return [200, {}, resp];
      });
      this.post('/v1/auth/userpass/login/:username', function (request) {
        const { username } = request.params;
        const resp = copy(USERPASS_RESPONSE, true);
        resp.auth.metadata.username = username;
        return [200, {}, resp];
      });

      this.post('/v1/auth/github/login', function () {
        const resp = copy(GITHUB_RESPONSE, true);
        return [200, {}, resp];
      });
    });

    this.server.prepareBody = function (body) {
      return body ? JSON.stringify(body) : '{"error": "not found"}';
    };

    this.server.prepareHeaders = function (headers) {
      headers['content-type'] = 'application/javascript';
      return headers;
    };
  });

  hooks.afterEach(function () {
    this.server.shutdown();
  });

  test('token authentication: root token', function (assert) {
    assert.expect(6);
    const done = assert.async();
    const self = this;
    const service = this.owner.factoryFor('service:auth').create({
      storage(tokenName) {
        if (
          tokenName &&
          tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 &&
          this.environment() !== 'development'
        ) {
          return self.memStore;
        } else {
          return self.store;
        }
      },
    });
    run(() => {
      service
        .authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } })
        .then(() => {
          const clusterTokenName = service.get('currentTokenName');
          const clusterToken = service.get('currentToken');
          const authData = service.get('authData');

          const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`;
          assert.strictEqual(clusterToken, 'test', 'token is saved properly');
          assert.strictEqual(
            `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`,
            clusterTokenName,
            'token name is saved properly'
          );
          assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
          assert.strictEqual(
            ROOT_TOKEN_RESPONSE.data.display_name,
            authData.displayName,
            'displayName is saved properly'
          );
          assert.ok(
            this.memStore.keys().includes(expectedTokenName),
            'root token is stored in the memory store'
          );
          assert.strictEqual(this.store.keys().length, 0, 'normal storage is empty');
        })
        .finally(() => {
          done();
        });
    });
  });

  test('token authentication: root token in ember development environment', async function (assert) {
    const self = this;
    const service = this.owner.factoryFor('service:auth').create({
      storage(tokenName) {
        if (
          tokenName &&
          tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 &&
          this.environment() !== 'development'
        ) {
          return self.memStore;
        } else {
          return self.store;
        }
      },
      environment: () => 'development',
    });
    await service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } });
    const clusterTokenName = service.get('currentTokenName');
    const clusterToken = service.get('currentToken');
    const authData = service.get('authData');

    const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`;
    assert.strictEqual(clusterToken, 'test', 'token is saved properly');
    assert.strictEqual(
      `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`,
      clusterTokenName,
      'token name is saved properly'
    );
    assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
    assert.strictEqual(
      ROOT_TOKEN_RESPONSE.data.display_name,
      authData.displayName,
      'displayName is saved properly'
    );
    assert.ok(this.store.keys().includes(expectedTokenName), 'root token is stored in the store');
    assert.strictEqual(this.memStore.keys().length, 0, 'mem storage is empty');
  });

  test('github authentication', function (assert) {
    assert.expect(6);
    const done = assert.async();
    const service = this.owner.factoryFor('service:auth').create({
      storage: (type) => (type === 'memory' ? this.memStore : this.store),
    });

    run(() => {
      service.authenticate({ clusterId: '1', backend: 'github', data: { token: 'test' } }).then(() => {
        const clusterTokenName = service.get('currentTokenName');
        const clusterToken = service.get('currentToken');
        const authData = service.get('authData');
        const expectedTokenName = `${TOKEN_PREFIX}github${TOKEN_SEPARATOR}1`;

        assert.strictEqual(GITHUB_RESPONSE.auth.client_token, clusterToken, 'token is saved properly');
        assert.strictEqual(expectedTokenName, clusterTokenName, 'token name is saved properly');
        assert.strictEqual(authData.backend.type, 'github', 'backend is saved properly');
        assert.strictEqual(
          GITHUB_RESPONSE.auth.metadata.org + '/' + GITHUB_RESPONSE.auth.metadata.username,
          authData.displayName,
          'displayName is saved properly'
        );
        assert.strictEqual(this.memStore.keys().length, 0, 'mem storage is empty');
        assert.ok(this.store.keys().includes(expectedTokenName), 'normal storage contains the token');
        done();
      });
    });
  });

  test('userpass authentication', function (assert) {
    assert.expect(4);
    const done = assert.async();
    const service = this.owner.factoryFor('service:auth').create({ storage: () => this.store });
    run(() => {
      service
        .authenticate({
          clusterId: '1',
          backend: 'userpass',
          data: { username: USERPASS_RESPONSE.auth.metadata.username, password: 'passoword' },
        })
        .then(() => {
          const clusterTokenName = service.get('currentTokenName');
          const clusterToken = service.get('currentToken');
          const authData = service.get('authData');

          assert.strictEqual(USERPASS_RESPONSE.auth.client_token, clusterToken, 'token is saved properly');
          assert.strictEqual(
            `${TOKEN_PREFIX}userpass${TOKEN_SEPARATOR}1`,
            clusterTokenName,
            'token name is saved properly'
          );
          assert.strictEqual(authData.backend.type, 'userpass', 'backend is saved properly');
          assert.strictEqual(
            USERPASS_RESPONSE.auth.metadata.username,
            authData.displayName,
            'displayName is saved properly'
          );
          done();
        });
    });
  });

  test('token auth expiry with non-root token', function (assert) {
    assert.expect(5);
    const tokenResp = TOKEN_NON_ROOT_RESPONSE();
    this.server.map(function () {
      this.get('/v1/auth/token/lookup-self', function (request) {
        const resp = copy(tokenResp, true);
        resp.id = request.requestHeaders['X-Vault-Token'];
        resp.data.id = request.requestHeaders['X-Vault-Token'];
        return [200, {}, resp];
      });
    });

    const done = assert.async();
    const service = this.owner.factoryFor('service:auth').create({ storage: () => this.store });
    run(() => {
      service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => {
        const clusterTokenName = service.get('currentTokenName');
        const clusterToken = service.get('currentToken');
        const authData = service.get('authData');

        assert.strictEqual(clusterToken, 'test', 'token is saved properly');
        assert.strictEqual(
          `${TOKEN_PREFIX}token${TOKEN_SEPARATOR}1`,
          clusterTokenName,
          'token name is saved properly'
        );
        assert.strictEqual(authData.backend.type, 'token', 'backend is saved properly');
        assert.strictEqual(
          authData.displayName,
          tokenResp.data.display_name,
          'displayName is saved properly'
        );
        assert.false(service.get('tokenExpired'), 'token is not expired');
        done();
      });
    });
  });
});
