'use strict';
/* global describe, it, before, after */

const spawn = require('cross-spawn');
const path = require('path');
const which = require('which');
const rimraf = require('rimraf');
const cpr = require('cpr');
const fs = require('fs');

require('chai').should();

describe('integration tests', () => {
  it('should run as a shell script with no arguments', done => {
    testArgs('./bin.js', [], done);
  });

  it('should run as a shell script with arguments', done => {
    testArgs('./bin.js', ['a', 'b', 'c'], done);
  });

  it('should run as a node script with no arguments', done => {
    testArgs('node bin.js', [], done);
  });

  it('should run as a node script with arguments', done => {
    testArgs('node bin.js', ['x', 'y', 'z'], done);
  });

  describe('path returned by "which"', () => {
    it('should match the actual path to the script file', done => {
      which('node', (err, resolvedPath) => {
        if (err) return done(err);
        testArgs(
          `${resolvedPath
            .replace('Program Files (x86)', 'Progra~2')
            .replace('Program Files', 'Progra~1')} bin.js`,
          [],
          done
        );
      });
    });

    it('should match the actual path to the script file, with arguments', done => {
      which('node', (err, resolvedPath) => {
        if (err) return done(err);
        testArgs(
          `${resolvedPath
            .replace('Program Files (x86)', 'Progra~2')
            .replace('Program Files', 'Progra~1')} bin.js`,
          ['q', 'r'],
          done
        );
      });
    });
  });

  // see #177
  it('allows --help to be completed without returning help message', done => {
    testCmd('./bin.js', ['--get-yargs-completions', '--h'], (code, stdout) => {
      if (code) {
        done(new Error(`cmd exited with code ${code}`));
        return;
      }

      stdout.should.not.match(/generate bash completion script/);
      stdout.should.match(/--help/);
      return done();
    });
  });

  // see #497
  it('flushes all output when --help is executed', function (done) {
    if (process.platform === 'win32' && process.version.match(/^v0.10/)) {
      // Doesn’t work in CI for some reason.
      return this.skip();
    }

    testCmd('./issue-497.js', ['--help'], (code, stdout) => {
      if (code) {
        done(new Error(`cmd exited with code ${code}`));
        return;
      }

      stdout.should.match(/--o999/);
      stdout.should.not.match(/never get here/);
      return done();
    });
  });

  it('correctly fills positional command args with preceding option', done => {
    testCmd(
      './opt-assignment-and-positional-command-arg.js',
      ['--foo', 'fOo', 'bar', 'bAz'],
      (code, stdout) => {
        if (code) {
          done(new Error(`cmd exited with code ${code}`));
          return;
        }

        const argv = JSON.parse(stdout);
        argv._.should.deep.equal(['bar']);
        argv.foo.should.equal('fOo');
        argv.baz.should.equal('bAz');
        return done();
      }
    );
  });

  it('correctly fills positional command args with = assignment in preceding option', done => {
    testCmd(
      './opt-assignment-and-positional-command-arg.js',
      ['--foo=fOo', 'bar', 'bAz'],
      (code, stdout) => {
        if (code) {
          done(new Error(`cmd exited with code ${code}`));
          return;
        }

        const argv = JSON.parse(stdout);
        argv._.should.deep.equal(['bar']);
        argv.foo.should.equal('fOo');
        argv.baz.should.equal('bAz');
        return done();
      }
    );
  });

  if (process.platform !== 'win32') {
    describe('load root package.json', () => {
      before(function (done) {
        this.timeout(10000);
        // create a symlinked, and a physical copy of yargs in
        // our fixtures directory, so that we can test that the
        // nearest package.json is appropriately loaded.
        cpr(
          './',
          './test/fixtures/yargs',
          {
            filter: /node_modules|example|test|package\.json/,
          },
          () => {
            fs.symlinkSync(process.cwd(), './test/fixtures/yargs-symlink');
            return done();
          }
        );
      });

      describe('version #', () => {
        it('defaults to appropriate version # when yargs is installed normally', done => {
          testCmd('./normal-bin.js', ['--version'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/9\.9\.9/);
            return done();
          });
        });

        it('defaults to appropriate version # when yargs is symlinked', done => {
          testCmd('./symlink-bin.js', ['--version'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/9\.9\.9/);
            return done();
          });
        });
      });

      describe('parser settings', () => {
        it('reads parser config settings when yargs is installed normally', done => {
          testCmd('./normal-bin.js', ['--foo.bar'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/foo\.bar/);
            return done();
          });
        });

        it('reads parser config settings when yargs is installed as a symlink', done => {
          testCmd('./symlink-bin.js', ['--foo.bar'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/foo\.bar/);
            return done();
          });
        });

        it('reads parser config settings when somebody obscures require.main', done => {
          testCmd('./no-require-main.js', ['--foo.bar'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/foo\.bar/);
            return done();
          });
        });

        it('reads parser config settings when entry file has no extension', done => {
          testCmd('./no-extension', ['--foo.bar'], (code, stdout) => {
            if (code) {
              return done(new Error(`cmd exited with code ${code}`));
            }

            stdout.should.match(/foo\.bar/);
            return done();
          });
        });
      });

      after(() => {
        rimraf.sync('./test/fixtures/yargs');
        fs.unlinkSync('./test/fixtures/yargs-symlink');
      });
    });
  }
});

function testCmd(cmd, args, cb) {
  const cmds = cmd.split(' ');

  const bin = spawn(cmds[0], cmds.slice(1).concat(args.map(String)), {
    cwd: path.join(__dirname, '/fixtures'),
  });

  let stdout = '';
  bin.stdout.setEncoding('utf8');
  bin.stdout.on('data', str => {
    stdout += str;
  });

  let stderr = '';
  bin.stderr.setEncoding('utf8');
  bin.stderr.on('data', str => {
    stderr += str;
  });

  bin.on('close', code => {
    cb(code, stdout, stderr);
  });
}

function testArgs(cmd, args, done) {
  testCmd(cmd, args, (code, stdout, stderr) => {
    if (code) {
      done(
        new Error(
          `cmd ${cmd} ${args} exited with code ${code}\n${stdout}\n${stderr}`
        )
      );
      return;
    }

    const _ = JSON.parse(stdout);
    _.map(String).should.deep.equal(args.map(String));
    done();
  });
}
