import fs from 'fs'
import path from 'path'
import { prepare, preparePackages } from '@pnpm/prepare'
import isWindows from 'is-windows'
import { execPnpm, execPnpmSync } from './utils'

const RECORD_ARGS_FILE = 'require(\'fs\').writeFileSync(\'args.json\', JSON.stringify(require(\'./args.json\').concat([process.argv.slice(2)])), \'utf8\')'
const testOnPosix = isWindows() ? test.skip : test

test('run -r: pass the args to the command that is specified in the build script', async () => {
  preparePackages([{
    name: 'project',
    scripts: {
      foo: 'node recordArgs',
      postfoo: 'node recordArgs',
      prefoo: 'node recordArgs',
    },
  }])
  fs.writeFileSync('project/args.json', '[]', 'utf8')
  fs.writeFileSync('project/recordArgs.js', RECORD_ARGS_FILE, 'utf8')

  await execPnpm(['run', '-r', '--config.enable-pre-post-scripts', 'foo', 'arg', '--flag=true'])

  const { default: args } = await import(path.resolve('project/args.json'))
  expect(args).toStrictEqual([
    [],
    ['arg', '--flag=true'],
    [],
  ])
})

test('run: pass the args to the command that is specified in the build script', async () => {
  prepare({
    name: 'project',
    scripts: {
      foo: 'node recordArgs',
      postfoo: 'node recordArgs',
      prefoo: 'node recordArgs',
    },
  })
  fs.writeFileSync('args.json', '[]', 'utf8')
  fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')

  await execPnpm(['run', 'foo', 'arg', '--flag=true'])

  const { default: args } = await import(path.resolve('args.json'))
  expect(args).toStrictEqual([
    [],
    ['arg', '--flag=true'],
    [],
  ])
})

// Before pnpm v7, `--` was required to pass flags to a build script. Now all
// arguments after the script name should be passed to the build script, even
// `--`.
test('run: pass all arguments after script name to the build script, even --', async () => {
  prepare({
    name: 'project',
    scripts: {
      foo: 'node recordArgs',
      postfoo: 'node recordArgs',
      prefoo: 'node recordArgs',
    },
  })
  fs.writeFileSync('args.json', '[]', 'utf8')
  fs.writeFileSync('recordArgs.js', RECORD_ARGS_FILE, 'utf8')

  await execPnpm(['run', 'foo', 'arg', '--', '--flag=true'])

  const { default: args } = await import(path.resolve('args.json'))
  expect(args).toStrictEqual([
    [],
    ['arg', '--', '--flag=true'],
    [],
  ])
})

test('exit code of child process is preserved', async () => {
  prepare({
    scripts: {
      foo: 'exit 87',
    },
  })
  const result = execPnpmSync(['run', 'foo'])
  expect(result.status).toBe(87)
})

test('test -r: pass the args to the command that is specified in the build script of a package.json manifest', async () => {
  preparePackages([{
    name: 'project',
    scripts: {
      test: 'ts-node test',
    },
  }])

  const result = execPnpmSync(['test', '-r', 'arg', '--', '--flag=true'])

  expect((result.stdout as Buffer).toString('utf8')).toMatch(/ts-node test "arg" "--flag=true"/)
})

test('start: run "node server.js" by default', async () => {
  prepare({}, { manifestFormat: 'YAML' })

  fs.writeFileSync('server.js', 'console.log("Hello world!")', 'utf8')

  const result = execPnpmSync(['start'])

  expect((result.stdout as Buffer).toString('utf8')).toMatch(/Hello world!/)
})

test('install-test: install dependencies and runs tests', async () => {
  prepare({
    scripts: {
      test: 'node -e "process.stdout.write(\'test\')" > ./output.txt',
    },
  }, { manifestFormat: 'JSON5' })

  await execPnpm(['install-test'])

  const scriptsRan = (fs.readFileSync('output.txt')).toString()
  expect(scriptsRan.trim()).toStrictEqual('test')
})

test('silent run only prints the output of the child process', async () => {
  prepare({
    scripts: {
      hi: 'echo hi && exit 1',
    },
  })

  const result = execPnpmSync(['run', '--silent', 'hi'])

  expect(result.stdout.toString().trim()).toBe('hi')
})

testOnPosix('pnpm run with preferSymlinkedExecutables true', async () => {
  prepare({
    scripts: {
      build: 'node -e "console.log(process.env.NODE_PATH)"',
    },
  })

  const npmrc = `
    prefer-symlinked-executables=true=true
  `

  fs.writeFileSync('.npmrc', npmrc, 'utf8')

  const result = execPnpmSync(['run', 'build'])

  expect(result.stdout.toString()).toContain(`project${path.sep}node_modules${path.sep}.pnpm${path.sep}node_modules`)
})

testOnPosix('pnpm run with preferSymlinkedExecutables and custom virtualStoreDir', async () => {
  prepare({
    scripts: {
      build: 'node -e "console.log(process.env.NODE_PATH)"',
    },
  })

  const npmrc = `
    virtual-store-dir=/foo/bar
    prefer-symlinked-executables=true=true
  `

  fs.writeFileSync('.npmrc', npmrc, 'utf8')

  const result = execPnpmSync(['run', 'build'])

  expect(result.stdout.toString()).toContain(`${path.sep}foo${path.sep}bar${path.sep}node_modules`)
})

test('collapse output when running multiple scripts in one project', async () => {
  prepare({
    scripts: {
      script1: 'echo 1',
      script2: 'echo 2',
    },
  })

  const result = execPnpmSync(['run', '/script[12]/'])

  const output = result.stdout.toString()
  expect(output).toContain('script1: 1')
  expect(output).toContain('script2: 2')
})

test('do not collapse output when running multiple scripts in one project sequentially', async () => {
  prepare({
    scripts: {
      script1: 'echo 1',
      script2: 'echo 2',
    },
  })

  const result = execPnpmSync(['--workspace-concurrency=1', 'run', '/script[12]/'])

  const output = result.stdout.toString()
  expect(output).not.toContain('script1: 1')
  expect(output).not.toContain('script2: 2')
})

test('--parallel should work with single project', async () => {
  prepare({
    scripts: {
      script1: 'echo 1',
      script2: 'echo 2',
    },
  })

  const result = execPnpmSync(['--parallel', 'run', '/script[12]/'])

  const output = result.stdout.toString()
  expect(output).toContain('script1: 1')
  expect(output).toContain('script2: 2')
})

test('--reporter-hide-prefix should hide workspace prefix', async () => {
  prepare({
    scripts: {
      script1: 'echo 1',
      script2: 'echo 2',
    },
  })

  const result = execPnpmSync(['--parallel', '--reporter-hide-prefix', 'run', '/script[12]/'])

  const output = result.stdout.toString()
  expect(output).toContain('1')
  expect(output).not.toContain('script1: 1')
  expect(output).toContain('2')
  expect(output).not.toContain('script2: 2')
})

test('recursive run when some packages define different node versions', async () => {
  preparePackages([
    {
      name: 'node-version-undefined',
      scripts: {
        'print-node-version': 'node -v',
      },
    },
    {
      name: 'node-version-18',
      scripts: {
        'print-node-version': 'node -v',
      },
      pnpm: {
        executionEnv: {
          nodeVersion: '18.0.0',
        },
      },
    },
    {
      name: 'node-version-20',
      scripts: {
        'print-node-version': 'node -v',
      },
      pnpm: {
        executionEnv: {
          nodeVersion: '20.0.0',
        },
      },
    },
  ])

  const runPrintNodeVersion = (args: string[]) =>
    execPnpmSync(args)
      .stdout
      .toString()
      .trim()
      .split('\n')
      .filter(x => /v[0-9]+\.[0-9]+\.[0-9]+/.test(x))
      .sort()

  expect(
    runPrintNodeVersion(['run', '-r', 'print-node-version'])
  ).toStrictEqual([
    'node-version-18 print-node-version: v18.0.0',
    'node-version-20 print-node-version: v20.0.0',
    `node-version-undefined print-node-version: ${process.version}`,
  ])

  expect(
    runPrintNodeVersion(['run', '-r', '--use-node-version=19.0.0', 'print-node-version'])
  ).toStrictEqual([
    'node-version-18 print-node-version: v18.0.0',
    'node-version-20 print-node-version: v20.0.0',
    'node-version-undefined print-node-version: v19.0.0',
  ])
})
