const { join } = require('path')
const { promisify } = require('util')
const fs = require('fs')
const spawk = require('spawk')
const t = require('tap')

spawk.preventUnmatched()

const readFile = promisify(fs.readFile)

const Sandbox = require('../../fixtures/sandbox.js')

t.test('config no args', async (t) => {
  const sandbox = new Sandbox(t)

  await t.rejects(sandbox.run('config', []), {
    code: 'EUSAGE',
  }, 'rejects with usage')
})

t.test('config ignores workspaces', async (t) => {
  const sandbox = new Sandbox(t)

  await t.rejects(sandbox.run('config', ['--workspaces']), {
    code: 'EUSAGE',
  }, 'rejects with usage')

  t.match(sandbox.logs.warn, [['config', 'This command does not support workspaces.']], 'logged the warning')
})

t.test('config list', async (t) => {
  const sandbox = new Sandbox(t)

  const temp = t.testdir({
    global: {
      npmrc: 'globalloaded=yes',
    },
    project: {
      '.npmrc': 'projectloaded=yes',
    },
    home: {
      '.npmrc': 'userloaded=yes',
    },
  })
  const global = join(temp, 'global')
  const project = join(temp, 'project')
  const home = join(temp, 'home')

  await sandbox.run('config', ['list'], { global, project, home })

  t.matchSnapshot(sandbox.output, 'output matches snapshot')
})

t.test('config list --long', async (t) => {
  const temp = t.testdir({
    global: {
      npmrc: 'globalloaded=yes',
    },
    project: {
      '.npmrc': 'projectloaded=yes',
    },
    home: {
      '.npmrc': 'userloaded=yes',
    },
  })
  const global = join(temp, 'global')
  const project = join(temp, 'project')
  const home = join(temp, 'home')

  const sandbox = new Sandbox(t, { global, project, home })
  await sandbox.run('config', ['list', '--long'])

  t.matchSnapshot(sandbox.output, 'output matches snapshot')
})

t.test('config list --json', async (t) => {
  const temp = t.testdir({
    global: {
      npmrc: 'globalloaded=yes',
    },
    project: {
      '.npmrc': 'projectloaded=yes',
    },
    home: {
      '.npmrc': 'userloaded=yes',
    },
  })
  const global = join(temp, 'global')
  const project = join(temp, 'project')
  const home = join(temp, 'home')

  const sandbox = new Sandbox(t, { global, project, home })
  await sandbox.run('config', ['list', '--json'])

  t.matchSnapshot(sandbox.output, 'output matches snapshot')
})

t.test('config delete no args', async (t) => {
  const sandbox = new Sandbox(t)

  await t.rejects(sandbox.run('config', ['delete']), {
    code: 'EUSAGE',
  }, 'rejects with usage')
})

t.test('config delete single key', async (t) => {
  // location defaults to user, so we work with a userconfig
  const home = t.testdir({
    '.npmrc': 'foo=bar\nbar=baz',
  })

  const sandbox = new Sandbox(t)
  await sandbox.run('config', ['delete', 'foo'], { home })

  t.equal(sandbox.config.get('foo'), undefined, 'foo should no longer be set')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo='), 'foo was removed on disk')
})

t.test('config delete multiple keys', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar\nbar=baz\nbaz=buz',
  })

  const sandbox = new Sandbox(t)
  await sandbox.run('config', ['delete', 'foo', 'bar'], { home })

  t.equal(sandbox.config.get('foo'), undefined, 'foo should no longer be set')
  t.equal(sandbox.config.get('bar'), undefined, 'bar should no longer be set')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo='), 'foo was removed on disk')
  t.not(contents.includes('bar='), 'bar was removed on disk')
})

t.test('config delete key --location=global', async (t) => {
  const global = t.testdir({
    npmrc: 'foo=bar\nbar=baz',
  })

  const sandbox = new Sandbox(t)
  await sandbox.run('config', ['delete', 'foo', '--location=global'], { global })

  t.equal(sandbox.config.get('foo', 'global'), undefined, 'foo should no longer be set')

  const contents = await readFile(join(global, 'npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo='), 'foo was removed on disk')
})

t.test('config delete key --global', async (t) => {
  const global = t.testdir({
    npmrc: 'foo=bar\nbar=baz',
  })

  const sandbox = new Sandbox(t)
  await sandbox.run('config', ['delete', 'foo', '--global'], { global })

  t.equal(sandbox.config.get('foo', 'global'), undefined, 'foo should no longer be set')

  const contents = await readFile(join(global, 'npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo='), 'foo was removed on disk')
})

t.test('config set no args', async (t) => {
  const sandbox = new Sandbox(t)

  await t.rejects(sandbox.run('config', ['set']), {
    code: 'EUSAGE',
  }, 'rejects with usage')
})

t.test('config set key', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar',
  })

  const sandbox = new Sandbox(t, { home })

  await sandbox.run('config', ['set', 'foo'])

  t.equal(sandbox.config.get('foo'), '', 'set the value for foo')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.ok(contents.includes('foo='), 'wrote foo to disk')
})

t.test('config set key value', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar',
  })

  const sandbox = new Sandbox(t, { home })

  await sandbox.run('config', ['set', 'foo', 'baz'])

  t.equal(sandbox.config.get('foo'), 'baz', 'set the value for foo')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.ok(contents.includes('foo=baz'), 'wrote foo to disk')
})

t.test('config set key=value', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar',
  })

  const sandbox = new Sandbox(t, { home })

  await sandbox.run('config', ['set', 'foo=baz'])

  t.equal(sandbox.config.get('foo'), 'baz', 'set the value for foo')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.ok(contents.includes('foo=baz'), 'wrote foo to disk')
})

t.test('config set key1 value1 key2=value2 key3', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar\nbar=baz\nbaz=foo',
  })

  const sandbox = new Sandbox(t, { home })
  await sandbox.run('config', ['set', 'foo', 'oof', 'bar=rab', 'baz'])

  t.equal(sandbox.config.get('foo'), 'oof', 'foo was set')
  t.equal(sandbox.config.get('bar'), 'rab', 'bar was set')
  t.equal(sandbox.config.get('baz'), '', 'baz was set')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.ok(contents.includes('foo=oof'), 'foo was written to disk')
  t.ok(contents.includes('bar=rab'), 'bar was written to disk')
  t.ok(contents.includes('baz='), 'baz was written to disk')
})

t.test('config set invalid key logs warning', async (t) => {
  const sandbox = new Sandbox(t)

  // this doesn't reject, it only logs a warning
  await sandbox.run('config', ['set', 'access=foo'])
  t.match(sandbox.logs.warn, [
    ['invalid config', 'access="foo"', `set in ${join(sandbox.home, '.npmrc')}`],
  ], 'logged warning')
})

t.test('config set key=value --location=global', async (t) => {
  const global = t.testdir({
    npmrc: 'foo=bar\nbar=baz',
  })

  const sandbox = new Sandbox(t, { global })
  await sandbox.run('config', ['set', 'foo=buzz', '--location=global'])

  t.equal(sandbox.config.get('foo', 'global'), 'buzz', 'foo should be set')

  const contents = await readFile(join(global, 'npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo=buzz'), 'foo was saved on disk')
})

t.test('config set key=value --global', async (t) => {
  const global = t.testdir({
    npmrc: 'foo=bar\nbar=baz',
  })

  const sandbox = new Sandbox(t, { global })
  await sandbox.run('config', ['set', 'foo=buzz', '--global'])

  t.equal(sandbox.config.get('foo', 'global'), 'buzz', 'foo should be set')

  const contents = await readFile(join(global, 'npmrc'), { encoding: 'utf8' })
  t.not(contents.includes('foo=buzz'), 'foo was saved on disk')
})

t.test('config get no args', async (t) => {
  const sandbox = new Sandbox(t)

  await sandbox.run('config', ['get'])
  const getOutput = sandbox.output

  sandbox.reset()

  await sandbox.run('config', ['list'])
  const listOutput = sandbox.output

  t.equal(listOutput, getOutput, 'get with no args outputs list')
})

t.test('config get single key', async (t) => {
  const sandbox = new Sandbox(t)

  await sandbox.run('config', ['get', 'node-version'])
  t.equal(sandbox.output, sandbox.config.get('node-version'), 'should get the value')
})

t.test('config get multiple keys', async (t) => {
  const sandbox = new Sandbox(t)

  await sandbox.run('config', ['get', 'node-version', 'npm-version'])
  t.ok(sandbox.output.includes(`node-version=${sandbox.config.get('node-version')}`), 'outputs node-version')
  t.ok(sandbox.output.includes(`npm-version=${sandbox.config.get('npm-version')}`), 'outputs npm-version')
})

t.test('config get private key', async (t) => {
  const sandbox = new Sandbox(t)

  await t.rejects(sandbox.run('config', ['get', '_authToken']), '_authToken is protected', 'rejects with protected string')
})

t.test('config edit', async (t) => {
  const home = t.testdir({
    '.npmrc': 'foo=bar\nbar=baz',
  })

  t.teardown(() => {
    spawk.clean()
  })

  const EDITOR = 'vim'
  const editor = spawk.spawn(EDITOR).exit(0)

  const sandbox = new Sandbox(t, { home })
  sandbox.process.env.EDITOR = EDITOR
  await sandbox.run('config', ['edit'])

  t.ok(editor.called, 'editor was spawned')
  t.same(editor.calledWith.args, [join(sandbox.home, '.npmrc')], 'editor opened the user config file')

  const contents = await readFile(join(home, '.npmrc'), { encoding: 'utf8' })
  t.ok(contents.includes('foo=bar'), 'kept foo')
  t.ok(contents.includes('bar=baz'), 'kept bar')
  t.ok(contents.includes('shown below with default values'), 'appends defaults to file')
})

t.test('config edit - editor exits non-0', async (t) => {
  t.teardown(() => {
    spawk.clean()
  })

  const EDITOR = 'vim'
  const editor = spawk.spawn(EDITOR).exit(1)

  const sandbox = new Sandbox(t)
  sandbox.process.env.EDITOR = EDITOR
  await t.rejects(sandbox.run('config', ['edit']), {
    message: 'editor process exited with code: 1',
  }, 'rejects with error about editor code')

  t.ok(editor.called, 'editor was spawned')
  t.same(editor.calledWith.args, [join(sandbox.home, '.npmrc')], 'editor opened the user config file')
})

t.test('completion', async (t) => {
  const sandbox = new Sandbox(t)

  let allKeys
  const testComp = async (argv, expect) => {
    t.match(await sandbox.complete('config', argv), expect, argv.join(' '))
    if (!allKeys)
      allKeys = Object.keys(sandbox.config.definitions)
    sandbox.reset()
  }

  await testComp([], ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'list'])
  await testComp(['set', 'foo'], [])
  await testComp(['get'], allKeys)
  await testComp(['set'], allKeys)
  await testComp(['delete'], allKeys)
  await testComp(['rm'], allKeys)
  await testComp(['edit'], [])
  await testComp(['list'], [])
  await testComp(['ls'], [])

  const getCommand = await sandbox.complete('get')
  t.match(getCommand, allKeys, 'also works for just npm get')
  sandbox.reset()

  const partial = await sandbox.complete('config', 'l')
  t.match(partial, ['get', 'set', 'delete', 'ls', 'rm', 'edit'], 'and works on partials')
})
