#!/usr/bin/env bash
set -uo pipefail;

####################################
# Ensure we can execute standalone #
####################################

function early_death() {
  echo "[FATAL] ${0}: ${1}" >&2;
  exit 1;
};

if [ -z "${TOFUENV_ROOT:-""}" ]; then
  # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
  readlink_f() {
    local target_file="${1}";
    local file_name;

    while [ "${target_file}" != "" ]; do
      cd "$(dirname "${target_file}")" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TOFUENV_ROOT";
      file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TOFUENV_ROOT";
      target_file="$(readlink "${file_name}")";
    done;

    echo "$(pwd -P)/${file_name}";
  };

  TOFUENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)";
  [ -n "${TOFUENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TOFUENV_ROOT";
else
  TOFUENV_ROOT="${TOFUENV_ROOT%/}";
fi;
export TOFUENV_ROOT;

if [ -n "${TOFUENV_HELPERS:-""}" ]; then
  log 'debug' 'TOFUENV_HELPERS is set, not sourcing helpers again';
else
  [ "${TOFUENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TOFUENV_ROOT}/lib/tofuenv-helpers.sh";
  if source "${TOFUENV_ROOT}/lib/tofuenv-helpers.sh"; then
    log 'debug' 'Helpers sourced successfully';
  else
    early_death "Failed to source helpers from ${TOFUENV_ROOT}/lib/tofuenv-helpers.sh";
  fi;
fi;

# Ensure libexec and bin are in $PATH
for dir in libexec bin; do
  case ":${PATH}:" in
    *:${TOFUENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TOFUENV_ROOT}/${dir}', not adding it again";;
    *)
      log 'debug' "\$PATH does not contain '${TOFUENV_ROOT}/${dir}', prepending and exporting it now";
      export PATH="${TOFUENV_ROOT}/${dir}:${PATH}";
      ;;
  esac;
done;

#####################
# Begin Script Body #
#####################

[ "${#}" -gt 1 ] && log 'error' 'usage: tofuenv install [<version>]';

declare requested="${1:-""}";

log debug "Resolving version with: tofuenv-resolve-version ${requested}";
declare resolved;
resolved="$(tofuenv-resolve-version ${requested})" || log 'error' "Failed to resolve ${requested} version";

declare version="${resolved%%\:*}";
declare regex="${resolved##*\:}";

[ -n "${version}" ] || log 'error' 'Version is not specified. This should not be possible as we default to latest';

log 'debug' "Processing install for version ${version}, using regex ${regex}";

if [[ ${version} =~ ${regex:-not} ]]; then
  log 'debug' "Version and regex matched";
else 
  log 'debug' "Version and regex not matched";
  if [ "${TOFUENV_SKIP_LIST_REMOTE:-0}" -eq 0 ]; then
    remote_version="$(tofuenv-list-remote | grep -e "${regex}" | head -n 1)";
    [ -n "${remote_version}" ] && version="${remote_version}" || log 'error' "No ${version} versions matching '${regex}' found in remote";
  fi;
fi;

dst_path="${TOFUENV_CONFIG_DIR}/versions/${version}";
if [ -f "${dst_path}/tofu" ]; then
  echo "OpenTofu v${version} is already installed";
  exit 0;
fi;

case "$(uname -s)" in
  Darwin*)
    kernel="darwin";
    ;;
  MINGW64*)
    kernel="windows";
    ;;
  MSYS*NT*)
    kernel="windows";
    ;;
  CYGWIN*NT*)
    kernel="windows";
    ;;
  FreeBSD*)
    kernel="freebsd";
    ;;
  *)
    kernel="linux";
    ;;
esac;

# Add support of ARM64 for Linux & Apple Silicon
case "$(uname -m)" in
  aarch64* | arm64*)
    case "${kernel}" in
      "linux")
          TOFUENV_ARCH="${TOFUENV_ARCH:-arm64}";
      ;;
      "darwin")
          TOFUENV_ARCH="${TOFUENV_ARCH:-arm64}";
      ;;
    esac;
    ;;
  *)
    TOFUENV_ARCH="${TOFUENV_ARCH:-amd64}";
    ;;
esac;

os="${kernel}_${TOFUENV_ARCH}"

keybase_bin="$(command -v keybase 2>/dev/null)";
shasum_bin="$(command -v shasum 2>/dev/null)";
sha256sum_bin="$(command -v sha256sum 2>/dev/null)";

TOFUENV_REMOTE="${TOFUENV_REMOTE:-https://github.com/opentofu/opentofu/releases}";
version_url="${TOFUENV_REMOTE}/download/v${version}";

tarball_name="tofu_${version}_${os}.zip";

shasums_name="tofu_${version}_SHA256SUMS";
shasums_sig="${shasums_name}.gpgsig";

log 'info' "Installing OpenTofu v${version}";

# Create a local temporary directory for downloads
tmpdir_arg="-t";

if mktemp --help 2>&1 | grep -- '--tmpdir' >/dev/null; then
  tmpdir_arg="--tmpdir";
fi;

download_tmp="$(mktemp -d ${tmpdir_arg} tofuenv_download.XXXXXX)" || log 'error' "Unable to create temporary download directory (mktemp -d ${tmpdir_arg} tofuenv_download.XXXXXX). Working Directory is: $(pwd)";

# Clean it up in case of error
trap "rm -rf ${download_tmp}" EXIT;

declare curl_progress="";
case "${TOFUENV_CURL_OUTPUT:-2}" in
  '2')
    log 'debug' 'Setting curl progress bar with "-#"';
    curl_progress="-#";
    ;;
  '1')
    log 'debug' 'Using default curl output';
    curl_progress="";
    ;;
  '0')
    log 'debug' 'Running curl silently with "-s"';
    curl_progress="-s";
    ;;
  *)
    log 'error' 'TOFUENV_CURL_OUTPUT specified, but not with a supported value ([0,1,2])';
    ;;
esac;

log 'info' "Downloading release tarball from ${version_url}/${tarball_name}";

status=$(curlw ${curl_progress} -w "%{http_code}" -f -L -o "${download_tmp}/${tarball_name}" "${version_url}/${tarball_name}");

case "${status}" in
    200) log 'debug' "'${requested:-$version}' version download successfully" ;;
    403) log 'error' "GitHub Rate limits exceeded" ;;
    404) log 'error' "No versions matching '${requested:-$version}' found in remote" ;;
    *)   log 'error' "Unknown error, status code = ${status}" ;;
esac;

log 'info' "Downloading SHA hash file from ${version_url}/${shasums_name}";
curlw -s -f -L -o "${download_tmp}/${shasums_name}" "${version_url}/${shasums_name}" || log 'error' 'SHA256 hashes download failed';

download_signature() {
  log 'info' "Downloading SHA hash signature file from ${version_url}/${shasums_sig}";
  curlw -s -f -L \
    -o "${download_tmp}/${shasums_sig}" \
    "${version_url}/${shasums_sig}" \
    && log 'debug' "SHA256SUMS signature file downloaded successfully to ${download_tmp}/${shasums_sig}" \
    || log 'error' 'SHA256SUMS signature download failed';
};

# If on MacOS with Homebrew, use GNU grep
# This allows keybase login detection to work on Mac,
# and is required to be able to detect tofu version
# from "required_version" setting in "*.tf" files
check_dependencies;

# Verify signature if verification mechanism (keybase, gpg, etc) is present
if [[ -f "${TOFUENV_CONFIG_DIR}/use-gnupg" ]]; then
  # GnuPG uses the user's keyring, and any web-of-trust or local signatures or
  # anything else they have setup.  This is the crazy-powerful mode which is
  # overly confusing to newcomers.  We don't support it without the user creating
  # the file use-gnupg, optionally with directives in it.
  gnupg_command="$(sed -E -n -e 's/^binary: *//p' <"${TOFUENV_CONFIG_DIR}/use-gnupg")";
  [[ -n "${gnupg_command}" ]] || gnupg_command=gpg;

  download_signature;
  # Deliberately unquoted command, in case caller has something fancier in "use-gnupg".
  # Also, don't use batch mode.  If someone specifies GnuPG, let them deal with any prompting.
  ${gnupg_command} \
    --verify "${download_tmp}/${shasums_sig}" \
    "${download_tmp}/${shasums_name}" \
    || log 'error' 'PGP signature rejected by GnuPG';

elif [[ -f "${TOFUENV_CONFIG_DIR}/use-gpgv" ]]; then
  # gpgv is a much simpler interface to verification, but does require that the
  # key have been downloaded and marked trusted.
  # We don't force the caller to trust the tofuenv repo's copy of their key, they
  # have to choose to make that trust decision.
  gpgv_command="$(sed -E -n -e 's/^binary: *//p' <"${TOFUENV_CONFIG_DIR}/use-gpgv")";
  trust_tofuenv="$(sed -E -n -e 's/^trust.?tofuenv: *//p' <"${TOFUENV_CONFIG_DIR}/use-gpgv")";
  [[ -n "${gpgv_command}" ]] || gpgv_command=gpgv;

  download_signature;
  if [[ "${trust_tofuenv}" == 'yes' ]]; then
    ${gpgv_command} \
      --keyring "${TOFUENV_ROOT}/share/opentofu.gpg" \
      "${download_tmp}/${shasums_sig}" \
      "${download_tmp}/${shasums_name}" \
      || log 'error' 'PGP signature rejected';

  else
    ${gpgv_command} \
      "${download_tmp}/${shasums_sig}" \
      "${download_tmp}/${shasums_name}" \
      || log 'error' 'PGP signature rejected';
  fi;
# TODO: uncomment when keybase GPG support is added
#elif [[ -n "${keybase_bin}" && -x "${keybase_bin}" ]]; then
#  grep -Eq '^Logged in:[[:space:]]*yes' <("${keybase_bin}" status);
#  keybase_logged_in="${?}";
#  grep -Fq hashicorp <("${keybase_bin}" list-following);
#  keybase_following_hc="${?}";
#
#  if [[ "${keybase_logged_in}" -ne 0 || "${keybase_following_hc}" -ne 0 ]]; then
#    log 'warn' 'Unable to verify OpenPGP signature unless logged into keybase and following hashicorp';
#  else
#    download_signature;
#    "${keybase_bin}" pgp verify \
#      -S hashicorp \
#      -d "${download_tmp}/${shasums_sig}" \
#      -i "${download_tmp}/${shasums_name}" \
#      && log 'debug' 'SHA256SUMS signature matched' \
#      || log 'error' 'SHA256SUMS signature does not match!';
#  fi;
else
  # Warning about this avoids an unwarranted sense of confidence in the SHA check
  log 'warn' "Not instructed to use Local GPG (${TOFUENV_CONFIG_DIR}/use-{gpgv,gnupg}), skipping GnuPG signature verification";
fi;

if [[ -n "${shasum_bin}" && -x "${shasum_bin}" ]]; then
  (
    cd "${download_tmp}";
    "${shasum_bin}" \
      -a 256 \
      -s \
      -c <(grep -F "${tarball_name}" "${shasums_name}")
  ) || log 'error' 'SHA256 hash does not match!';
elif [[ -n "${sha256sum_bin}" && -x "${sha256sum_bin}" ]]; then
  (
    cd "${download_tmp}";
    "${sha256sum_bin}" \
      -c <(grep -F "${tarball_name}" "${shasums_name}")
  ) || log 'error' 'SHA256 hash does not match!';
else
  # Lack of shasum deserves a proper warning
  log 'warn' 'No shasum tool available. Skipping SHA256 hash validation';
fi;

mkdir -p "${dst_path}" || log 'error' "Failed to make directory ${dst_path}";

declare unzip_output;
unzip_output="$(unzip -o "${download_tmp}/${tarball_name}" -d "${dst_path}")" || log 'error' 'Tarball unzip failed';
while IFS= read -r unzip_line; do
 log 'info' "${unzip_line}";
done < <(printf '%s\n' "${unzip_output}");

log 'info' "Installation of tofu v${version} successful. To make this your default version, run 'tofuenv use ${version}'";
