const fs = require('fs').promises
const path = require('path')
const test = require('ava')
const {execFilePromise, matchOutput, wait} = require('./util')

// We are going to run the server directly, not through npm
const {INIT_CWD, ...MAKE_ENV} = process.env
process.chdir(__dirname)

async function makeC(dir) {
  try { await fs.rmdir(dir, {recursive: true}) }
  catch {}

  await fs.mkdir(dir)
  await fs.writeFile(`${dir}/c`, 'cde')
}

async function make(dir, ...targets) {
  const {stdout} = await execFilePromise(
    'node', ['../../make.js', '../special-cases-makefile.js', ...targets],
    {cwd: dir, env: MAKE_ENV}
  )
  return stdout
}

async function makeError(dir, ...targets) {
  let output
  try { output = await make(dir, ...targets) }
  catch (e) { return {output: e.stdout, code: e.code} }

  return {output, code: 0}
}

test('request source file', async t => {
  const dir = 'source-file'
  await makeC(dir)
  const {mtimeMs} = await fs.stat(`${dir}/c`)

  // Request that c (an existing file) is built
  await wait(100)
  const output = await make(dir, 'c')

  // Check that no files were created and c was not modified
  const files = await fs.readdir(dir)
  t.deepEqual(files, ['c'], 'Incorrect list of files')
  const {mtimeMs: newMtime} = await fs.stat(`${dir}/c`)
  t.deepEqual(newMtime, mtimeMs, 'File c should not have been modified')

  // Check that the output from make.js is valid
  t.deepEqual(output, '', 'Unexpected make.js output')
})

test('no dependencies and no build command', async t => {
  const dir = 'no-dependencies'
  await makeC(dir)
  const output = await make(dir, 'all')

  // Check that the correct files exist and have the right contents
  const files = await fs.readdir(dir)
  t.deepEqual(new Set(files), new Set(['a', 'b', 'c']), 'Incorrect list of files')
  const aContents = await fs.readFile(`${dir}/a`, 'utf8')
  t.deepEqual(aContents, 'abc', `File ${dir}/a has incorrect contents`)
  const bContents = await fs.readFile(`${dir}/b`, 'utf8')
  t.deepEqual(bContents, 'bcd', `File ${dir}/b has incorrect contents`)
  const cContents = await fs.readFile(`${dir}/c`, 'utf8')
  t.deepEqual(cContents, 'cde', `File ${dir}/c has incorrect contents`)

  // Check that the output from make.js is valid
  matchOutput(output, [
    {target: 'a', dependencies: [], lines: ['node ../create.js a abc']},
    {target: 'b', dependencies: [], lines: ['node ../create.js b bcd']}
  ])

  // Store the modification times to ensure make.js doesn't rebuild the files
  const initialMtimes = await Promise.all(files.map(async file => {
    const {mtimeMs} = await fs.stat(`${dir}/${file}`)
    return {file, mtimeMs}
  }))

  // Running make.js again should not do anything
  await wait(100)
  const output2 = await make(dir, 'all')
  await Promise.all(initialMtimes.map(async ({file, mtimeMs}) => {
    const {mtimeMs: newMtime} = await fs.stat(`${dir}/${file}`)
    t.deepEqual(newMtime, mtimeMs, `File ${dir}/${file} should not have been modified`)
  }))
  t.deepEqual(output2, '', 'Unexpected make.js output')
})

test('phony target', async t => {
  // Build a, b, and c
  const dir = 'phony-target'
  await makeC(dir)
  await make(dir, 'all')
  const files = await fs.readdir(dir)
  t.deepEqual(new Set(files), new Set(['a', 'b', 'c']), 'Incorrect list of files')

  // Run 'clean' several times; the command should run every time
  for (let i = 0; i < 10; i++) {
    const output = await make(dir, 'clean')
    t.deepEqual(await fs.readdir(dir), ['c'], 'Incorrect list of files')
    t.deepEqual(output, 'node ../remove.js a b\n', 'Unexpected make.js output')
  }
})

test('build error', async t => {
  const dir = 'build-error'
  try { await fs.rmdir(dir, {recursive: true}) }
  catch {}
  await fs.mkdir(dir)

  // Build error1, then error2, then error3
  const {output, code} = await makeError(dir, 'error3')
  t.truthy(code, 'make.js should have exited with an error')
  t.deepEqual(
    output,
    'node ../create.js error1 e\n' + 'node ../cat.js error2 error2\n',
    'Unexpected make.js output'
  )

  // error1 should have been created, but not error2
  const files = await fs.readdir(dir)
  t.deepEqual(files, ['error1'], 'Incorrect list of files')

  // Building again should only try to build error2
  const {output: output2, code: code2} = await makeError(dir, 'error3')
  t.truthy(code2, 'make.js should have exited with an error')
  t.deepEqual(output2, 'node ../cat.js error2 error2\n', 'Unexpected make.js output')
})