import {UserProfile} from 'app/common/LoginSessionAPI';
import {AccessOptionWithRole} from 'app/gen-server/entity/Organization';
import axios from 'axios';
import {AxiosRequestConfig} from 'axios';
import {assert} from 'chai';
import omit = require('lodash/omit');
import {TestServer} from 'test/gen-server/apiUtils';
import * as testUtils from 'test/server/testUtils';

const nobody: AxiosRequestConfig = {
  responseType: 'json',
  validateStatus: (status: number) => true
};

describe('ApiSession', function() {

  let server: TestServer;
  let serverUrl: string;
  testUtils.setTmpLogLevel('error');

  const regular = 'chimpy@getgrist.com';

  beforeEach(async function() {
    this.timeout(5000);
    server = new TestServer(this);
    serverUrl = await server.start();
  });

  afterEach(async function() {
    await server.stop();
  });

  it('GET /api/session/access/active returns user and org (with access)', async function() {
    const cookie = await server.getCookieLogin('nasa', {email: regular, name: 'Chimpy'});

    const resp = await axios.get(`${serverUrl}/o/nasa/api/session/access/active`, cookie);
    assert.equal(resp.status, 200);
    assert.sameMembers(['user', 'org'], Object.keys(resp.data));
    assert.deepEqual(omit(resp.data.user, ['helpScoutSignature', 'ref']), {
      id: await server.dbManager.testGetId("Chimpy"),
      email: "chimpy@getgrist.com",
      name: "Chimpy",
      picture: null,
    });
    assert.deepEqual(omit(resp.data.org, ['billingAccount', 'createdAt', 'updatedAt']), {
      id: await server.dbManager.testGetId("NASA"),
      name: "NASA",
      access: "owners",
      domain: "nasa",
      host: null,
      owner: null
    });
  });

  it('GET /api/session/access/active returns org with billing account information', async function() {
    const cookie = await server.getCookieLogin('nasa', {email: regular, name: 'Chimpy'});

    // Make Chimpy a billing account manager for NASA.
    await server.addBillingManager('Chimpy', 'nasa');

    const resp = await axios.get(`${serverUrl}/o/nasa/api/session/access/active`, cookie);
    assert.equal(resp.status, 200);
    assert.hasAllKeys(resp.data.org, ['id', 'name', 'access', 'domain', 'owner', 'billingAccount',
                                      'createdAt', 'updatedAt', 'host']);
    assert.deepEqual(resp.data.org.billingAccount,
                     { id: 1, individual: false, inGoodStanding: true, status: null,
                       externalId: null, externalOptions: null, paymentLink: null,
                       isManager: true, paid: false, features: null, stripePlanId: null,
                       product: { id: 1, name: 'Free', features: {workspaces: true, vanityDomain: true} } });

    // Check that internally we have access to stripe ids.
    const userId = await server.dbManager.testGetId('Chimpy') as number;
    const org2 = await server.dbManager.getOrg({userId}, 'nasa');
    assert.hasAllKeys(org2.data!.billingAccount,
                      ['id', 'individual', 'inGoodStanding', 'status', 'stripeCustomerId',
                       'stripeSubscriptionId', 'stripePlanId', 'product', 'paid', 'isManager',
                       'externalId', 'externalOptions', 'features', 'paymentLink']);
  });

  it('GET /api/session/access/active returns orgErr when org is forbidden', async function() {
    const cookie = await server.getCookieLogin('nasa', {email: 'kiwi@getgrist.com', name: 'Kiwi'});

    const resp = await axios.get(`${serverUrl}/o/nasa/api/session/access/active`, cookie);
    assert.equal(resp.status, 200);
    assert.sameMembers(['user', 'org', 'orgError'], Object.keys(resp.data));
    assert.deepEqual(omit(resp.data.user, ['helpScoutSignature', 'ref']), {
      id: await server.dbManager.testGetId("Kiwi"),
      email: "kiwi@getgrist.com",
      name: "Kiwi",
      picture: null,
    });
    assert.equal(resp.data.org, null);
    assert.deepEqual(resp.data.orgError, {
      status: 403,
      error: 'access denied'
    });
  });

  it('GET /api/session/access/active returns orgErr when org is non-existent', async function() {
    const cookie = await server.getCookieLogin('nasa', {email: 'kiwi@getgrist.com', name: 'Kiwi'});

    const resp = await axios.get(`${serverUrl}/o/boing/api/session/access/active`, cookie);
    assert.equal(resp.status, 200);
    assert.sameMembers(['user', 'org', 'orgError'], Object.keys(resp.data));
    assert.deepEqual(omit(resp.data.user, ['helpScoutSignature', 'ref']), {
      id: await server.dbManager.testGetId("Kiwi"),
      email: "kiwi@getgrist.com",
      name: "Kiwi",
      picture: null,
    });
    assert.equal(resp.data.org, null);
    assert.deepEqual(resp.data.orgError, {
      status: 404,
      error: 'organization not found'
    });
  });

  it('POST /api/session/access/active can change user', async function() {
    // add two profiles
    const cookie = await server.getCookieLogin('nasa', {email: 'charon@getgrist.com', name: 'Charon'});
    await server.getCookieLogin('pr', {email: 'kiwi@getgrist.com', name: 'Kiwi'});

    // pick kiwi profile for fish org
    let resp = await axios.post(`${serverUrl}/o/fish/api/session/access/active`, {
      email: 'kiwi@getgrist.com'
    }, cookie);
    assert.equal(resp.status, 200);

    // check kiwi profile stuck
    resp = await axios.get(`${serverUrl}/o/fish/api/session/access/active`, cookie);
    assert.equal(resp.data.user.email, 'kiwi@getgrist.com');

    // ... and that it didn't affect other org
    resp = await axios.get(`${serverUrl}/o/nasa/api/session/access/active`, cookie);
    assert.equal(resp.data.user.email, 'charon@getgrist.com');

    // pick charon profile for fish org
    resp = await axios.post(`${serverUrl}/o/fish/api/session/access/active`, {
      email: 'charon@getgrist.com'
    }, cookie);
    assert.equal(resp.status, 200);

    // check charon profile stuck
    resp = await axios.get(`${serverUrl}/o/fish/api/session/access/active`, cookie);
    assert.equal(resp.data.user.email, 'charon@getgrist.com');

    // make sure bogus profile for fish org fails
    resp = await axios.post(`${serverUrl}/o/fish/api/session/access/active`, {
      email: 'nonexistent@getgrist.com'
    }, cookie);
    assert.equal(resp.status, 403);
  });

  it('GET /api/session/access/all returns users and orgs', async function() {
    const cookie = await server.getCookieLogin('nasa', {email: 'charon@getgrist.com', name: 'Charon'});
    await server.getCookieLogin('pr', {email: 'kiwi@getgrist.com', name: 'Kiwi'});
    const resp = await axios.get(`${serverUrl}/o/pr/api/session/access/all`, cookie);
    assert.equal(resp.status, 200);
    assert.sameMembers(['users', 'orgs'], Object.keys(resp.data));
    assert.sameMembers(resp.data.users.map((user: UserProfile) => user.name),
      ['Charon', 'Kiwi']);
    // In following list, 'Kiwiland' is the the merged personal org, and Chimpyland is not
    // listed explicitly.
    assert.sameMembers(resp.data.orgs.map((org: any) => org.name),
      ['Abyss', 'Fish', 'Flightless', 'Kiwiland', 'NASA', 'Primately']);
    const fish = resp.data.orgs.find((org: any) => org.name === 'Fish');
    const accessOptions: AccessOptionWithRole[] = fish.accessOptions;
    assert.lengthOf(accessOptions, 2);
    assert.equal('editors', accessOptions.find(opt => opt.name === 'Kiwi')!.access);
    assert.equal('viewers', accessOptions.find(opt => opt.name === 'Charon')!.access);
  });

  it('GET /api/session/access/all functions with anonymous access', async function() {
    const resp = await axios.get(`${serverUrl}/o/pr/api/session/access/all`, nobody);
    assert.equal(resp.status, 200);
    // No orgs listed without access
    assert.lengthOf(resp.data.orgs, 0);
    // A single anonymous user
    assert.lengthOf(resp.data.users, 1);
    assert.equal(resp.data.users[0].anonymous, true);
  });
});
