<gh_stars>0
const path = require('path')

const AggregateError = require('aggregate-error')
const execa = require('execa')
const fs = require('fs-extra')
const fetch = require('isomorphic-fetch')
const {merge, pick} = require('lodash')
const auto = require('auto-promised')

const {
  collectingCoverage,
  downloadFile,
  getTestFolderFilename,
  isCi,
  isUiRepo,
  loadYamlFile,
  requireEnvVars,
  spawnDetachedProcess,
  writeYamlFile
} = require('./utils')

const serverJarFilename = 'dt-latest-dev.jar'

/**
 * download, configure and start an instance of datatools-server
 */
async function startBackendServer () {
  // if running this from the datatools-server repo, skip this step as there
  // are special steps taken in the datatools-server repo in order to collect
  // back-end code coverage
  if (!isUiRepo) return

  console.log('prepare to start backend server')

  // make sure required environment variables are set
  try {
    requireEnvVars([
      'AUTH0_CLIENT_ID',
      'AUTH0_DOMAIN',
      'AUTH0_SECRET',
      'AUTH0_API_CLIENT',
      'AUTH0_API_SECRET',
      'OSM_VEX',
      'SPARKPOST_EMAIL',
      'SPARKPOST_KEY',
      'S3_BUCKET',
      'TRANSITFEEDS_KEY'
    ])
  } catch (e) {
    console.error(`At least one required env var is missin: ${e}`)
    throw e
  }

  const serverFolder = path.join(
    process.env.TRAVIS_BUILD_DIR,
    '..',
    'datatools-server'
  )
  const serverJarFile = path.join(serverFolder, serverJarFilename)
  const envFile = path.join(serverFolder, 'env.yml')
  const serverFile = path.join(serverFolder, 'server.yml')
  const baseDtServerGithubConfigUrl = 'https://raw.githubusercontent.com/ibi-group/datatools-server/dev/configurations/default/'

  try {
    await auto({
      async makeGtfsPlusFolder () {
        console.log('creating directory for gtfs plus files')
        await fs.mkdirp('/tmp/gtfsplus')
      },
      async makeServerFolder () {
        console.log('creating directory for server files')
        await fs.mkdirp(serverFolder)
      },
      downloadJar: ['makeServerFolder', (results) => {
        console.log('downloading server jar')
        return downloadFile(
          `https://s3.amazonaws.com/datatools-builds/${serverJarFilename}`,
          serverJarFile
        )
      }],
      downloadEnvTemplate: ['makeServerFolder', (results) => {
        console.log('downloading backend env.yml template')
        return downloadFile(
          `${baseDtServerGithubConfigUrl}env.yml.tmp`,
          envFile
        )
      }],
      downloadServerTemplate: ['makeServerFolder', (results) => {
        console.log('downloading backend server.yml template')
        return downloadFile(
          `${baseDtServerGithubConfigUrl}server.yml.tmp`,
          serverFile
        )
      }],
      readEnvTemplate: ['downloadEnvTemplate', (results) => {
        console.log('loading backend env template')
        return loadYamlFile(envFile)
      }],
      readServerTemplate: ['downloadServerTemplate', (results) => {
        console.log('loading backend server template')
        return loadYamlFile(serverFile)
      }],
      writeEnvFile: ['readEnvTemplate', (results) => {
        console.log('writing backend env.yml')

        return writeYamlFile(
          envFile,
          merge(
            results.readEnvTemplate,
            pick(
              process.env,
              [
                'AUTH0_CLIENT_ID',
                'AUTH0_DOMAIN',
                'AUTH0_SECRET',
                'AUTH0_API_CLIENT',
                'AUTH0_API_SECRET',
                'OSM_VEX',
                'SPARKPOST_KEY',
                'SPARKPOST_EMAIL'
              ]
            )
          )
        )
      }],
      writeServerFile: ['readServerTemplate', (results) => {
        console.log('writing backend server.yml')

        const {
          S3_BUCKET,
          TRANSITFEEDS_KEY
        } = process.env

        const serverEnv = results.readServerTemplate
        serverEnv.application.client_assets_url = 'http://localhost:4000'
        serverEnv.application.data.gtfs_s3_bucket = S3_BUCKET
        serverEnv.application.data.use_s3_storage = true
        serverEnv.extensions.transitfeeds.key = TRANSITFEEDS_KEY
        return writeYamlFile(
          serverFile,
          serverEnv
        )
      }],
      startServer: [
        'makeGtfsPlusFolder',
        'downloadJar',
        'writeEnvFile',
        'writeServerFile',
        async (results) => {
          console.log('starting backend server')

          try {
            await spawnDetachedProcess(
              'java',
              [
                '-Xmx6G',
                '-jar',
                serverJarFile,
                envFile,
                serverFile
              ],
              'datatools-server'
            )
          } catch (e) {
            console.error(`error starting datatools-server: ${e}`)
          }
        }
      ]
    })
  } catch (err) {
    console.error(`failed to start backed server due to error: ${err}`)
    throw err
  }

  console.log('backend server started successfully')
}

/**
 * Perform all necessary actions to start the datatools-ui dev server
 */
async function startClientServer () {
  console.log('prepare to start client dev server')

  // make sure required environment variables are set
  try {
    requireEnvVars([
      'AUTH0_CLIENT_ID',
      'AUTH0_DOMAIN',
      'E2E_AUTH0_PASSWORD',
      'E2E_AUTH0_USERNAME',
      'GRAPH_HOPPER_KEY',
      'MAPBOX_ACCESS_TOKEN',
      'S3_BUCKET',
      'TRANSITFEEDS_KEY'
    ])
  } catch (e) {
    console.error(`At least one required env var is missin: ${e}`)
    throw e
  }

  // set the working directories for datatools-ui
  const datatoolsUiDir = path.join(
    process.env.TRAVIS_BUILD_DIR,
    '..',
    'datatools-ui'
  )
  const defaultConfigFolder = path.join(
    datatoolsUiDir,
    'configurations',
    'default'
  )

  // asynchronously complete the remaining tasks as they involve a lot of file
  // writing that can be performed asynchronously.
  try {
    await auto({
      async startClientDevServer () {
        console.log('starting client dev server')

        try {
          await spawnDetachedProcess(
            'node', ['__tests__/test-utils/travis-client-dev-server.js'],
            'client-dev-server'
          )
        } catch (e) {
          console.error(`error starting dev server: ${e}`)
          throw e
        }
      },
      writeE2eEnvYml: () => {
        console.log('writing e2e env.yml')

        // create e2e config file with valid auth0 login
        // > configurations/end-to-end/env.yml
        return writeYamlFile(
          path.join(datatoolsUiDir, 'configurations', 'end-to-end', 'env.yml'),
          {
            password: <PASSWORD>,
            username: process.env.E2E_AUTH0_USERNAME
          }
        )
      },
      loadDefaultEnvYml: () => {
        console.log('loading client env.yml template')

        return loadYamlFile(path.join(defaultConfigFolder, 'env.yml.tmp'))
      },
      loadDefaultSettingsYml: () => {
        console.log('loading client settings.yml template')

        return loadYamlFile(path.join(defaultConfigFolder, 'settings.yml'))
      },
      writeDefaultEnvYml: ['loadDefaultEnvYml', (results) => {
        console.log('writing client env.yml template')

        return writeYamlFile(
          path.join(defaultConfigFolder, 'env.yml'),
          merge(
            results.loadDefaultEnvYml,
            pick(
              process.env,
              [
                'AUTH0_CLIENT_ID',
                'AUTH0_DOMAIN',
                'GRAPH_HOPPER_KEY',
                'MAPBOX_ACCESS_TOKEN'
              ]
            )
          )
        )
      }],
      writeDefaultSettingsYml: ['loadDefaultSettingsYml', (results) => {
        console.log('writing client settings.yml template')

        const {
          S3_BUCKET
        } = process.env

        const clientSettings = results.loadDefaultSettingsYml
        clientSettings.S3_BUCKET = S3_BUCKET
        return writeYamlFile(
          path.join(defaultConfigFolder, 'settings.yml'),
          clientSettings
        )
      }],
      buildClientDistFiles: [
        'writeE2eEnvYml',
        'writeDefaultEnvYml',
        'writeDefaultSettingsYml',
        async (results) => {
          console.log('building client dist files')

          const args = ['run', 'build-dev', '--prefix', datatoolsUiDir]

          // if running in the ui environment, build with instrumented code
          if (isUiRepo) {
            args.push('--')
            args.push('--instrument')
          }

          let stdout, stderr
          try {
            ({stdout, stderr} = await execa('npm', args))
          } catch (e) {
            console.error(`An error occurred while building client dist files: ${e}`)
            throw e
          }

          function writeOutputs (data, logType) {
            return fs.writeFile(
              getTestFolderFilename(`mastarm-build-${logType}.log`),
              data
            )
          }

          return Promise.all([
            writeOutputs(stdout, 'out'),
            writeOutputs(stderr, 'err')
          ])
        }
      ]
    })
  } catch (err) {
    console.error(`failed to start frontend server due to error: ${err}`)
    throw err
  }

  console.log('successfully started client dev server')
}

/**
 * Perform all necessary actions to start the datatools-ui dev server
 */
async function startOtp () {
  const otpJarFilename = 'otp-1.4.0-shaded.jar'

  console.log('downloading otp jar')

  // download otp
  await downloadFile(
    'https://repo1.maven.org/maven2/org/opentripplanner/otp/1.4.0/otp-1.4.0-shaded.jar',
    otpJarFilename
  )

  console.log('starting otp')

  // start otp
  try {
    await spawnDetachedProcess(
      'java',
      [
        '-Xmx6G',
        '-jar',
        otpJarFilename,
        '--server',
        '--insecure',
        '--router',
        'default'
      ],
      'otp'
    )
  } catch (e) {
    console.error(`error starting dev server: ${e}`)
    throw e
  }

  console.log('successfully started otp')
}

/**
 * delete and recreate test results folder
 */
function recreateEndToEndTestResultDirectory () {
  const testFolderFilename = getTestFolderFilename()
  fs.removeSync(testFolderFilename)
  fs.ensureDirSync(testFolderFilename)
}

/**
 * Verifies that the needed items in order to run the e2e tests locally have
 * been setup properly.
 */
async function verifySetupForLocalEnvironment () {
  const errors = []

  // make sure e2e.yml exists
  try {
    await fs.stat('configurations/end-to-end/env.yml')
  } catch (e) {
    errors.push(new Error('Failed to detect file `configurations/end-to-end/env.yml`'))
  }

  // make sure services are running
  const endpointChecks = [
    {
      name: 'Front-end server',
      url: 'http://localhost:9966'
    }, {
      name: 'Back-end server',
      url: 'http://localhost:4000'
    }, {
      name: 'OTP server',
      url: 'http://localhost:8080'
    }
  ]

  await Promise.all(
    endpointChecks.map(
      endpoint => (
        // return an executed promise
        async () => {
          try {
            const fetchResult = await fetch(endpoint.url)
            if (fetchResult.status >= 400) {
              throw new Error('Bad response')
            }
          } catch (e) {
            errors.push(
              new Error(
                `Unable to fetch from ${endpoint.name} (${endpoint.url})`
              )
            )
          }
        }
      )()
    )
  )

  // TODO: verify that aws credentials have been setup properly

  if (errors.length > 0) {
    throw new AggregateError(errors)
  }
}

/**
 * Sets up the needed items in order to be ran in a continuous integration
 * environment. For now, Travis CI is the only CI environment that this code is
 * built for.
 */
function setupForContinuousIntegrationEnvironment () {
  return Promise.all([
    startBackendServer(),
    startClientServer(),
    startOtp()
  ])
    .catch(e => {
      console.error(`CI environment setup failed! ${e}`)
      throw e
    })
}

/**
 * Start a server to collect coverage from the e2e tests.
 */
function startCoverageServer () {
  return new Promise((resolve, reject) => {
    console.log('starting coverage server')

    try {
      spawnDetachedProcess(
        'node',
        ['__tests__/test-utils/e2e-coverage-collector-server.js'],
        'e2e-coverage-collector-server'
      )
    } catch (e) {
      console.error(`error starting coverage server: ${e}`)
      return reject(e)
    }

    console.log('successfully started coverage server')
    resolve()
  })
}

/**
 * Perform all needed actions to get the current environment ready for
 * performing the e2e tests. This is called from the e2e-environment.js.
 */
module.exports = async function () {
  // syncrhonously recreate the test results directory in to avoid race
  // conditions
  recreateEndToEndTestResultDirectory()

  // do different setup depending on runtime environment
  const setupItems = []
  if (isCi) {
    setupItems.push(setupForContinuousIntegrationEnvironment())
  } else {
    setupItems.push(verifySetupForLocalEnvironment())
  }
  if (collectingCoverage) setupItems.push(startCoverageServer())

  return Promise.all(setupItems)
    .catch(e => {
      console.error(`e2e setup failed! ${e}`)
      throw e
    })
}
