const cli = require('heroku-cli-util')
const Dyno = require('@heroku-cli/plugin-run-v5').Dyno
const co = require('co')
const api = require('../../lib/heroku-api')
const git = require('../../lib/git')
const source = require('../../lib/source')
const TestRun = require('../../lib/test-run')
const Utils = require('../../lib/utils')
const PipelineCompletion = require('../../lib/completions')

// Default command. Run setup, source profile.d scripts and open a bash session
const SETUP_COMMAND = 'ci setup && eval $(ci env)'

function * run (context, heroku) {
  const pipeline = yield Utils.getPipeline(context, heroku)

  const pipelineRepository = yield api.pipelineRepository(heroku, pipeline.id)
  const organization = pipelineRepository.organization &&
                       pipelineRepository.organization.name

  const commit = yield git.readCommit('HEAD')
  const sourceBlobUrl = yield cli.action('Preparing source', co(function * () {
    return yield source.createSourceBlob(commit.ref, context, heroku)
  }))

  // Create test run and wait for it to transition to `debugging`
  const testRun = yield cli.action('Creating test run', co(function * () {
    const run = yield api.createTestRun(heroku, {
      commit_branch: commit.branch,
      commit_message: commit.message,
      commit_sha: commit.ref,
      debug: true,
      clear_cache: !!context.flags['no-cache'],
      organization,
      pipeline: pipeline.id,
      source_blob_url: sourceBlobUrl
    })

    return yield TestRun.waitForStates(['debugging', 'errored'], run, { heroku })
  }))

  if (testRun.status === 'errored') {
    cli.exit(1, `Test run creation failed while ${testRun.error_state} with message "${testRun.message}"`)
  }

  const appSetup = yield api.appSetup(heroku, testRun.app_setup.id)
  const noSetup = context.flags['no-setup']

  cli.log(`${noSetup ? 'Attaching' : 'Running setup and attaching'} to test dyno...`)

  if (noSetup) {
    cli.warn('Skipping test setup phase.')
    cli.warn(`Run \`${SETUP_COMMAND}\``)
    cli.warn('to execute a build and configure the environment')
  }

  const testNodes = yield api.testNodes(heroku, testRun.id)

  const dyno = new Dyno({
    heroku,
    app: appSetup.app.id,
    showStatus: false
  })

  dyno.dyno = { attach_url: Utils.dig(testNodes, 0, 'dyno', 'attach_url') }

  function sendSetup (data, connection) {
    if (data.toString().includes('$')) {
      dyno.write(SETUP_COMMAND + '\n')
      dyno.removeListener('data', sendSetup)
    }
  }

  if (!noSetup) {
    dyno.on('data', sendSetup)
  }

  try {
    yield dyno.attach()
  } catch (err) {
    if (err.exitCode) cli.exit(err.exitCode, err)
    else throw err
  }

  yield cli.action(
    'Cleaning up',
    api.updateTestRun(heroku, testRun.id, {
      status: 'cancelled',
      message: 'debug run cancelled by Heroku CLI'
    })
  )
}

module.exports = {
  topic: 'ci',
  command: 'debug',
  wantsApp: true,
  needsAuth: true,
  description: 'opens an interactive test debugging session with the contents of the current directory',
  help: `Example:

    $ heroku ci:debug
    Preparing source... done
    Creating test run... done
    Running setup and attaching to test dyno...

~ $
`,
  flags: [
    {
      name: 'no-setup',
      hasValue: false,
      description: 'start test dyno without running test-setup'
    },
    {
      name: 'pipeline',
      char: 'p',
      hasValue: true,
      description: 'pipeline',
      completion: PipelineCompletion
    },
    {
      name: 'no-cache',
      hasValue: false,
      description: 'start test run with an empty cache'
    }
  ],
  run: cli.command(co.wrap(run))
}
