const { test } = require('tap')
const requireInject = require('require-inject')

const Access = require('../../lib/access.js')

const npm = {
  output: () => null,
}

test('completion', t => {
  const access = new Access({ flatOptions: {} })
  const testComp = (argv, expect) => {
    const res = access.completion({conf: {argv: {remain: argv}}})
    t.resolves(res, expect, argv.join(' '))
  }

  testComp(['npm', 'access'], [
    'public', 'restricted', 'grant', 'revoke', 'ls-packages',
    'ls-collaborators', 'edit', '2fa-required', '2fa-not-required',
  ])
  testComp(['npm', 'access', 'grant'], ['read-only', 'read-write'])
  testComp(['npm', 'access', 'grant', 'read-only'], [])
  testComp(['npm', 'access', 'public'], [])
  testComp(['npm', 'access', 'restricted'], [])
  testComp(['npm', 'access', 'revoke'], [])
  testComp(['npm', 'access', 'ls-packages'], [])
  testComp(['npm', 'access', 'ls-collaborators'], [])
  testComp(['npm', 'access', 'edit'], [])
  testComp(['npm', 'access', '2fa-required'], [])
  testComp(['npm', 'access', '2fa-not-required'], [])
  testComp(['npm', 'access', 'revoke'], [])

  t.rejects(
    access.completion({conf: {argv: {remain: ['npm', 'access', 'foobar']}}}),
    { message: 'foobar not recognized' }
  )

  t.end()
})

test('subcommand required', t => {
  const access = new Access({ flatOptions: {} })
  access.exec([], (err) => {
    t.match(err, access.usageError('Subcommand is required.'))
    t.end()
  })
})

test('unrecognized subcommand', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec(['blerg'], (err) => {
    t.match(
      err,
      /Usage: blerg is not a recognized subcommand/,
      'should throw EUSAGE on missing subcommand'
    )
    t.end()
  })
})

test('edit', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'edit',
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /edit subcommand is not implemented yet/,
      'should throw not implemented yet error'
    )
    t.end()
  })
})

test('access public on unscoped package', (t) => {
  const prefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'npm-access-public-pkg',
    }),
  })
  const access = new Access({ prefix })
  access.exec([
    'public',
  ], (err) => {
    t.match(
      err,
      /Usage: This command is only available for scoped packages/,
      'should throw scoped-restricted error'
    )
    t.end()
  })
})

test('access public on scoped package', (t) => {
  t.plan(4)
  const name = '@scoped/npm-access-public-pkg'
  const prefix = t.testdir({
    'package.json': JSON.stringify({ name }),
  })
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      public: (pkg, { registry }) => {
        t.equal(pkg, name, 'should use pkg name ref')
        t.equal(
          registry,
          'https://registry.npmjs.org',
          'should forward correct options'
        )
        return true
      },
    },
  })
  const access = new Access({
    flatOptions: { registry: 'https://registry.npmjs.org' },
    prefix,
  })
  access.exec([
    'public',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access public on scoped package')
  })
})

test('access public on missing package.json', (t) => {
  const prefix = t.testdir({
    node_modules: {},
  })
  const access = new Access({ prefix })
  access.exec([
    'public',
  ], (err) => {
    t.match(
      err,
      /no package name passed to command and no package.json found/,
      'should throw no package.json found error'
    )
    t.end()
  })
})

test('access public on invalid package.json', (t) => {
  const prefix = t.testdir({
    'package.json': '{\n',
    node_modules: {},
  })
  const access = new Access({ prefix })
  access.exec([
    'public',
  ], (err) => {
    t.match(
      err,
      /JSONParseError/,
      'should throw failed to parse package.json'
    )
    t.end()
  })
})

test('access restricted on unscoped package', (t) => {
  const prefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'npm-access-restricted-pkg',
    }),
  })
  const access = new Access({ prefix })
  access.exec([
    'restricted',
  ], (err) => {
    t.match(
      err,
      /Usage: This command is only available for scoped packages/,
      'should throw scoped-restricted error'
    )
    t.end()
  })
})

test('access restricted on scoped package', (t) => {
  t.plan(4)
  const name = '@scoped/npm-access-restricted-pkg'
  const prefix = t.testdir({
    'package.json': JSON.stringify({ name }),
  })
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      restricted: (pkg, { registry }) => {
        t.equal(pkg, name, 'should use pkg name ref')
        t.equal(
          registry,
          'https://registry.npmjs.org',
          'should forward correct options'
        )
        return true
      },
    },
  })
  const access = new Access({
    flatOptions: { registry: 'https://registry.npmjs.org' },
    prefix,
  })
  access.exec([
    'restricted',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access restricted on scoped package')
  })
})

test('access restricted on missing package.json', (t) => {
  const prefix = t.testdir({
    node_modules: {},
  })
  const access = new Access({ prefix })
  access.exec([
    'restricted',
  ], (err) => {
    t.match(
      err,
      /no package name passed to command and no package.json found/,
      'should throw no package.json found error'
    )
    t.end()
  })
})

test('access restricted on invalid package.json', (t) => {
  const prefix = t.testdir({
    'package.json': '{\n',
    node_modules: {},
  })
  const access = new Access({ prefix })
  access.exec([
    'restricted',
  ], (err) => {
    t.match(
      err,
      /JSONParseError/,
      'should throw failed to parse package.json'
    )
    t.end()
  })
})

test('access grant read-only', (t) => {
  t.plan(5)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      grant: (spec, team, permissions) => {
        t.equal(spec, '@scoped/another', 'should use expected spec')
        t.equal(team, 'myorg:myteam', 'should use expected team')
        t.equal(permissions, 'read-only', 'should forward permissions')
        return true
      },
    },
  })
  const access = new Access({})
  access.exec([
    'grant',
    'read-only',
    'myorg:myteam',
    '@scoped/another',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access grant read-only')
  })
})

test('access grant read-write', (t) => {
  t.plan(5)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      grant: (spec, team, permissions) => {
        t.equal(spec, '@scoped/another', 'should use expected spec')
        t.equal(team, 'myorg:myteam', 'should use expected team')
        t.equal(permissions, 'read-write', 'should forward permissions')
        return true
      },
    },
  })
  const access = new Access({})
  access.exec([
    'grant',
    'read-write',
    'myorg:myteam',
    '@scoped/another',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access grant read-write')
  })
})

test('access grant current cwd', (t) => {
  t.plan(5)
  const prefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'yargs',
    }),
  })
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      grant: (spec, team, permissions) => {
        t.equal(spec, 'yargs', 'should use expected spec')
        t.equal(team, 'myorg:myteam', 'should use expected team')
        t.equal(permissions, 'read-write', 'should forward permissions')
        return true
      },
    },
  })
  const access = new Access({ prefix })
  access.exec([
    'grant',
    'read-write',
    'myorg:myteam',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access grant current cwd')
  })
})

test('access grant others', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'grant',
    'rerere',
    'myorg:myteam',
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /Usage: First argument must be either `read-only` or `read-write`./,
      'should throw unrecognized argument error'
    )
    t.end()
  })
})

test('access grant missing team args', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'grant',
    'read-only',
    undefined,
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /Usage: `<scope:team>` argument is required./,
      'should throw missing argument error'
    )
    t.end()
  })
})

test('access grant malformed team arg', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'grant',
    'read-only',
    'foo',
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /Usage: Second argument used incorrect format.\n/,
      'should throw malformed arg error'
    )
    t.end()
  })
})

test('access 2fa-required/2fa-not-required', t => {
  t.plan(2)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      tfaRequired: (spec) => {
        t.equal(spec, '@scope/pkg', 'should use expected spec')
        return true
      },
      tfaNotRequired: (spec) => {
        t.equal(spec, 'unscoped-pkg', 'should use expected spec')
        return true
      },
    },
  })
  const access = new Access({})

  access.exec(['2fa-required', '@scope/pkg'], er => {
    if (er)
      throw er
  })

  access.exec(['2fa-not-required', 'unscoped-pkg'], er => {
    if (er)
      throw er
  })
})

test('access revoke', (t) => {
  t.plan(4)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      revoke: (spec, team) => {
        t.equal(spec, '@scoped/another', 'should use expected spec')
        t.equal(team, 'myorg:myteam', 'should use expected team')
        return true
      },
    },
  })
  const access = new Access({})
  access.exec([
    'revoke',
    'myorg:myteam',
    '@scoped/another',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access revoke')
  })
})

test('access revoke missing team args', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'revoke',
    undefined,
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /Usage: `<scope:team>` argument is required./,
      'should throw missing argument error'
    )
    t.end()
  })
})

test('access revoke malformed team arg', (t) => {
  const access = new Access({ flatOptions: {} })
  access.exec([
    'revoke',
    'foo',
    '@scoped/another',
  ], (err) => {
    t.match(
      err,
      /Usage: First argument used incorrect format.\n/,
      'should throw malformed arg error'
    )
    t.end()
  })
})

test('npm access ls-packages with no team', (t) => {
  t.plan(3)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      lsPackages: (entity) => {
        t.equal(entity, 'foo', 'should use expected entity')
        return {}
      },
    },
    '../../lib/utils/get-identity.js': () => Promise.resolve('foo'),
  })
  const access = new Access(npm)
  access.exec([
    'ls-packages',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access ls-packages with no team')
  })
})

test('access ls-packages on team', (t) => {
  t.plan(3)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      lsPackages: (entity) => {
        t.equal(entity, 'myorg:myteam', 'should use expected entity')
        return {}
      },
    },
  })
  const access = new Access(npm)
  access.exec([
    'ls-packages',
    'myorg:myteam',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access ls-packages on team')
  })
})

test('access ls-collaborators on current', (t) => {
  t.plan(3)
  const prefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'yargs',
    }),
  })
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      lsCollaborators: (spec) => {
        t.equal(spec, 'yargs', 'should use expected spec')
        return {}
      },
    },
  })
  const access = new Access({ prefix, ...npm })
  access.exec([
    'ls-collaborators',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access ls-collaborators on current')
  })
})

test('access ls-collaborators on spec', (t) => {
  t.plan(3)
  const Access = requireInject('../../lib/access.js', {
    libnpmaccess: {
      lsCollaborators: (spec) => {
        t.equal(spec, 'yargs', 'should use expected spec')
        return {}
      },
    },
  })
  const access = new Access(npm)
  access.exec([
    'ls-collaborators',
    'yargs',
  ], (err) => {
    t.ifError(err, 'npm access')
    t.ok('should successfully access ls-packages with no team')
  })
})
