const t = require('tap')

const { real: mockNpm } = require('../fixtures/mock-npm.js')

const unsupportedMock = {
  checkForBrokenNode: () => {},
  checkForUnsupportedNode: () => {},
}

let exitHandlerCalled = null
let exitHandlerNpm = null
let exitHandlerCb
const exitHandlerMock = (...args) => {
  exitHandlerCalled = args
  if (exitHandlerCb)
    exitHandlerCb()
}
exitHandlerMock.setNpm = npm => {
  exitHandlerNpm = npm
}

const logs = []
const npmlogMock = {
  pause: () => logs.push('pause'),
  verbose: (...msg) => logs.push(['verbose', ...msg]),
  info: (...msg) => logs.push(['info', ...msg]),
}

const cliMock = (npm) => t.mock('../../lib/cli.js', {
  '../../lib/npm.js': npm,
  '../../lib/utils/update-notifier.js': async () => null,
  '../../lib/utils/unsupported.js': unsupportedMock,
  '../../lib/utils/exit-handler.js': exitHandlerMock,
  npmlog: npmlogMock,
})

const processMock = (proc) => {
  const mocked = {
    ...process,
    on: () => {},
    ...proc,
  }
  // nopt looks at process directly
  process.argv = mocked.argv
  return mocked
}

const { argv } = process

t.afterEach(() => {
  logs.length = 0
  process.argv = argv
  exitHandlerCalled = null
  exitHandlerNpm = null
})

t.test('print the version, and treat npm_g as npm -g', async t => {
  const proc = processMock({
    argv: ['node', 'npm_g', '-v'],
    version: process.version,
  })

  const { npm, outputs } = mockNpm(t)
  const cli = cliMock(npm)
  await cli(proc)

  t.strictSame(proc.argv, ['node', 'npm', '-g', '-v'], 'npm process.argv was rewritten')
  t.strictSame(process.argv, ['node', 'npm', '-g', '-v'], 'system process.argv was rewritten')
  t.strictSame(logs, [
    'pause',
    ['verbose', 'cli', proc.argv],
    ['info', 'using', 'npm@%s', npm.version],
    ['info', 'using', 'node@%s', process.version],
  ])
  t.strictSame(outputs, [[npm.version]])
  t.strictSame(exitHandlerCalled, [])
})

t.test('calling with --versions calls npm version with no args', async t => {
  const proc = processMock({
    argv: ['node', 'npm', 'install', 'or', 'whatever', '--versions'],
  })
  const { npm, outputs } = mockNpm(t)
  const cli = cliMock(npm)

  let versionArgs
  npm.commands.version = (args, cb) => {
    versionArgs = args
    cb()
  }

  await cli(proc)
  t.strictSame(versionArgs, [])
  t.equal(proc.title, 'npm')
  t.strictSame(npm.argv, [])
  t.strictSame(logs, [
    'pause',
    ['verbose', 'cli', proc.argv],
    ['info', 'using', 'npm@%s', npm.version],
    ['info', 'using', 'node@%s', process.version],
  ])

  t.strictSame(outputs, [])
  t.strictSame(exitHandlerCalled, [])
})

t.test('logged argv is sanitized', async t => {
  const proc = processMock({
    argv: ['node', 'npm', 'testcommand', 'https://username:password@npmjs.org/test_url_with_a_password'],
  })
  const { npm } = mockNpm(t)
  const cli = cliMock(npm)

  npm.commands.testcommand = (args, cb) => {
    cb()
  }

  await cli(proc)
  t.equal(proc.title, 'npm')
  t.strictSame(logs, [
    'pause',
    ['verbose', 'cli', [
      'node',
      'npm',
      'testcommand',
      'https://username:***@npmjs.org/test_url_with_a_password',
    ]],
    ['info', 'using', 'npm@%s', npm.version],
    ['info', 'using', 'node@%s', process.version],
  ])
})

t.test('print usage if no params provided', async t => {
  const proc = processMock({
    argv: ['node', 'npm'],
  })

  const { npm, outputs } = mockNpm(t)
  const cli = cliMock(npm)
  await cli(proc)
  t.match(outputs[0][0], 'Usage:', 'outputs npm usage')
  t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
  t.ok(exitHandlerNpm, 'exitHandler npm is set')
  t.match(proc.exitCode, 1)
})

t.test('print usage if non-command param provided', async t => {
  const proc = processMock({
    argv: ['node', 'npm', 'tset'],
  })

  const { npm, outputs } = mockNpm(t)
  const cli = cliMock(npm)
  await cli(proc)
  t.match(outputs[0][0], 'Unknown command: "tset"')
  t.match(outputs[0][0], 'Did you mean this?')
  t.match(exitHandlerCalled, [], 'should call exitHandler with no args')
  t.ok(exitHandlerNpm, 'exitHandler npm is set')
  t.match(proc.exitCode, 1)
})

t.test('load error calls error handler', async t => {
  const proc = processMock({
    argv: ['node', 'npm', 'asdf'],
  })

  const { npm } = mockNpm(t)
  const cli = cliMock(npm)
  const er = new Error('test load error')
  npm.load = () => Promise.reject(er)
  await cli(proc)
  t.strictSame(exitHandlerCalled, [er])
})
