const t = require('tap')

process.env.NODE = '/path/to/some/node'
process.env.NODE_ENV = 'development'

const logs = []
const log = require('npmlog')
log.warn = (...args) => logs.push(['warn', ...args])
log.verbose = (...args) => logs.push(['verbose', ...args])

class Mocknpm {
  constructor (opts = {}) {
    this.modes = {
      exec: 0o777,
      file: 0o666,
      umask: 0o22,
    }
    this.color = true
    this.projectScope = '@npmcli'
    this.tmp = '/tmp'
    this.command = null
    this.globalPrefix = '/usr/local'
    this.localPrefix = '/path/to/npm/cli'
    this.prefix = this.localPrefix
    this.version = '7.6.5'
    this.config = new MockConfig(opts)
    this.flatOptions = null
  }
}

class MockConfig {
  constructor (opts = {}) {
    this.list = [{
      cache: 'cache',
      'node-version': '1.2.3',
      global: 'global',
      registry: 'registry',
      access: 'access',
      'always-auth': 'always-auth',
      audit: 'audit',
      'audit-level': 'audit-level',
      'auth-type': 'auth-type',
      before: 'before',
      browser: 'browser',
      ca: 'ca',
      cafile: 'cafile',
      call: 'call',
      cert: 'cert',
      key: 'key',
      'cache-lock-retries': 'cache-lock-retries',
      'cache-lock-stale': 'cache-lock-stale',
      'cache-lock-wait': 'cache-lock-wait',
      cidr: 'cidr',
      'read-only': 'read-only',
      preid: 'preid',
      'tag-version-prefix': 'tag-version-prefix',
      'allow-same-version': 'allow-same-version',
      message: 'message',
      'commit-hooks': 'commit-hooks',
      'git-tag-version': 'git-tag-version',
      'sign-git-commit': 'sign-git-commit',
      'sign-git-tag': 'sign-git-tag',
      depth: 'depth',
      description: 'description',
      searchexclude: 'searchexclude',
      searchlimit: 'searchlimit',
      searchopts: 'from=1',
      searchstaleness: 'searchstaleness',
      'dry-run': 'dry-run',
      'engine-strict': 'engine-strict',
      'fetch-retries': 'fetch-retries',
      'fetch-retry-factor': 'fetch-retry-factor',
      'fetch-retry-mintimeout': 'fetch-retry-mintimeout',
      'fetch-retry-maxtimeout': 'fetch-retry-maxtimeout',
      'fetch-timeout': 'fetch-timeout',
      force: 'force',
      'format-package-lock': 'format-package-lock',
      fund: 'fund',
      git: 'git',
      viewer: 'viewer',
      editor: 'editor',
      'bin-links': 'bin-links',
      'rebuild-bundle': 'rebuild-bundle',
      package: 'package',
      'package-lock': 'package-lock',
      'package-lock-only': 'package-lock-only',
      'global-style': 'global-style',
      'legacy-bundling': 'legacy-bundling',
      'script-shell': 'script-shell',
      omit: [],
      include: [],
      save: 'save',
      'save-bundle': 'save-bundle',
      'save-dev': 'save-dev',
      'save-optional': 'save-optional',
      'save-peer': 'save-peer',
      'save-prod': 'save-prod',
      'save-exact': 'save-exact',
      'save-prefix': 'save-prefix',
      otp: 'otp',
      offline: 'offline',
      'prefer-online': 'prefer-online',
      'prefer-offline': 'prefer-offline',
      'cache-max': 'cache-max',
      'cache-min': 'cache-min',
      'strict-ssl': 'strict-ssl',
      scope: '',
      tag: 'tag',
      'user-agent': 'user-agent',
      '@scope:registry': '@scope:registry',
      '//nerf.dart:_authToken': '//nerf.dart:_authToken',
      proxy: 'proxy',
      noproxy: 'noproxy',
      ...opts,
    }]
  }

  get (key) {
    return this.list[0][key]
  }

  set (key, val) {
    this.list[0][key] = val
  }
}

const flatOptions = require('../../../lib/utils/flat-options.js')
t.match(logs, [[
  'verbose',
  'npm-session',
  /^[0-9a-f]{16}$/,
]], 'logged npm session verbosely')
logs.length = 0

t.test('basic', t => {
  const npm = new Mocknpm()
  const generatedFlat = flatOptions(npm)
  const clean = {
    ...generatedFlat,
    npmBin: '/path/to/npm/bin.js',
    log: {},
    npmSession: '12345',
    cache: generatedFlat.cache.replace(/\\/g, '/'),
  }
  t.matchSnapshot(clean, 'flat options')
  t.equal(generatedFlat.npmCommand, null, 'command not set yet')
  npm.command = 'view'
  t.equal(generatedFlat.npmCommand, 'view', 'command updated via getter')
  t.equal(generatedFlat.npmBin, require.main.filename)
  // test the object is frozen
  generatedFlat.newField = 'asdf'
  t.equal(generatedFlat.newField, undefined, 'object is frozen')
  const preExistingOpts = { flat: 'options' }
  npm.flatOptions = preExistingOpts
  t.equal(flatOptions(npm), preExistingOpts, 'use pre-existing npm.flatOptions')
  t.end()
})

t.test('get preferOffline from cache-min', t => {
  const npm = new Mocknpm({
    'cache-min': 9999999,
    'prefer-offline': undefined,
  })
  const opts = flatOptions(npm)
  t.equal(opts.preferOffline, true, 'got preferOffline from cache min')
  logs.length = 0
  t.equal(opts.cacheMin, undefined, 'opts.cacheMin is not set')
  t.match(logs, [])
  logs.length = 0
  t.end()
})

t.test('get preferOnline from cache-max', t => {
  const npm = new Mocknpm({
    'cache-max': -1,
    'prefer-online': undefined,
  })
  const opts = flatOptions(npm)
  t.equal(opts.preferOnline, true, 'got preferOnline from cache min')
  logs.length = 0
  t.equal(opts.cacheMax, undefined, 'opts.cacheMax is not set')
  t.match(logs, [])
  logs.length = 0
  t.end()
})

t.test('tag emits warning', t => {
  const npm = new Mocknpm({ tag: 'foobar' })
  t.equal(flatOptions(npm).tag, 'foobar', 'tag is foobar')
  t.match(logs, [])
  logs.length = 0
  t.end()
})

t.test('omit/include options', t => {
  t.test('omit explicitly', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      omit: ['dev', 'optional', 'peer'],
    })
    t.strictSame(flatOptions(npm).omit, ['dev', 'optional', 'peer'])
    t.equal(process.env.NODE_ENV, 'production')
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.test('omit and include some', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      omit: ['dev', 'optional', 'peer'],
      include: ['peer'],
    })
    t.strictSame(flatOptions(npm).omit, ['dev', 'optional'])
    t.equal(process.env.NODE_ENV, 'production')
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.test('dev flag', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      omit: ['dev', 'optional', 'peer'],
      include: [],
      dev: true,
    })
    t.strictSame(flatOptions(npm).omit, ['optional', 'peer'])
    t.equal(process.env.NODE_ENV, NODE_ENV)
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.test('production flag', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      omit: [],
      include: [],
      production: true,
    })
    t.strictSame(flatOptions(npm).omit, ['dev'])
    t.equal(process.env.NODE_ENV, 'production')
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.test('only', t => {
    const { NODE_ENV } = process.env
    const cases = ['prod', 'production']
    t.plan(cases.length)
    cases.forEach(c => t.test(c, t => {
      const npm = new Mocknpm({
        omit: [],
        include: [],
        only: c,
      })
      t.strictSame(flatOptions(npm).omit, ['dev'])
      t.equal(process.env.NODE_ENV, 'production')
      process.env.NODE_ENV = NODE_ENV
      t.end()
    }))
  })

  t.test('also dev', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      omit: ['dev', 'optional', 'peer'],
      also: 'dev',
    })
    t.strictSame(flatOptions(npm).omit, ['optional', 'peer'])
    t.equal(process.env.NODE_ENV, NODE_ENV)
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.test('no-optional', t => {
    const { NODE_ENV } = process.env
    const npm = new Mocknpm({
      optional: false,
      omit: null,
      include: null,
    })
    t.strictSame(flatOptions(npm).omit, ['optional'])
    t.equal(process.env.NODE_ENV, NODE_ENV)
    process.env.NODE_ENV = NODE_ENV
    t.end()
  })

  t.end()
})

t.test('get the node without the environ', t => {
  delete process.env.NODE
  t.equal(flatOptions(new Mocknpm()).nodeBin, process.execPath)
  t.end()
})

t.test('various default values and falsey fallbacks', t => {
  const npm = new Mocknpm({
    'script-shell': false,
    registry: 'http://example.com',
    searchlimit: 0,
    'save-exact': false,
    'save-prefix': '>=',
  })
  const opts = flatOptions(npm)
  t.equal(opts.scriptShell, undefined, 'scriptShell is undefined if falsey')
  t.equal(opts.search.limit, 20, 'searchLimit defaults to 20')
  t.equal(opts.savePrefix, '>=', 'save-prefix respected if no save-exact')
  t.equal(opts.scope, '', 'scope defaults to empty string')
  logs.length = 0
  t.end()
})

t.test('legacy _auth token', t => {
  const npm = new Mocknpm({
    _auth: 'asdfasdf',
  })
  t.strictSame(
    flatOptions(npm)._auth,
    'asdfasdf',
    'should set legacy _auth token'
  )
  t.end()
})

t.test('save-type', t => {
  const base = {
    'save-optional': false,
    'save-peer': false,
    'save-dev': false,
    'save-prod': false,
  }
  const cases = [
    ['peerOptional', {
      'save-optional': true,
      'save-peer': true,
    }],
    ['optional', {
      'save-optional': true,
    }],
    ['dev', {
      'save-dev': true,
    }],
    ['peer', {
      'save-peer': true,
    }],
    ['prod', {
      'save-prod': true,
    }],
    [null, {}],
  ]
  for (const [expect, options] of cases) {
    const opts = flatOptions(new Mocknpm({
      ...base,
      ...options,
    }))
    t.equal(opts.saveType, expect, JSON.stringify(options))
  }
  t.end()
})
