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

let RUN_FAIL = null
const RUN_SCRIPTS = []
const npm = {
  localPrefix: __dirname,
  flatOptions: {
    scriptShell: undefined,
    json: false,
    parseable: false
  },
  config: {
    settings: {
      'if-present': false
    },
    get: k => npm.config.settings[k],
    set: (k, v) => {
      npm.config.settings[k] = v
    }
  }
}

const output = []

const npmlog = { level: 'warn' }
const getRS = windows => requireInject('../../lib/run-script.js', {
  '@npmcli/run-script': async opts => {
    RUN_SCRIPTS.push(opts)
  },
  npmlog,
  '../../lib/npm.js': npm,
  '../../lib/utils/is-windows-shell.js': windows,
  '../../lib/utils/output.js': (...msg) => output.push(msg)
})

const runScript = getRS(false)
const runScriptWin = getRS(true)

const { writeFileSync } = require('fs')
t.test('completion', t => {
  const dir = t.testdir()
  npm.localPrefix = dir
  t.test('already have a script name', t => {
    runScript.completion({conf:{argv:{remain: ['npm','run','x']}}}, (er, results) => {
      if (er) {
        throw er
      }
      t.equal(results, undefined)
      t.end()
    })
  })
  t.test('no package.json', t => {
    runScript.completion({conf:{argv:{remain: ['npm','run']}}}, (er, results) => {
      if (er) {
        throw er
      }
      t.strictSame(results, [])
      t.end()
    })
  })
  t.test('has package.json, no scripts', t => {
    writeFileSync(`${dir}/package.json`, JSON.stringify({}))
    runScript.completion({conf:{argv:{remain: ['npm', 'run']}}}, (er, results) => {
      if (er) {
        throw er
      }
      t.strictSame(results, [])
      t.end()
    })
  })
  t.test('has package.json, with scripts', t => {
    writeFileSync(`${dir}/package.json`, JSON.stringify({
      scripts: { hello: 'echo hello', world: 'echo world' }
    }))
    runScript.completion({conf:{argv:{remain: ['npm', 'run']}}}, (er, results) => {
      if (er) {
        throw er
      }
      t.strictSame(results, ['hello', 'world'])
      t.end()
    })
  })
  t.end()
})

t.test('fail if no package.json', async t => {
  npm.localPrefix = t.testdir()
  await runScript([], er => t.match(er, { code: 'ENOENT' }))
  await runScript(['test'], er => t.match(er, { code: 'ENOENT' }))
})

t.test('default env and restart scripts', async t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({ name: 'x', version: '1.2.3' })
  })

  await runScript(['env'], er => {
    if (er) {
      throw er
    }
    t.match(RUN_SCRIPTS, [
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'inherit',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          env: 'env'
        } },
        event: 'env'
      }
    ])
  })
  RUN_SCRIPTS.length = 0

  await runScriptWin(['env'], er => {
    if (er) {
      throw er
    }
    t.match(RUN_SCRIPTS, [
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'inherit',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          env: 'SET'
        } },
        event: 'env'
      }
    ])
  })
  RUN_SCRIPTS.length = 0

  await runScript(['restart'], er => {
    if (er) {
      throw er
    }
    t.match(RUN_SCRIPTS, [
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'inherit',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          restart: 'npm stop --if-present && npm start'
        } },
        event: 'restart'
      }
    ])
  })
  RUN_SCRIPTS.length = 0
})

t.test('try to run missing script', t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      scripts: { hello: 'world' }
    })
  })
  t.test('no suggestions', async t => {
    await runScript(['notevenclose'], er => {
      t.match(er, {
        message: 'missing script: notevenclose'
      })
    })
  })
  t.test('suggestions', async t => {
    await runScript(['helo'], er => {
      t.match(er, {
        message: 'missing script: helo\n\nDid you mean this?\n    hello'
      })
    })
  })
  t.test('with --if-present', async t => {
    npm.config.set('if-present', true)
    await runScript(['goodbye'], er => {
      if (er) {
        throw er
      }
      t.strictSame(RUN_SCRIPTS, [], 'did not try to run anything')
    })
  })
  t.end()
})

t.test('run pre/post hooks', async t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: {
        preenv: 'echo before the env',
        postenv: 'echo after the env'
      }
    })
  })

  await runScript(['env'], er => {
    if (er) {
      throw er
    }
    t.match(RUN_SCRIPTS, [
      { event: 'preenv' },
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'inherit',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          env: 'env'
        } },
        event: 'env'
      },
      { event: 'postenv' }
    ])
  })
  RUN_SCRIPTS.length = 0
})

t.test('skip pre/post hooks when using ignoreScripts', async t => {
  npm.flatOptions.ignoreScripts = true

  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: {
        preenv: 'echo before the env',
        postenv: 'echo after the env'
      }
    })
  })

  await runScript(['env'], er => {
    if (er) {
      throw er
    }
    t.deepEqual(RUN_SCRIPTS, [
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'inherit',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          preenv: 'echo before the env',
          postenv: 'echo after the env',
          env: 'env'
        } },
        event: 'env'
      }
    ])

    delete npm.flatOptions.ignoreScripts
  })
  RUN_SCRIPTS.length = 0
})

t.test('run silent', async t => {
  npmlog.level = 'silent'
  t.teardown(() => { npmlog.level = 'warn' })

  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: {
        preenv: 'echo before the env',
        postenv: 'echo after the env'
      }
    })
  })

  await runScript(['env'], er => {
    if (er) {
      throw er
    }
    t.match(RUN_SCRIPTS, [
      {
        event: 'preenv',
        stdio: 'pipe'
      },
      {
        path: npm.localPrefix,
        args: [],
        scriptShell: undefined,
        stdio: 'pipe',
        stdioString: true,
        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {
          env: 'env'
        } },
        event: 'env'
      },
      {
        event: 'postenv',
        stdio: 'pipe'
      }
    ])
  })
  RUN_SCRIPTS.length = 0
})

t.test('list scripts', async t => {
  const scripts = {
    test: 'exit 2',
    start: 'node server.js',
    stop: 'node kill-server.js',
    preenv: 'echo before the env',
    postenv: 'echo after the env'
  }
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts
    })
  })

  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [
    [ 'Lifecycle scripts included in x:' ],
    [ '  test\n    exit 2' ],
    [ '  start\n    node server.js' ],
    [ '  stop\n    node kill-server.js' ],
    [ '\navailable via `npm run-script`:' ],
    [ '  preenv\n    echo before the env' ],
    [ '  postenv\n    echo after the env' ]
  ], 'basic report')
  output.length = 0

  npmlog.level = 'silent'
  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [])
  npmlog.level = 'warn'

  npm.flatOptions.json = true
  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [[JSON.stringify(scripts, 0, 2)]], 'json report')
  output.length = 0
  npm.flatOptions.json = false

  npm.flatOptions.parseable = true
  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [
    [ 'test:exit 2' ],
    [ 'start:node server.js' ],
    [ 'stop:node kill-server.js' ],
    [ 'preenv:echo before the env' ],
    [ 'postenv:echo after the env' ]
  ])
  output.length = 0
  npm.flatOptions.parseable = false
})

t.test('list scripts when no scripts', async t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3'
    })
  })

  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [], 'nothing to report')
  output.length = 0
})

t.test('list scripts, only commands', async t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: { preversion: 'echo doing the version dance' }
    })
  })

  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [
    ["Lifecycle scripts included in x:"],
    ["  preversion\n    echo doing the version dance"],
  ])
  output.length = 0
})

t.test('list scripts, only non-commands', async t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: { glorp: 'echo doing the glerp glop' }
    })
  })

  await runScript([], er => {
    if (er) {
      throw er
    }
  })
  t.strictSame(output, [
    ["Scripts available in x via `npm run-script`:"],
    ["  glorp\n    echo doing the glerp glop"]
  ])
  output.length = 0
})
