/*
 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
 * Google as part of the polymer project is also subject to an additional IP
 * rights grant found at http://polymer.github.io/PATENTS.txt
 */

import {assert, use} from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as sinon from 'sinon';
import {PassThrough} from 'stream';
import * as tempMod from 'temp';
import chaiSubset = require('chai-subset');

import {Github, GithubResponseError} from '../../../github/github';
import {invertPromise, fixtureDir} from '../../util';

use(chaiSubset);

const temp = tempMod.track();

suite('github/github', () => {
  suite('tokenFromFile()', () => {
    test.skip(
        'returns a token from file if that file exists',
        () => {
            // not currently in use, add tests if put back into use
        });
    test.skip(
        'returns null if token file cannot be read',
        () => {
            // not currently in use, add tests if put back into use
        });
  });

  suite('extractReleaseTarball()', () => {
    test('extracts a tarball from a github tarball url', async () => {
      const tarballUrl = 'http://foo.com/bar.tar';
      let requestedUrl;
      const mockRequestApi = (options: {url: string}) => {
        requestedUrl = options.url;
        return fs.createReadStream(
            path.join(fixtureDir, 'github-test-data/test_tarball.tgz'));
      };
      const github = new Github({
        owner: 'TEST_OWNER',
        repo: 'TEST_REPO',
        // tslint:disable-next-line: no-any
        requestApi: mockRequestApi as any,
      });
      const tmpDir = temp.mkdirSync();
      await github.extractReleaseTarball(tarballUrl, tmpDir);
      assert.equal(requestedUrl, tarballUrl);
      assert.deepEqual(fs.readdirSync(tmpDir), ['file1.txt']);
    });

    test('rejects when Github returns a 404 response status code', async () => {
      const mockRequestApi = () => {
        const readStream = new PassThrough();
        setTimeout(() => {
          readStream.emit('response', {
            statusCode: 404,
            statusMessage: 'TEST MESSAGE - 404',
          });
        }, 10);
        return readStream;
      };
      const github = new Github({
        owner: 'TEST_OWNER',
        repo: 'TEST_REPO',
        // tslint:disable-next-line: no-any
        requestApi: mockRequestApi as any,
      });
      const tmpDir = temp.mkdirSync();
      const err = await invertPromise(
          github.extractReleaseTarball('http://foo.com/bar.tar', tmpDir));
      assert.instanceOf(err, GithubResponseError);
      assert.equal(
          err!.message,
          '404 fetching http://foo.com/bar.tar - TEST MESSAGE - 404');
    });
  });

  suite('removeUnwantedFiles()', () => {
    function makeDirStruct(files: string[]) {
      const tmpDir = temp.mkdirSync();
      files.forEach((file) => {
        const nodes = file.split('/');
        let tmpPath = tmpDir;
        nodes.forEach((node, index) => {
          tmpPath = path.join(tmpPath, node);
          if (fs.existsSync(tmpPath)) {
            return;
          }
          if (index === nodes.length - 1) {
            fs.writeFileSync(tmpPath, '');
          } else {
            fs.mkdirSync(tmpPath);
          }
        });
      });
      return tmpDir;
    }

    test('removes correct files', () => {
      const tmpDir = makeDirStruct([
        '.gitattributes',
        '.github/CONTRIBUTING',
        '.gitignore',
        '.travis.yml',
        'README',
        'src/base.js',
      ]);
      const github = new Github({
        owner: 'TEST_OWNER',
        repo: 'TEST_REPO',
      });
      github.removeUnwantedFiles(tmpDir);
      assert.deepEqual(fs.readdirSync(tmpDir), ['.gitignore', 'README', 'src']);
      assert.deepEqual(fs.readdirSync(path.join(tmpDir, 'src')), ['base.js']);
    });
  });

  suite('getSemverRelease()', () => {
    let listReleasesStub: sinon.SinonStub;
    let github: Github;

    const basicReleasesResponse = {
      data: [
        {tag_name: 'v1.0.0'},
        {tag_name: 'v1.1.0'},
        {tag_name: 'v1.2.1'},
        {tag_name: 'v2.0.0'},
        {tag_name: 'v2.0.0-pre.1'},
        {tag_name: 'v2.0.1'},
        {tag_name: 'v2.1.0'},
        {tag_name: 'TAG_NAME_WITHOUT_VERSION'},
      ]
    };

    setup(() => {
      listReleasesStub = sinon.stub();
      github = new Github({
        owner: 'TEST_OWNER',
        repo: 'TEST_REPO',
        githubApi: {
          repos: {
            listReleases: listReleasesStub,
          },
        },
        // tslint:disable-next-line: no-any
      } as any);
    });

    test('calls the github API with correct params', async () => {
      listReleasesStub.returns(Promise.resolve(basicReleasesResponse));

      await github.getSemverRelease('*');
      assert.isOk(listReleasesStub.calledWithExactly({
        owner: 'TEST_OWNER',
        repo: 'TEST_REPO',
        per_page: 100,
      }));
    });

    let testName =
        'resolves with latest semver release that matches the range: *';
    test(testName, async () => {
      listReleasesStub.returns(Promise.resolve(basicReleasesResponse));

      const release = await github.getSemverRelease('*');
      assert.containSubset(release, {name: 'v2.1.0'});
    });

    testName =
        'resolves with latest semver release that matches the range: ^v1.0.0';
    test(testName, async () => {
      listReleasesStub.returns(Promise.resolve(basicReleasesResponse));

      const release = await github.getSemverRelease('^v1.0.0');
      assert.containSubset(release, {name: 'v1.2.1'});
    });

    testName =
        'resolves with latest semver release that matches the range: ^v2.0.0';
    test(testName, async () => {
      listReleasesStub.returns(Promise.resolve(basicReleasesResponse));

      const release = await github.getSemverRelease('^v2.0.0');
      assert.containSubset(release, {name: 'v2.1.0'});
    });

    testName = 'rejects with an error if no matching releases are found';
    test(testName, async () => {
      listReleasesStub.returns(Promise.resolve(basicReleasesResponse));

      const err = await invertPromise(github.getSemverRelease('^v3.0.0'));
      assert.equal(
          err!.message,
          'TEST_OWNER/TEST_REPO has no releases matching ^v3.0.0.');
    });
  });
});
