import { mockProcessExit } from 'jest-mock-process';
import { readFileSync } from 'fs';
import { stopAndPersist, fail } from '../__mocks__/ora';
import { mockConsole, unMockConsole } from './helper';
import nock from 'nock';

import { getToken, getTokenCommand } from '../src/index';

const GITHUB_URL = 'https://api.github.com';

describe('getToken', () => {
  let consoleLogMock: jest.Mock;
  beforeAll(() => {
    consoleLogMock = mockConsole('log');
  });

  afterAll(() => {
    unMockConsole('log');
  });
  beforeEach(() => {
    consoleLogMock.mockReset();
    stopAndPersist.mockReset();
    fail.mockReset();
  });

  const APP_ID = 1;
  const KEY_LOCATION = '__mocks__/private.key';

  const PRIVATE_KEY = readFileSync(KEY_LOCATION).toString();

  const getAccessTokensURL = (installationID: number) => `/app/installations/${installationID}/access_tokens`;
  const installationID = 1234;
  // The response object has to be in snake_case because is how the API returns fields, octokit will camelCase every field
  const response = {
    type: 'token',
    token_type: 'installation',
    token: 'secret-installation-token-1234',
    installation_id: 12400204,
    permissions: {
      actions: 'write',
      administration: 'admin',
      checks: 'write',
      contents: 'write',
      issues: 'write',
      metadata: 'read',
      pull_requests: 'write',
      statuses: 'admin',
    },
    created_at: new Date('2020-12-14').toDateString(),
    expires_at: new Date('2020-12-15').toDateString(),
    repository_selection: 'all',
  };
  it('It tries to get the token, but it gets a malformed response from Github', async () => {
    const { token, ...responseWithoutToken } = response;
    const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, responseWithoutToken);

    try {
      await getToken({
        appId: APP_ID,
        installationId: installationID,
        privateKey: PRIVATE_KEY,
      });
    } catch (e) {
      expect(e.message).toMatchInlineSnapshot(
        '"Something went wrong on the token retrieval, enable debug to inspect further"'
      );
    }

    expect(github.isDone()).toBe(true);
  });
  it('Retrieves the token', async () => {
    const github = nock(GITHUB_URL).post(getAccessTokensURL(123456)).reply(201, response);

    const { token } = await getToken({
      appId: APP_ID,
      installationId: 123456,
      privateKey: PRIVATE_KEY,
    });

    expect(token).toMatchInlineSnapshot('"secret-installation-token-1234"');
    expect(github.isDone()).toBe(true);
  });

  it('fails when invoking the command', async () => {
    const mockExit = mockProcessExit();
    const github = nock(GITHUB_URL)
      .post(getAccessTokensURL(installationID))
      .replyWithError('failed with some server error');

    await getTokenCommand({
      appId: APP_ID,
      installationId: installationID,
      privateKey: PRIVATE_KEY,
    });

    expect(mockExit).toHaveBeenCalled();
    expect(fail).toHaveBeenCalledTimes(1);
    expect(github.isDone()).toBe(true);
  });
  it('invokes getToken via command', async () => {
    const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);

    const mockExit = mockProcessExit();
    await getTokenCommand({
      appId: APP_ID,
      installationId: installationID,
      privateKey: PRIVATE_KEY,
    });

    expect(consoleLogMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          "secret-installation-token-1234",
        ],
      ]
    `);
    expect(mockExit).not.toHaveBeenCalled();
    expect(stopAndPersist).toHaveBeenCalledTimes(1);
    expect(github.isDone()).toBe(true);
  });

  it('invokes getToken via command providing a private.key location(with raw Response)', async () => {
    const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);

    const mockExit = mockProcessExit();
    await getTokenCommand({
      appId: APP_ID,
      installationId: installationID,
      privateKeyLocation: KEY_LOCATION,
      rawResponse: true,
    });

    expect(mockExit).not.toHaveBeenCalled();
    expect(stopAndPersist).toHaveBeenCalledTimes(1);
    expect(stopAndPersist.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "symbol": "💫",
            "text": "The token is: secret-installation-token-1234 and expires Tue Dec 15 2020",
          },
        ],
      ]
    `);
    expect(github.isDone()).toBe(true);
  });
  it('invokes getToken via command providing a private.key location', async () => {
    const github = nock(GITHUB_URL).post(getAccessTokensURL(installationID)).reply(201, response);

    const mockExit = mockProcessExit();
    await getTokenCommand({
      appId: APP_ID,
      installationId: installationID,
      privateKeyLocation: KEY_LOCATION,
    });

    expect(mockExit).not.toHaveBeenCalled();
    expect(stopAndPersist).toHaveBeenCalledTimes(1);
    expect(stopAndPersist.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "symbol": "💫",
            "text": "The token is: secret-installation-token-1234 and expires Tue Dec 15 2020",
          },
        ],
      ]
    `);
    expect(github.isDone()).toBe(true);
  });

  it('invokes getToken via command not providing a private.key or private.keyLocation', async () => {
    const mockExit = mockProcessExit();
    await getTokenCommand({
      appId: APP_ID,
      installationId: installationID,
    });

    expect(mockExit).toHaveBeenCalled();
    expect(fail.mock.calls[0][0]).toMatchInlineSnapshot(
      '"Input is not valid, either privateKey or privateKeyLocation should be provided"'
    );
  });

  it('Uses other baseUrl if provided', async () => {
    const other_url = 'https://test.de';
    const github = nock(other_url).post(getAccessTokensURL(123456)).reply(201, response);

    const { token } = await getToken({
      appId: APP_ID,
      installationId: 123456,
      privateKey: PRIVATE_KEY,
      baseUrl: other_url,
    });

    expect(token).toMatchInlineSnapshot('"secret-installation-token-1234"');
    expect(github.isDone()).toBe(true);
  });
});
