/**
 * @license
 * 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} from 'chai';
import * as childProcess from 'child_process';
import * as fs from 'fs';
import * as inquirer from 'inquirer';
import * as sinon from 'sinon';
import * as tempMod from 'temp';
import * as YeomanEnvironment from 'yeoman-environment';
import * as helpers from 'yeoman-test';

import * as polymerInit from '../../../init/init';
import {invertPromise} from '../../util';

const temp = tempMod.track();
const isPlatformWin = /^win/.test(process.platform);
const uname = childProcess.execSync('uname -s').toString();
const isMinGw = !!/^mingw/i.test(uname);

function stripAnsi(str: string) {
  const ansiRegex =
      /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
  return str.replace(ansiRegex, '');
}

interface FakeEnv {
  getGeneratorsMeta: sinon.SinonStub;
  run: sinon.SinonStub;
}

suite('init', () => {
  function createFakeEnv(): FakeEnv {
    return {
      getGeneratorsMeta: sinon.stub(),
      run: sinon.stub().yields(),
    };
  }

  teardown(() => {
    sinon.restore();
  });

  suite('runGenerator', () => {
    test('runs the given generator', async () => {
      const GENERATOR_NAME = 'TEST-GENERATOR';
      const yeomanEnv = createFakeEnv();
      yeomanEnv.getGeneratorsMeta.returns({
        [GENERATOR_NAME]: GENERATOR_NAME,
      });

      await polymerInit.runGenerator(GENERATOR_NAME, {env: yeomanEnv});
      assert.isOk(yeomanEnv.run.calledWith(GENERATOR_NAME));
    });

    test('fails if an unknown generator is requested', async () => {
      const UNKNOWN_GENERATOR_NAME = 'UNKNOWN-GENERATOR';
      const yeomanEnv = createFakeEnv();
      yeomanEnv.getGeneratorsMeta.returns({
        'TEST-GENERATOR': 'TEST-GENERATOR',
      });

      const error = await invertPromise(
          polymerInit.runGenerator(UNKNOWN_GENERATOR_NAME, {env: yeomanEnv}));

      assert.equal(
          error!.message, `Template ${UNKNOWN_GENERATOR_NAME} not found`);
    });

    test('works with the default yeoman environment', async () => {
      // Note: Do not use a fake Yeoman environment in this test so that we get
      // coverage of the case where env isn't specified.
      const UNKNOWN_GENERATOR_NAME = 'UNKNOWN-GENERATOR';
      const error =
          await invertPromise(polymerInit.runGenerator(UNKNOWN_GENERATOR_NAME));
      assert.equal(
          error!.message, `Template ${UNKNOWN_GENERATOR_NAME} not found`);
    });
  });

  suite('promptGeneratorSelection', () => {
    let yeomanEnvMock: FakeEnv;
    interface Generator {
      generatorName: string;
      metaName: string;
      shortName: string;
      description: string;
      resolved?: string;
    }
    const GENERATORS: Generator[] = [
      {
        generatorName: 'polymer-init-element:app',
        metaName: 'polymer-init-element',
        shortName: 'element',
        description: 'A blank element template',
        resolved: 'unknown',
      },
      {
        generatorName: 'polymer-init-my-test-app:app',
        metaName: 'polymer-init-my-test-app',
        shortName: 'my-test-app',
        description: 'my test app',
      },
      {
        generatorName: 'polymer-init-polymer-starter-kit-custom-1:app',
        metaName: 'polymer-init-polymer-starter-kit-1',
        shortName: 'polymer-starter-kit-1',
        description: 'PSK 1',
      },
      {
        generatorName: 'polymer-init-polymer-starter-kit-custom-2:app',
        metaName: 'generator-polymer-init-polymer-starter-kit-2',
        shortName: 'polymer-starter-kit-2',
        description: 'PSK 2',
      },
      {
        generatorName: 'polymer-init-custom-build-1:app',
        metaName: 'generator-polymer-init-custom-build-1',
        shortName: 'custom-build-1',
        description: 'custom build 1',
      },
      {
        generatorName: 'polymer-init-custom-build-2:app',
        metaName: 'polymer-init-custom-build-2',
        shortName: 'custom-build-2',
        description: 'custom build 2',
      },
      {
        generatorName: 'polymer-init-custom-build-3:app',
        metaName: 'custom-build-3',
        shortName: 'custom-build-3',
        description: 'custom build 3',
      },
    ];

    setup(() => {
      const generators: {
        [generatorName: string]: {resolved: string|undefined, namespace: string}
      } = {};

      for (const generator of GENERATORS) {
        if (!generator.resolved) {
          const tmpDir = temp.mkdirSync();
          const packageJsonPath = `${tmpDir}/package.json`;
          fs.writeFileSync(packageJsonPath, JSON.stringify({
            description: generator.description,
            name: generator.metaName,
          }));
          generator.resolved = tmpDir;
        }

        generators[generator.generatorName] = {
          resolved: generator.resolved,
          namespace: generator.generatorName,
        };
      }

      yeomanEnvMock = createFakeEnv();
      yeomanEnvMock.getGeneratorsMeta.returns(generators);
    });


    test('works with the default yeoman environment', async () => {
      sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
        foo: 'TEST',
      }));
      // tslint:disable-next-line: no-any
      (polymerInit as any).runGenerator = function() {};
      const error = await invertPromise(polymerInit.promptGeneratorSelection());
      assert.equal(error!.message, 'Template TEST not found');
    });

    let testName =
        'prompts with a list to get generatorName property from user';
    test(testName, async () => {
      const promptStub = sinon.stub(inquirer, 'prompt')
                             .returns(Promise.resolve({foo: 'TEST'}));
      try {
        await polymerInit.promptGeneratorSelection({env: yeomanEnvMock});
      } catch (error) {
        assert.equal(error.message, 'Template TEST not found');
      }
      assert.isTrue(promptStub.calledOnce);
      assert.equal(
          (promptStub.firstCall.args[0] as inquirer.Question[])[0].type,
          'list');
      assert.equal(
          (promptStub.firstCall.args[0] as inquirer.Question[])[0].message,
          'Which starter template would you like to use?');
    });

    test('prompts with a list of all registered generators', async () => {
      const promptStub = sinon.stub(inquirer, 'prompt')
                             .returns(Promise.resolve({foo: 'TEST'}));
      try {
        await polymerInit.promptGeneratorSelection({env: yeomanEnvMock});
      } catch (error) {
        assert.equal(error.message, 'Template TEST not found');
      }
      const choices =
          (promptStub.firstCall.args[0] as inquirer.Question[])[0].choices as
          inquirer.objects.ChoiceOption[];
      assert.equal(choices.length, GENERATORS.length);

      for (const choice of choices) {
        const generator = GENERATORS.find(
            (generator) => generator.generatorName === choice.value)!;
        assert.isDefined(generator, `generator not found: ${choice.value}`);
        assert.oneOf(stripAnsi(choice.name!), [
          generator.shortName,
          `${generator.shortName} - ${generator.description}`,
        ]);
        assert.equal(choice.value, generator.generatorName);
        // tslint:disable-next-line: no-any
        assert.equal((choice as any).short, generator.shortName);
      }
    });

    testName = 'includes user-provided generators in the list when properly ' +
        'installed/registered';
    test(testName, async () => {
      const yeomanEnv = new YeomanEnvironment();
      const promptStub =
          sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
            foo: 'TEST',
          }));
      helpers.registerDependencies(yeomanEnv, [[
                                     // tslint:disable-next-line: no-any
                                     function() {} as any,
                                     'polymer-init-custom-template:app',
                                   ]]);
      try {
        await polymerInit.promptGeneratorSelection({env: yeomanEnv});
      } catch (error) {
        assert.equal(error.message, 'Template TEST not found');
      }
      assert.isTrue(promptStub.calledOnce);
      const choices =
          (promptStub.firstCall.args[0] as inquirer.Question[])[0].choices as
          inquirer.objects.ChoiceOption[];
      const customGeneratorChoice = choices[choices.length - 1];
      assert.equal(stripAnsi(customGeneratorChoice.name!), 'custom-template');
      assert.equal(
          customGeneratorChoice.value, 'polymer-init-custom-template:app');
      // tslint:disable-next-line: no-any
      assert.equal((customGeneratorChoice as any).short, 'custom-template');
    });

    test('prompts the user with a list', async () => {
      const promptStub = sinon.stub(inquirer, 'prompt')
                             .returns(Promise.resolve({foo: 'TEST'}));

      try {
        await polymerInit.promptGeneratorSelection({env: yeomanEnvMock});
      } catch (error) {
        assert.equal(error.message, 'Template TEST not found');
      }
      assert.isTrue(promptStub.calledOnce);
      assert.equal(
          (promptStub.firstCall.args[0] as inquirer.Question[])[0].type,
          'list');
    });

    if (isPlatformWin && isMinGw) {
      test('prompts with a rawlist if being used in MinGW shell', async () => {
        const promptStub = sinon.stub(inquirer, 'prompt')
                               .returns(Promise.resolve({foo: 'TEST'}));
        sinon.stub(childProcess, 'execSync')
            .withArgs('uname -s')
            .returns(Buffer.from('mingw'));

        try {
          await polymerInit.promptGeneratorSelection({env: yeomanEnvMock});
        } catch (error) {
          assert.equal(error.message, 'Template TEST not found');
        }
        assert.isTrue(promptStub.calledOnce);
        assert.equal(
            (promptStub.firstCall.args[0] as inquirer.Question[])[0].type,
            'rawlist');
      });
    }
  });
});
