/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/* global $ argv, path, cd, nothrow */

'use strict'

let ora
let closest
try {
  require('zx/globals')
  ora = require('ora')
  const fl = require('fastest-levenshtein')
  closest = fl.closest
} catch (err) {
  console.log('It looks like you didn\'t install the project dependencies, please run \'make setup\'')
  process.exit(1)
}

const { readFile, writeFile } = require('fs/promises')

// enable subprocess colors
process.env.COLOR = true
process.env.FORCE_COLOR = 3

$.verbose = false

const spinner = ora('Loading').start()
const compilerPath = __dirname
const tsGeneratorPath = path.join(__dirname, '..', 'typescript-generator')
const cloneEsPath = path.join(__dirname, '..', '..', 'clients-flight-recorder', 'scripts', 'clone-elasticsearch')
const uploadRecordingsPath = path.join(__dirname, '..', '..', 'clients-flight-recorder', 'scripts', 'upload-recording')
const tsValidationPath = path.join(__dirname, '..', '..', 'clients-flight-recorder', 'scripts', 'types-validator')
const DAY = 1000 * 60 * 60 * 24
const specPath = path.join(__dirname, '..', 'specification')
const outputPath = path.join(__dirname, '..', 'output/schema')

const apis = require('../output/schema/schema.json')
  .endpoints
  .map(endpoint => endpoint.name)

async function run () {
  spinner.text = 'Checking requirements'

  const noCache = argv.cache === false
  const metadata = await readMetadata()
  const lastRun = metadata.lastRun ? new Date(metadata.lastRun) : new Date(0)
  const isStale = lastRun.getTime() + DAY < Date.now()

  if (typeof argv.api !== 'string') {
    spinner.fail('You must specify the api, for example: \'make validate api=index type=request stack-version=8.1.0-SNAPSHOT\'')
    process.exit(1)
  }

  if (!apis.includes(argv.api)) {
    spinner.fail(`The api '${argv.api}' does not exists, did you mean '${closest(argv.api, apis)}'?`)
    process.exit(1)
  }

  // if true it's because the make target wasn't configured with a type argument
  if (argv.type !== true && argv.type !== 'request' && argv.type !== 'response') {
    spinner.fail('You must specify the type (request or response), for example: \'make validate api=index type=request stack-version=8.1.0-SNAPSHOT\'')
    process.exit(1)
  }

  if (typeof argv['stack-version'] !== 'string') {
    spinner.fail('You must specify the stack version, for example: \'make validate api=index type=request stack-version=8.1.0-SNAPSHOT\'')
    process.exit(1)
  }

  const isFlightRecorderCloned = await $`[[ -d ${path.join(__dirname, '..', '..', 'clients-flight-recorder')} ]]`.exitCode === 0
  if (!isFlightRecorderCloned) {
    spinner.text = 'It looks like you didn\'t cloned the flight recorder, doing that for you'
    await $`git clone https://github.com/elastic/clients-flight-recorder.git ${path.join(__dirname, '..', '..', 'clients-flight-recorder')}`
  } else if (isStale) {
    spinner.text = 'Pulling the latest flight recorder changes'
    cd(path.join(__dirname, '..', '..', 'clients-flight-recorder'))
    await $`git pull`
    cd(path.join(compilerPath, '..'))
  }

  const isCompilerInstalled = await $`[[ -d ${path.join(compilerPath, 'node_modules')} ]]`.exitCode === 0
  const isTsGeneratorInstalled = await $`[[ -d ${path.join(tsGeneratorPath, 'node_modules')} ]]`.exitCode === 0
  if (noCache || !isCompilerInstalled || !isTsGeneratorInstalled) {
    spinner.text = 'It looks like you didn\'t installed the project dependencies, doing that for you'
    await $`npm install --prefix ${compilerPath}`
    await $`npm install --prefix ${tsGeneratorPath}`
  }

  const isCloneEsInstalled = await $`[[ -d ${path.join(cloneEsPath, 'node_modules')} ]]`.exitCode === 0
  const isUploadRecordingInstalled = await $`[[ -d ${path.join(uploadRecordingsPath, 'node_modules')} ]]`.exitCode === 0
  const isTypesValidatorInstalled = await $`[[ -d ${path.join(tsValidationPath, 'node_modules')} ]]`.exitCode === 0

  if (noCache || !isCloneEsInstalled || !isUploadRecordingInstalled || !isTypesValidatorInstalled) {
    spinner.text = 'It looks like you didn\'t installed the flight recorder project dependencies, doing that for you'
    await $`npm install --prefix ${cloneEsPath}`
    await $`npm install --prefix ${uploadRecordingsPath}`
    await $`npm install --prefix ${tsValidationPath}`
  }

  const isCompilerBuilt = await $`[[ -d ${path.join(compilerPath, 'lib')} ]]`.exitCode === 0
  if (noCache || isStale || !isCompilerBuilt) {
    spinner.text = 'Optimizing the compiler'
    await $`npm run build --prefix ${compilerPath}`
  }

  const isTsGeneratorBuilt = await $`[[ -d ${path.join(tsGeneratorPath, 'lib')} ]]`.exitCode === 0
  if (noCache || isStale || !isTsGeneratorBuilt) {
    spinner.text = 'Optimizing the ts generator'
    await $`npm run build --prefix ${tsGeneratorPath}`
  }

  {
    spinner.text = 'Compiling specification'
    const Process = await nothrow($`npm run compile:specification --prefix ${compilerPath}`)
    if (Process.exitCode !== 0) {
      spinner.fail(removeHeader(Process.stdout))
      process.exit(1)
    }
  }

  {
    spinner.text = 'Generating schema'
    const Process = await nothrow($`node ${path.join(compilerPath, 'lib', 'index.js')} --spec ${specPath} --output ${outputPath}`)
    if (Process.exitCode !== 0) {
      spinner.fail(removeHeader(Process.stdout))
      console.log(Process.stderr)
      process.exit(1)
    }
  }

  {
    spinner.text = 'Generating typescript view'
    const Process = await nothrow($`node ${path.join(tsGeneratorPath, 'lib', 'index.js')}`)
    if (Process.exitCode !== 0) {
      spinner.fail(removeHeader(Process.toString()))
      process.exit(1)
    }
  }

  {
    spinner.text = 'Validating typescript view'
    const Process = await nothrow($`npm run validate-ts-view --prefix ${compilerPath}`)
    if (Process.exitCode !== 0) {
      spinner.fail(removeHeader(Process.toString()))
      process.exit(1)
    }
  }

  spinner.text = 'Running validations'

  const branchName = argv['stack-version'].startsWith('7.') ? '7.x' : argv['stack-version'].slice(0, 3)

  if (noCache || isStale || metadata.branchName !== branchName) {
    metadata.lastRun = new Date()
    metadata.branchName = branchName

    spinner.text = 'Downloading recordings'
    await $`node ${path.join(uploadRecordingsPath, 'download.js')} --branch ${branchName}`

    spinner.text = 'Fetching artifacts'
    await $`node ${path.join(cloneEsPath, 'index.js')} --version ${argv['stack-version']}`
  }

  cd(tsValidationPath)
  spinner.text = 'Validating endpoints'
  // the ts validator will copy types.ts and schema.json autonomously
  const flags = ['--verbose']
  if (argv.type === true) {
    flags.push('--request')
    flags.push('--response')
  } else {
    flags.push(`--${argv.type}`)
  }
  const output = await $`STACK_VERSION=${argv['stack-version']} node ${path.join(tsValidationPath, 'index.js')} --api ${argv.api} ${flags}`

  cd(path.join(compilerPath, '..'))
  if (output.exitCode === 0) {
    spinner.stop()
    console.log(output.toString())
  } else {
    spinner.fail(output.toString())
  }

  await writeFile(
    path.join(compilerPath, '..', '.validation.json'),
    JSON.stringify(metadata),
    'utf8'
  )
}

async function readMetadata () {
  try {
    return JSON.parse(await readFile(path.join(compilerPath, '..', '.validation.json'), 'utf8'))
  } catch (err) {
    return {}
  }
}

function removeHeader (log) {
  return log.split('\n').slice(4).join('\n')
}

run().catch(err => {
  cd(path.join(compilerPath, '..'))
  spinner.fail(err.message)
  process.exit(1)
})