jest.setTimeout(20000)
jest.mock('inquirer')

const invoke = require('../lib/invoke')
const { expectPrompts } = require('inquirer')
const create = require('@vue/cli-test-utils/createTestProject')

const parseJS = file => {
  const res = {}
  ;(new Function('module', file))(res) // eslint-disable-line no-new-func
  return res.exports
}

const baseESLintConfig = Object.assign({}, require('@vue/cli-plugin-eslint/eslintOptions').config({
  hasPlugin: (name) => { if (name === 'babel') { return true } }
}), {
  rules: {
    'no-console': 'off',
    'no-debugger': 'off'
  }
})

async function createAndInstall (name) {
  const project = await create(name, {
    plugins: {
      '@vue/cli-plugin-babel': {}
    }
  })
  // mock install
  const pkg = JSON.parse(await project.read('package.json'))
  pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
  await project.write('package.json', JSON.stringify(pkg, null, 2))
  return project
}

async function assertUpdates (project) {
  const updatedPkg = JSON.parse(await project.read('package.json'))
  expect(updatedPkg.scripts.lint).toBe('vue-cli-service lint')
  expect(updatedPkg.devDependencies).toHaveProperty('lint-staged')
  expect(updatedPkg.gitHooks).toEqual({
    'pre-commit': 'lint-staged'
  })

  const eslintrc = parseJS(await project.read('.eslintrc.js'))
  expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
    extends: ['plugin:vue/essential', '@vue/airbnb']
  }))

  const lintedMain = await project.read('src/main.js')
  expect(lintedMain).toMatch(';') // should've been linted in post-generate hook
}

test('invoke with inline options', async () => {
  const project = await createAndInstall(`invoke-inline`)
  await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb --lintOn save,commit`)
  await assertUpdates(project)
})

test('invoke with prompts', async () => {
  const project = await createAndInstall(`invoke-prompts`)
  expectPrompts([
    {
      message: `Pick an ESLint config`,
      choices: [`Error prevention only`, `Airbnb`, `Standard`, `Prettier`],
      choose: 1
    },
    {
      message: `Pick additional lint features`,
      choices: [`on save`, 'on commit'],
      check: [0, 1]
    }
  ])
  // need to be in the same process to have inquirer mocked
  // so calling directly
  const cwd = process.cwd()
  // By default, @babel/eslint-parser will load babel.config.js in `path.resolve('.')`(equals `process.cwd()`) through @babel/core.
  // chdir, and let @babel/eslint-parser find the babel.config.js in our test project
  process.chdir(project.dir)

  await invoke(`eslint`, {}, project.dir)
  await assertUpdates(project)

  // restore
  process.chdir(cwd)
})

test('invoke with ts', async () => {
  const project = await create(`invoke-existing`, {
    useConfigFiles: true,
    plugins: {
      '@vue/cli-plugin-babel': {},
      '@vue/cli-plugin-eslint': { config: 'base' }
    }
  })
  // mock install
  const pkg = JSON.parse(await project.read('package.json'))
  pkg.devDependencies['@vue/cli-plugin-typescript'] = '*'
  await project.write('package.json', JSON.stringify(pkg, null, 2))

  // mock existing vue.config.js
  await project.write('vue.config.js', `module.exports = { lintOnSave: 'default' }`)

  const eslintrc = parseJS(await project.read('.eslintrc.js'))
  expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
    extends: ['plugin:vue/essential', 'eslint:recommended']
  }))

  await project.run(`${require.resolve('../bin/vue')} invoke typescript --classComponent --useTsWithBabel`)

  const updatedESLintrc = parseJS(await project.read('.eslintrc.js'))
  expect(updatedESLintrc).toEqual(Object.assign({}, baseESLintConfig, {
    extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/typescript'],
    parserOptions: {
      parser: '@typescript-eslint/parser'
    }
  }))
})

test('invoke with existing files (yaml)', async () => {
  const project = await create(`invoke-existing-yaml`, {
    useConfigFiles: true,
    plugins: {
      '@vue/cli-plugin-babel': {},
      '@vue/cli-plugin-eslint': {}
    }
  })

  const eslintrc = parseJS(await project.read('.eslintrc.js'))
  expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
    extends: ['plugin:vue/essential', 'eslint:recommended']
  }))

  await project.rm(`.eslintrc.js`)
  await project.write(`.eslintrc.yml`, `
root: true
extends:
  - plugin:vue/essential
  - eslint:recommended
  `.trim())

  await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb`)

  const updated = await project.read('.eslintrc.yml')
  expect(updated).toMatch(`
extends:
  - plugin:vue/essential
  - eslint:recommended
  - '@vue/airbnb'
`.trim())
})

test('invoking a plugin that renames files', async () => {
  const project = await create(`invoke-rename`, { plugins: {} })
  const pkg = JSON.parse(await project.read('package.json'))
  pkg.devDependencies['@vue/cli-plugin-typescript'] = '*'
  await project.write('package.json', JSON.stringify(pkg, null, 2))
  await project.run(`${require.resolve('../bin/vue')} invoke typescript -d`)
  expect(project.has('src/main.js')).toBe(false)
})

test('should prompt if invoking in a git repository with uncommitted changes', async () => {
  delete process.env.VUE_CLI_SKIP_DIRTY_GIT_PROMPT
  const project = await create('invoke-dirty', {
    plugins: {
      '@vue/cli-plugin-babel': {}
    }
  })
  await project.write('some-random-file', '')
  expectPrompts([
    {
      message: `Still proceed?`,
      confirm: true
    }
  ])
  await invoke(`babel`, {}, project.dir)
})

test.todo('invoke: should respect plugin resolveFrom')