const { test } = require('tap')
const { resolve } = require('path')
const requireInject = require('require-inject')
const { EventEmitter } = require('events')

let editorBin = null
let editorArgs = null
let editorOpts = null
let EDITOR_CODE = 0
const childProcess = {
  spawn: (bin, args, opts) => {
    // save for assertions
    editorBin = bin
    editorArgs = args
    editorOpts = opts

    const editorEvents = new EventEmitter()
    process.nextTick(() => {
      editorEvents.emit('exit', EDITOR_CODE)
    })
    return editorEvents
  },
}

let rebuildArgs = null
let rebuildFail = null
let EDITOR = 'vim'
const npm = {
  config: {
    get: () => EDITOR,
  },
  dir: resolve(__dirname, '../../node_modules'),
  commands: {
    rebuild: (args, cb) => {
      rebuildArgs = args
      return cb(rebuildFail)
    },
  },
}

const gracefulFs = require('graceful-fs')
const Edit = requireInject('../../lib/edit.js', {
  child_process: childProcess,
  'graceful-fs': gracefulFs,
})
const edit = new Edit(npm)

test('npm edit', t => {
  t.teardown(() => {
    rebuildArgs = null
    editorBin = null
    editorArgs = null
    editorOpts = null
  })

  return edit.exec(['semver'], (err) => {
    if (err)
      throw err

    const path = resolve(__dirname, '../../node_modules/semver')
    t.strictSame(editorBin, EDITOR, 'used the correct editor')
    t.strictSame(editorArgs, [path], 'edited the correct directory')
    t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
    t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
    t.end()
  })
})

test('rebuild fails', t => {
  t.teardown(() => {
    rebuildFail = null
    rebuildArgs = null
    editorBin = null
    editorArgs = null
    editorOpts = null
  })

  rebuildFail = new Error('test error')
  return edit.exec(['semver'], (err) => {
    const path = resolve(__dirname, '../../node_modules/semver')
    t.strictSame(editorBin, EDITOR, 'used the correct editor')
    t.strictSame(editorArgs, [path], 'edited the correct directory')
    t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
    t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
    t.match(err, { message: 'test error' })
    t.end()
  })
})

test('npm edit editor has flags', t => {
  EDITOR = 'code -w'
  t.teardown(() => {
    rebuildArgs = null
    editorBin = null
    editorArgs = null
    editorOpts = null
    EDITOR = 'vim'
  })

  return edit.exec(['semver'], (err) => {
    if (err)
      throw err

    const path = resolve(__dirname, '../../node_modules/semver')
    t.strictSame(editorBin, 'code', 'used the correct editor')
    t.strictSame(editorArgs, ['-w', path], 'edited the correct directory, keeping flags')
    t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
    t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
    t.end()
  })
})

test('npm edit no args', t => {
  return edit.exec([], (err) => {
    t.match(err, /npm edit/, 'throws usage error')
    t.end()
  })
})

test('npm edit lstat error propagates', t => {
  const _lstat = gracefulFs.lstat
  gracefulFs.lstat = (dir, cb) => {
    return cb(new Error('lstat failed'))
  }
  t.teardown(() => {
    gracefulFs.lstat = _lstat
  })

  return edit.exec(['semver'], (err) => {
    t.match(err, /lstat failed/, 'user received correct error')
    t.end()
  })
})

test('npm edit editor exit code error propagates', t => {
  EDITOR_CODE = 137
  t.teardown(() => {
    EDITOR_CODE = 0
  })

  return edit.exec(['semver'], (err) => {
    t.match(err, /exited with code: 137/, 'user received correct error')
    t.end()
  })
})
