const t = require('tap')
const fs = require('fs')
const requireInject = require('require-inject')

const npm = {
  lockfileVersion: 2,
  globalDir: '',
  flatOptions: {
    depth: 0,
    global: false,
  },
  prefix: '',
}
const tree = {
  meta: {
    hiddenLockfile: null,
    loadedFromDisk: false,
    filename: '',
    originalLockfileVersion: 2,
    save () {},
  },
}
const mocks = {
  npmlog: { notice () {} },
  '@npmcli/arborist': class {
    loadVirtual () {
      return tree
    }

    loadActual () {
      return tree
    }
  },
  '../../lib/utils/usage.js': () => 'usage instructions',
}

t.afterEach(cb => {
  npm.prefix = ''
  npm.flatOptions.global = false
  npm.globalDir = ''
  cb()
})

t.test('no args', t => {
  t.plan(4)

  npm.prefix = '/project/a'

  class Arborist {
    constructor (args) {
      t.deepEqual(
        args,
        { ...npm.flatOptions, path: npm.prefix },
        'should call arborist constructor with expected args'
      )
    }

    async loadVirtual () {
      t.ok('should load virtual tree')
      return {
        ...tree,
        meta: {
          ...tree.meta,
          save () {
            t.ok('should save the lockfile')
          },
        },
      }
    }
  }

  const npmlog = {
    notice (title, msg) {
      t.equal(
        msg,
        'created a lockfile as npm-shrinkwrap.json',
        'should log notice msg that file was successfully created'
      )
    },
  }

  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
    ...mocks,
    npmlog,
    '@npmcli/arborist': Arborist,
  })
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    if (err)
      throw err
  })
})

t.test('no virtual tree', t => {
  t.plan(4)

  npm.prefix = '/project/a'

  class Arborist {
    constructor (args) {
      t.deepEqual(
        args,
        { ...npm.flatOptions, path: npm.prefix },
        'should call arborist constructor with expected args'
      )
    }

    async loadVirtual () {
      throw new Error('ERR')
    }

    async loadActual () {
      t.ok('should load actual tree')
      return {
        ...tree,
        meta: {
          ...tree.meta,
          save () {
            t.ok('should save the lockfile')
          },
        },
      }
    }
  }

  const npmlog = {
    notice (title, msg) {
      t.equal(
        msg,
        'created a lockfile as npm-shrinkwrap.json',
        'should log notice msg that file was successfully created'
      )
    },
  }

  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
    ...mocks,
    npmlog,
    '@npmcli/arborist': Arborist,
  })
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    if (err)
      throw err
  })
})

t.test('existing package-json file', t => {
  t.plan(5)

  npm.prefix = '/project/a'

  class Arborist {
    constructor (args) {
      t.deepEqual(
        args,
        { ...npm.flatOptions, path: npm.prefix },
        'should call arborist constructor with expected args'
      )
    }

    async loadVirtual () {
      t.ok('should load virtual tree')
      return {
        ...tree,
        meta: {
          hiddenLockfile: false,
          loadedFromDisk: true,
          filename: 'package-lock.json',
          save () {
            t.ok('should save the lockfile')
          },
        },
      }
    }
  }

  const npmlog = {
    notice (title, msg) {
      t.equal(
        msg,
        'package-lock.json has been renamed to npm-shrinkwrap.json',
        'should log notice msg that file was renamed'
      )
    },
  }

  const fs = {
    promises: {
      unlink (filename) {
        t.equal(filename, 'package-lock.json', 'should remove old lockfile')
      },
    },
  }

  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
    ...mocks,
    fs,
    npmlog,
    '@npmcli/arborist': Arborist,
  })
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    if (err)
      throw err
  })
})

t.test('update shrinkwrap file version', t => {
  t.plan(4)

  npm.prefix = '/project/a'

  class Arborist {
    constructor (args) {
      t.deepEqual(
        args,
        { ...npm.flatOptions, path: npm.prefix },
        'should call arborist constructor with expected args'
      )
    }

    async loadVirtual () {
      t.ok('should load virtual tree')
      return {
        ...tree,
        meta: {
          hiddenLockfile: false,
          loadedFromDisk: true,
          filename: 'npm-shrinkwrap.json',
          originalLockfileVersion: 1,
          save () {
            t.ok('should save the lockfile')
          },
        },
      }
    }
  }

  const npmlog = {
    notice (title, msg) {
      t.equal(
        msg,
        'npm-shrinkwrap.json updated to version 2',
        'should log notice msg that file was updated'
      )
    },
  }

  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
    ...mocks,
    npmlog,
    '@npmcli/arborist': Arborist,
  })
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    if (err)
      throw err
  })
})

t.test('update to date shrinkwrap file', t => {
  t.plan(4)

  npm.prefix = '/project/a'

  class Arborist {
    constructor (args) {
      t.deepEqual(
        args,
        { ...npm.flatOptions, path: npm.prefix },
        'should call arborist constructor with expected args'
      )
    }

    async loadVirtual () {
      t.ok('should load virtual tree')
      return {
        ...tree,
        meta: {
          hiddenLockfile: false,
          loadedFromDisk: true,
          filename: 'npm-shrinkwrap.json',
          originalLockfileVersion: 2,
          save () {
            t.ok('should save the lockfile')
          },
        },
      }
    }
  }

  const npmlog = {
    notice (title, msg) {
      t.equal(
        msg,
        'npm-shrinkwrap.json up to date',
        'should log notice msg shrinkwrap up to date'
      )
    },
  }

  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
    ...mocks,
    npmlog,
    '@npmcli/arborist': Arborist,
  })
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    if (err)
      throw err
  })
})

t.test('shrinkwrap --global', t => {
  const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', mocks)

  npm.flatOptions.global = true
  const shrinkwrap = new Shrinkwrap(npm)

  shrinkwrap.exec([], err => {
    t.match(
      err,
      /does not work for global packages/,
      'should throw no global support msg'
    )
    t.equal(err.code, 'ESHRINKWRAPGLOBAL', 'should throw expected error code')
    t.end()
  })
})

t.test('works without fs.promises', async t => {
  t.doesNotThrow(() => {
    const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', {
      ...mocks,
      fs: { ...fs, promises: null },
    })
    new Shrinkwrap(npm)
  })
})
