const Command = require("../structures/Command")
const { CommandWords, InitTypes, AnalyticsEvents } = require("../constants")
const { lookpath } = require("lookpath")
const {
  downloadFile,
  logErrorToFile,
  success,
  info,
  parseEnv,
} = require("../utils")
const { confirmation } = require("../questions")
const fs = require("fs")
const compose = require("docker-compose")
const makeEnv = require("./makeEnv")
const axios = require("axios")
const AnalyticsClient = require("../analytics/Client")

const BUDIBASE_SERVICES = ["app-service", "worker-service"]
const ERROR_FILE = "docker-error.log"
const FILE_URLS = [
  "https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
  "https://raw.githubusercontent.com/Budibase/budibase/master/hosting/envoy.yaml",
]
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"

const client = new AnalyticsClient()

async function downloadFiles() {
  const promises = []
  for (let url of FILE_URLS) {
    const fileName = url.split("/").slice(-1)[0]
    promises.push(downloadFile(url, `./${fileName}`))
  }
  await Promise.all(promises)
}

async function checkDockerConfigured() {
  const error =
    "docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/self-hosting/hosting-methods/docker-compose#installing-docker"
  const docker = await lookpath("docker")
  const compose = await lookpath("docker-compose")
  if (!docker || !compose) {
    throw error
  }
}

function checkInitComplete() {
  if (!fs.existsSync(makeEnv.filePath)) {
    throw "Please run the hosting --init command before any other hosting command."
  }
}

async function handleError(func) {
  try {
    await func()
  } catch (err) {
    if (err && err.err) {
      logErrorToFile(ERROR_FILE, err.err)
    }
    throw `Failed to start - logs written to file: ${ERROR_FILE}`
  }
}

async function init(type) {
  const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
  await checkDockerConfigured()
  if (!isQuick) {
    const shouldContinue = await confirmation(
      "This will create multiple files in current directory, should continue?"
    )
    if (!shouldContinue) {
      console.log("Stopping.")
      return
    }
  }
  client.capture({
    distinctId: "cli",
    event: AnalyticsEvents.SelfHostInit,
    properties: {
      type,
    },
  })
  await downloadFiles()
  const config = isQuick ? makeEnv.QUICK_CONFIG : {}
  if (type === InitTypes.DIGITAL_OCEAN) {
    try {
      const output = await axios.get(DO_USER_DATA_URL)
      const response = parseEnv(output.data)
      for (let [key, value] of Object.entries(makeEnv.ConfigMap)) {
        if (response[key]) {
          config[value] = response[key]
        }
      }
    } catch (err) {
      // don't need to handle error, just don't do anything
    }
  }
  await makeEnv.make(config)
}

async function start() {
  await checkDockerConfigured()
  checkInitComplete()
  console.log(
    info(
      "Starting services, this may take a moment - first time this may take a few minutes to download images."
    )
  )
  const port = makeEnv.get("MAIN_PORT")
  await handleError(async () => {
    // need to log as it makes it more clear
    await compose.upAll({ cwd: "./", log: true })
  })
  console.log(
    success(
      `Services started, please go to http://localhost:${port} for next steps.`
    )
  )
}

async function status() {
  await checkDockerConfigured()
  checkInitComplete()
  console.log(info("Budibase status"))
  await handleError(async () => {
    const response = await compose.ps()
    console.log(response.out)
  })
}

async function stop() {
  await checkDockerConfigured()
  checkInitComplete()
  console.log(info("Stopping services, this may take a moment."))
  await handleError(async () => {
    await compose.stop()
  })
  console.log(success("Services have been stopped successfully."))
}

async function update() {
  await checkDockerConfigured()
  checkInitComplete()
  if (
    await confirmation(
      "Do you wish to update you docker-compose.yaml and envoy.yaml?"
    )
  ) {
    await downloadFiles()
  }
  await handleError(async () => {
    const status = await compose.ps()
    const parts = status.out.split("\n")
    const isUp = parts[2] && parts[2].indexOf("Up") !== -1
    if (isUp) {
      console.log(info("Stopping services, this may take a moment."))
      await compose.stop()
    }
    console.log(info("Beginning update, this may take a few minutes."))
    await compose.pullMany(BUDIBASE_SERVICES, { log: true })
    if (isUp) {
      console.log(success("Update complete, restarting services..."))
      await start()
    }
  })
}

const command = new Command(`${CommandWords.HOSTING}`)
  .addHelp("Controls self hosting on the Budibase platform.")
  .addSubOption(
    "--init [type]",
    "Configure a self hosted platform in current directory, type can be unspecified or 'quick'.",
    init
  )
  .addSubOption(
    "--start",
    "Start the configured platform in current directory.",
    start
  )
  .addSubOption(
    "--status",
    "Check the status of currently running services.",
    status
  )
  .addSubOption(
    "--stop",
    "Stop the configured platform in the current directory.",
    stop
  )
  .addSubOption(
    "--update",
    "Update the Budibase images to the latest version.",
    update
  )

exports.command = command
