import { run } from '@ember/runloop';
import EmberObject, { computed } from '@ember/object';
import Evented from '@ember/object/evented';
import Service from '@ember/service';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, settled, waitUntil } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import Pretender from 'pretender';
import { resolve } from 'rsvp';
import { create } from 'ember-cli-page-object';
import form from '../../pages/components/auth-jwt';
import { ERROR_WINDOW_CLOSED, ERROR_MISSING_PARAMS, ERROR_JWT_LOGIN } from 'vault/components/auth-jwt';
const component = create(form);
const windows = [];
const buildMessage = opts => ({
  isTrusted: true,
  origin: 'https://my-vault.com',
  data: {},
  ...opts,
});
const fakeWindow = EmberObject.extend(Evented, {
  init() {
    this._super(...arguments);
    this.on('close', () => {
      this.set('closed', true);
    });
    windows.push(this);
  },
  screen: computed(function() {
    return {
      height: 600,
      width: 500,
    };
  }),
  origin: 'https://my-vault.com',
  closed: false,
});
fakeWindow.reopen({
  open() {
    return fakeWindow.create();
  },
  close() {
    windows.forEach(w => w.trigger('close'));
  },
});
const OIDC_AUTH_RESPONSE = {
  auth: {
    client_token: 'token',
  },
};
const routerStub = Service.extend({
  urlFor() {
    return 'http://example.com';
  },
});
const renderIt = async (context, path = 'jwt') => {
  let handler = (data, e) => {
    if (e && e.preventDefault) e.preventDefault();
    return resolve();
  };
  let fake = fakeWindow.create();
  context.set('window', fake);
  context.set('handler', sinon.spy(handler));
  context.set('roleName', '');
  context.set('selectedAuthPath', path);
  await render(hbs`
    
    `);
};
module('Integration | Component | auth jwt', function(hooks) {
  setupRenderingTest(hooks);
  hooks.beforeEach(function() {
    this.openSpy = sinon.spy(fakeWindow.proto(), 'open');
    this.owner.register('service:router', routerStub);
    this.server = new Pretender(function() {
      this.get('/v1/auth/:path/oidc/callback', function() {
        return [200, { 'Content-Type': 'application/json' }, JSON.stringify(OIDC_AUTH_RESPONSE)];
      });
      this.post('/v1/auth/:path/oidc/auth_url', request => {
        let body = JSON.parse(request.requestBody);
        if (body.role === 'test') {
          return [
            200,
            { 'Content-Type': 'application/json' },
            JSON.stringify({
              data: {
                auth_url: 'http://example.com',
              },
            }),
          ];
        }
        if (body.role === 'okta') {
          return [
            200,
            { 'Content-Type': 'application/json' },
            JSON.stringify({
              data: {
                auth_url: 'http://okta.com',
              },
            }),
          ];
        }
        return [400, { 'Content-Type': 'application/json' }, JSON.stringify({ errors: [ERROR_JWT_LOGIN] })];
      });
    });
  });
  hooks.afterEach(function() {
    this.openSpy.restore();
    this.server.shutdown();
  });
  test('it renders the yield', async function(assert) {
    await render(hbs`Hello!`);
    assert.equal(component.yieldContent, 'Hello!', 'yields properly');
  });
  test('jwt: it renders and makes auth_url requests', async function(assert) {
    await renderIt(this);
    await settled();
    assert.ok(component.jwtPresent, 'renders jwt field');
    assert.ok(component.rolePresent, 'renders jwt field');
    assert.equal(this.server.handledRequests.length, 1, 'request to the default path is made');
    assert.equal(this.server.handledRequests[0].url, '/v1/auth/jwt/oidc/auth_url');
    this.set('selectedAuthPath', 'foo');
    await settled();
    assert.equal(this.server.handledRequests.length, 2, 'a second request was made');
    assert.equal(
      this.server.handledRequests[1].url,
      '/v1/auth/foo/oidc/auth_url',
      'requests when path is set'
    );
  });
  test('jwt: it calls passed action on login', async function(assert) {
    await renderIt(this);
    await component.login();
    assert.ok(this.handler.calledOnce);
  });
  test('oidc: test role: it renders', async function(assert) {
    await renderIt(this);
    await settled();
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    await settled();
    assert.notOk(component.jwtPresent, 'does not show jwt input for OIDC type login');
    assert.equal(component.loginButtonText, 'Sign in with OIDC Provider');
    await component.role('okta');
    // 1 for initial render, 1 for each time role changed = 3
    assert.equal(this.server.handledRequests.length, 4, 'fetches the auth_url when the path changes');
    assert.equal(component.loginButtonText, 'Sign in with Okta', 'recognizes auth methods with certain urls');
  });
  test('oidc: it calls window.open popup window on login', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    run.cancelTimers();
    let call = this.openSpy.getCall(0);
    assert.deepEqual(
      call.args,
      ['http://example.com', 'vaultOIDCWindow', 'width=500,height=600,resizable,scrollbars=yes,top=0,left=0'],
      'called with expected args'
    );
  });
  test('oidc: it calls error handler when popup is closed', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    this.window.close();
    await settled();
    assert.equal(this.error, ERROR_WINDOW_CLOSED, 'calls onError with error string');
  });
  test('oidc: shows error when message posted with state key, wrong params', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    this.window.trigger(
      'message',
      buildMessage({ data: { source: 'oidc-callback', state: 'state', foo: 'bar' } })
    );
    run.cancelTimers();
    assert.equal(this.error, ERROR_MISSING_PARAMS, 'calls onError with params missing error');
  });
  test('oidc: storage event fires with state key, correct params', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    this.window.trigger(
      'message',
      buildMessage({
        data: {
          source: 'oidc-callback',
          path: 'foo',
          state: 'state',
          code: 'code',
        },
      })
    );
    await settled();
    assert.equal(this.selectedAuth, 'token', 'calls onSelectedAuth with token');
    assert.equal(this.token, 'token', 'calls onToken with token');
    assert.ok(this.handler.calledOnce, 'calls the onSubmit handler');
  });
  test('oidc: fails silently when event origin does not match window origin', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    this.window.trigger(
      'message',
      buildMessage({
        origin: 'http://hackerz.com',
        data: {
          source: 'oidc-callback',
          path: 'foo',
          state: 'state',
          code: 'code',
        },
      })
    );
    run.cancelTimers();
    await settled();
    assert.notOk(this.handler.called, 'should not call the submit handler');
  });
  test('oidc: fails silently when event is not trusted', async function(assert) {
    await renderIt(this);
    this.set('selectedAuthPath', 'foo');
    await component.role('test');
    component.login();
    await waitUntil(() => {
      return this.openSpy.calledOnce;
    });
    this.window.trigger(
      'message',
      buildMessage({
        isTrusted: false,
        data: {
          source: 'oidc-callback',
          path: 'foo',
          state: 'state',
          code: 'code',
        },
      })
    );
    run.cancelTimers();
    await settled();
    assert.notOk(this.handler.called, 'should not call the submit handler');
  });
});