const cacache = require('cacache')
const { promisify } = require('util')
const log = require('npmlog')
const npm = require('./npm.js')
const output = require('./utils/output.js')
const pacote = require('pacote')
const path = require('path')
const rimraf = promisify(require('rimraf'))

const usageUtil = require('./utils/usage.js')

const usage = usageUtil('cache',
  'npm cache add <tarball file>' +
  '\nnpm cache add <folder>' +
  '\nnpm cache add <tarball url>' +
  '\nnpm cache add <git url>' +
  '\nnpm cache add <name>@<version>' +
  '\nnpm cache clean' +
  '\nnpm cache verify'
)

const completion = (opts, cb) => {
  const argv = opts.conf.argv.remain
  if (argv.length === 2) {
    return cb(null, ['add', 'clean'])
  }

  // TODO - eventually...
  switch (argv[2]) {
    case 'clean':
    case 'add':
      return cb(null, [])
  }
}

const cmd = (args, cb) => cache(args).then(() => cb()).catch(cb)

const cache = async (args) => {
  const cmd = args.shift()
  switch (cmd) {
    case 'rm': case 'clear': case 'clean':
      return await clean(args)
    case 'add':
      return await add(args, npm.prefix)
    case 'verify': case 'check':
      return await verify()
    default:
      throw usage
  }
}

// npm cache clean [pkg]*
const clean = async (args) => {
  if (args.length) {
    throw new Error('npm cache clear does not accept arguments')
  }
  const cachePath = path.join(npm.cache, '_cacache')
  if (!npm.flatOptions.force) {
    throw new Error('As of npm@5, the npm cache self-heals from corruption ' +
      'issues and data extracted from the cache is guaranteed to be valid. ' +
      'If you want to make sure everything is consistent, use `npm cache ' +
      'verify` instead. On the other hand, if you\'re debugging an issue ' +
      'with the installer, you can use `npm install --cache /tmp/empty-cache` ' +
      'to use a temporary cache instead of nuking the actual one.\n\n' +
      'If you\'re sure you want to delete the entire cache, rerun this ' +
      'command with --force.')
  }
  return rimraf(cachePath)
}

// npm cache add <tarball-url>
// npm cache add <pkg> <ver>
// npm cache add <tarball>
// npm cache add <folder>
const add = async (args, where) => {
  const usage = 'Usage:\n' +
    '    npm cache add <tarball-url>\n' +
    '    npm cache add <pkg>@<ver>\n' +
    '    npm cache add <tarball>\n' +
    '    npm cache add <folder>\n'
  log.silly('cache add', 'args', args)
  const spec = args[0] +
    (args[1] === undefined || args[1] === null ? '' : `@${args[1]}`)

  log.verbose('cache add', 'spec', spec)
  if (!spec) {
    throw new Error(usage)
  }

  log.silly('cache add', 'parsed spec', spec)
  const opts = { ...npm.flatOptions, where }

  // we ask pacote for the thing, and then just throw the data
  // away so that it tee-pipes it into the cache like it does
  // for a normal request.
  await pacote.tarball.stream(spec, stream => {
    stream.resume()
    return stream.promise()
  }, opts)
}

const verify = async () => {
  const cache = path.join(npm.cache, '_cacache')
  const prefix = cache.indexOf(process.env.HOME) === 0
    ? `~${cache.substr(process.env.HOME.length)}`
    : cache
  const stats = await cacache.verify(cache)
  output(`Cache verified and compressed (${prefix}):`)
  output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`)
  stats.badContentCount && output(`Corrupted content removed: ${stats.badContentCount}`)
  stats.reclaimedCount && output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`)
  stats.missingContent && output(`Missing content: ${stats.missingContent}`)
  output(`Index entries: ${stats.totalEntries}`)
  output(`Finished in ${stats.runTime.total / 1000}s`)
}

module.exports = Object.assign(cmd, { completion, usage })
