#!/usr/bin/env bash
set -euo pipefail

readonly BASE_URL="${BUILDIN_CLI_RELEASE_BASE_URL:-https://cdn.buildin.ai/buildin-cli}"
readonly REQUESTED_VERSION="${BUILDIN_VERSION:-latest}"

if [[ "${REQUESTED_VERSION}" != "latest" && ! "${REQUESTED_VERSION}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
  printf 'error: invalid version "%s" -- expected "latest" or a semver like "1.2.3" or "v1.2.3"\n' "${REQUESTED_VERSION}" >&2
  exit 1
fi

info() {
  printf '==> %s\n' "$*" >&2
}

fail() {
  printf 'error: %s\n' "$*" >&2
  exit 1
}

require_command() {
  local command_name="$1"
  command -v "$command_name" >/dev/null 2>&1 || fail "missing required command: ${command_name}"
}

normalize_path_component() {
  local path="$1"
  while [[ "${path}" != "/" && "${path}" == */ ]]; do
    path="${path%/}"
  done
  printf '%s\n' "${path}"
}

path_contains_dir() {
  local dir
  dir="$(normalize_path_component "$1")"
  local path_entry
  local IFS=:
  for path_entry in ${PATH:-}; do
    if [[ "$(normalize_path_component "${path_entry}")" == "${dir}" ]]; then
      return 0
    fi
  done
  return 1
}

is_absolute_path() {
  local path="$1"
  [[ -n "${path}" && "${path}" == /* ]]
}

can_install_to_dir() {
  local dir="$1"
  is_absolute_path "${dir}" || return 1
  [[ -d "${dir}" && -w "${dir}" && -x "${dir}" ]]
}

prepare_explicit_install_dir() {
  local dir="$1"
  is_absolute_path "${dir}" || return 1
  if [[ ! -d "${dir}" ]]; then
    mkdir -p "${dir}" 2>/dev/null || return 1
  fi
  [[ -w "${dir}" && -x "${dir}" ]]
}

INSTALL_DIR=""
INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="false"

use_install_dir() {
  INSTALL_DIR="$1"
  if ! path_contains_dir "${INSTALL_DIR}"; then
    INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="true"
  fi
}

create_home_local_bin_or_fail() {
  if [[ -z "${HOME:-}" ]]; then
    fail "could not find a writable install directory. Set BUILDIN_INSTALL_DIR to a directory you can write to and rerun the installer."
  fi

  local home_local_bin="${HOME}/.local/bin"
  if ! is_absolute_path "${home_local_bin}"; then
    fail "could not find a writable install directory. Set BUILDIN_INSTALL_DIR to a directory you can write to and rerun the installer."
  fi

  if [[ -e "${home_local_bin}" && ! -d "${home_local_bin}" ]]; then
    fail "${home_local_bin} exists but is not a directory. Set BUILDIN_INSTALL_DIR to a directory you can write to and rerun the installer."
  fi

  if mkdir -p "${home_local_bin}" 2>/dev/null; then
    use_install_dir "${home_local_bin}"
    INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="true"
    return
  fi

  fail "could not create ${home_local_bin}. Set BUILDIN_INSTALL_DIR to a directory you can write to and rerun the installer."
}

choose_install_dir() {
  if [[ -n "${BUILDIN_INSTALL_DIR:-}" ]]; then
    if ! prepare_explicit_install_dir "${BUILDIN_INSTALL_DIR}"; then
      fail "BUILDIN_INSTALL_DIR must be an absolute directory you can create and write to: ${BUILDIN_INSTALL_DIR}"
    fi
    use_install_dir "${BUILDIN_INSTALL_DIR}"
    return
  fi

  if [[ -n "${HOME:-}" ]]; then
    local home_local_bin="${HOME}/.local/bin"
    local home_bin="${HOME}/bin"

    if can_install_to_dir "${home_local_bin}" && path_contains_dir "${home_local_bin}"; then
      use_install_dir "${home_local_bin}"
      return
    fi

    if can_install_to_dir "${home_bin}" && path_contains_dir "${home_bin}"; then
      use_install_dir "${home_bin}"
      return
    fi
  fi

  if can_install_to_dir "/usr/local/bin" && path_contains_dir "/usr/local/bin"; then
    use_install_dir "/usr/local/bin"
    return
  fi

  if [[ -n "${HOME:-}" ]]; then
    local home_local_bin="${HOME}/.local/bin"
    local home_bin="${HOME}/bin"

    if can_install_to_dir "${home_local_bin}"; then
      use_install_dir "${home_local_bin}"
      return
    fi

    if can_install_to_dir "${home_bin}"; then
      use_install_dir "${home_bin}"
      return
    fi
  fi

  create_home_local_bin_or_fail
}

detect_downloader() {
  if command -v curl >/dev/null 2>&1; then
    DOWNLOADER="curl"
  elif command -v wget >/dev/null 2>&1; then
    DOWNLOADER="wget"
  else
    fail "either curl or wget is required but neither is installed"
  fi
}

download() {
  local url="$1"
  local output="$2"

  if [[ "${DOWNLOADER}" == "curl" ]]; then
    curl --retry 3 --retry-delay 2 --retry-connrefused -fsSL -o "${output}" "${url}"
  else
    wget -q --tries=3 --waitretry=2 -O "${output}" "${url}"
  fi
}

detect_platform() {
  local os
  local arch

  os="$(uname -s)"
  arch="$(uname -m)"

  if [[ "${os}" == "Darwin" && "${arch}" == "x86_64" ]]; then
    if [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" == "1" ]]; then
      arch="arm64"
    fi
  fi

  case "${os}" in
    Darwin) os="darwin" ;;
    Linux) os="linux" ;;
    MINGW* | MSYS* | CYGWIN*) fail "this installer does not support Windows" ;;
    *) fail "unsupported operating system: ${os}" ;;
  esac

  case "${arch}" in
    x86_64 | amd64) arch="x64" ;;
    arm64 | aarch64) arch="arm64" ;;
    *) fail "unsupported architecture: ${arch}" ;;
  esac

  printf '%s-%s\n' "${os}" "${arch}"
}

normalize_version() {
  local version="$1"
  if [[ "${version}" == v* ]]; then
    printf '%s\n' "${version}"
  else
    printf 'v%s\n' "${version}"
  fi
}

resolve_version() {
  local version_file="$1"
  if [[ "${REQUESTED_VERSION}" == "latest" ]]; then
    download "${BASE_URL}/latest/version" "${version_file}"
    tr -d '[:space:]' < "${version_file}"
  else
    normalize_version "${REQUESTED_VERSION}"
  fi
}

verify_archive() {
  local archive_dir="$1"
  local archive_name="$2"
  local checksum_name="$3"

  if command -v shasum >/dev/null 2>&1; then
    (cd "${archive_dir}" && grep "  ${archive_name}$" "${checksum_name}" | shasum -a 256 -c - >/dev/null) || fail "checksum verification failed"
    return
  fi

  if command -v sha256sum >/dev/null 2>&1; then
    (cd "${archive_dir}" && grep "  ${archive_name}$" "${checksum_name}" | sha256sum -c - >/dev/null) || fail "checksum verification failed"
    return
  fi

  fail "no checksum tool found; install shasum or sha256sum"
}

install_binary() {
  local binary_path="$1"
  local destination_path="${INSTALL_DIR}/buildin"

  if mkdir -p "${INSTALL_DIR}" 2>/dev/null && install -m 0755 "${binary_path}" "${destination_path}" 2>/dev/null; then
    return
  fi

  cat >&2 <<EOF
error: could not install Buildin CLI to ${destination_path}

The installer could not create or write to ${INSTALL_DIR}. Choose a directory
you can write to and rerun the installer with BUILDIN_INSTALL_DIR:

  curl -fsSL "${BASE_URL}/install" | BUILDIN_INSTALL_DIR="\$HOME/.local/bin" bash

You can also manually download the binary archive from:

  ${ARCHIVE_URL}
EOF
  exit 1
}

detect_downloader
require_command tar
require_command uname
require_command mktemp
require_command install

PLATFORM="$(detect_platform)"
readonly PLATFORM

choose_install_dir
readonly INSTALL_DIR
readonly INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS

TMP_DIR="$(mktemp -d)"
readonly TMP_DIR
trap 'rm -rf "${TMP_DIR}"' EXIT

VERSION="$(resolve_version "${TMP_DIR}/version")" || true
readonly VERSION
[[ -n "${VERSION}" ]] || fail "could not resolve version from ${BASE_URL}/latest/version"

readonly ARTIFACT="buildin-${PLATFORM}.tar.gz"
readonly RELEASE_BASE="${BASE_URL}/releases/${VERSION}"
readonly ARCHIVE_URL="${RELEASE_BASE}/${ARTIFACT}"
readonly CHECKSUM_URL="${RELEASE_BASE}/SHA256SUMS"
readonly ARCHIVE_PATH="${TMP_DIR}/${ARTIFACT}"
readonly CHECKSUM_PATH="${TMP_DIR}/SHA256SUMS"

info "Downloading Buildin CLI ${VERSION} for ${PLATFORM}"
if ! download "${ARCHIVE_URL}" "${ARCHIVE_PATH}"; then
  rm -f "${ARCHIVE_PATH}"
  fail "failed to download ${ARCHIVE_URL}"
fi

if ! download "${CHECKSUM_URL}" "${CHECKSUM_PATH}"; then
  rm -f "${CHECKSUM_PATH}"
  fail "failed to download checksum file from ${CHECKSUM_URL}"
fi

verify_archive "${TMP_DIR}" "${ARTIFACT}" "$(basename "${CHECKSUM_PATH}")"

tar -xzf "${ARCHIVE_PATH}" -C "${TMP_DIR}"
[[ -x "${TMP_DIR}/buildin" ]] || fail "downloaded archive did not contain an executable buildin binary"

install_binary "${TMP_DIR}/buildin"

printf '\n  Buildin CLI\n\n' >&2
printf '  Installed buildin %s to %s/buildin\n\n' "${VERSION}" "${INSTALL_DIR}" >&2

if [[ "${INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS}" == "true" ]]; then
  cat >&2 <<EOF
  Add buildin to your PATH:

    export PATH="${INSTALL_DIR}:\$PATH"

  For zsh, add that line to ~/.zshrc.
  For bash, add that line to ~/.bashrc or ~/.bash_profile.

EOF
fi

cat >&2 <<'NEXT'
  Get started:

    buildin login
    buildin help

NEXT
