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

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 = []

t.afterEach(cb => {
  output.length = 0
  RUN_SCRIPTS.length = 0
  npm.flatOptions.json = false
  npm.flatOptions.parseable = false
  cb()
})

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

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', async t => {
    const res = await runScript.completion({conf: {argv: {remain: ['npm', 'run', 'x']}}})
    t.equal(res, undefined)
    t.end()
  })
  t.test('no package.json', async t => {
    const res = await runScript.completion({conf: {argv: {remain: ['npm', 'run']}}})
    t.strictSame(res, [])
    t.end()
  })
  t.test('has package.json, no scripts', async t => {
    writeFileSync(`${dir}/package.json`, JSON.stringify({}))
    const res = await runScript.completion({conf: {argv: {remain: ['npm', 'run']}}})
    t.strictSame(res, [])
    t.end()
  })
  t.test('has package.json, with scripts', async t => {
    writeFileSync(`${dir}/package.json`, JSON.stringify({
      scripts: { hello: 'echo hello', world: 'echo world' },
    }))
    const res = await runScript.completion({conf: {argv: {remain: ['npm', 'run']}}})
    t.strictSame(res, ['hello', 'world'])
    t.end()
  })
  t.end()
})

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

t.test('default env, start, and restart scripts', t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }),
    'server.js': 'console.log("hello, world")',
  })

  t.test('start', t => {
    runScript.exec(['start'], 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: {}},
          event: 'start',
        },
      ])
      t.end()
    })
  })

  t.test('env', t => {
    runScript.exec(['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',
        },
      ])
      t.end()
    })
  })

  t.test('windows env', t => {
    runScriptWin.exec(['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',
        },
      ])
      t.end()
    })
  })

  t.test('restart', t => {
    runScript.exec(['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',
        },
      ])
      t.end()
    })
  })
  t.end()
})

t.test('non-default env script', t => {
  npm.localPrefix = t.testdir({
    'package.json': JSON.stringify({
      name: 'x',
      version: '1.2.3',
      scripts: {
        env: 'hello',
      },
    }),
  })

  t.test('env', t => {
    runScript.exec(['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: 'hello',
            },
          },
          event: 'env',
        },
      ])
      t.end()
    })
  })

  t.test('env windows', t => {
    runScriptWin.exec(['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: 'hello',
            },
          },
          event: 'env',
        },
      ])
      t.end()
    })
  })
  t.end()
})

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

      t.strictSame(RUN_SCRIPTS, [], 'did not try to run anything')
      t.end()
    })
  })
  t.end()
})

t.test('run pre/post hooks', 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',
      },
    }),
  })

  runScript.exec(['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' },
    ])
    t.end()
  })
})

t.test('skip pre/post hooks when using ignoreScripts', 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',
      },
    }),
  })

  runScript.exec(['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',
          } },
        banner: true,
        event: 'env',
      },
    ])
    t.end()
    delete npm.flatOptions.ignoreScripts
  })
})

t.test('run silent', 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',
      },
    }),
  })

  runScript.exec(['env'], er => {
    if (er)
      throw er

    t.match(RUN_SCRIPTS, [
      {
        event: 'preenv',
        stdio: 'inherit',
      },
      {
        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',
        banner: false,
      },
      {
        event: 'postenv',
        stdio: 'inherit',
      },
    ])
    t.end()
  })
})

t.test('list scripts', 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,
    }),
  })

  t.test('no args', t => {
    runScript.exec([], 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')
      t.end()
    })
  })

  t.test('silent', t => {
    npmlog.level = 'silent'
    runScript.exec([], er => {
      if (er)
        throw er
      t.strictSame(output, [])
      t.end()
    })
  })
  t.test('warn json', t => {
    npmlog.level = 'warn'
    npm.flatOptions.json = true
    runScript.exec([], er => {
      if (er)
        throw er
      t.strictSame(output, [[JSON.stringify(scripts, 0, 2)]], 'json report')
      t.end()
    })
  })

  t.test('parseable', t => {
    npm.flatOptions.parseable = true
    runScript.exec([], 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'],
      ])
      t.end()
    })
  })
  t.end()
})

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

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

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

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

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

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