#!/bin/sh
# Install a formal open-compute `ocd` release into PATH.
#
# Authority: GitHub Releases for elliothux/open-compute (immutable assets).
# open-compute.dev may link humans to the same assets; this script downloads
# release.json, SHA256SUMS, and the target binary directly from GitHub Releases
# (or an injectable mirror base). It never creates config, data dirs, tokens,
# or OS services — use `ocd setup` after install.
#
# Usage (per-user under $HOME/.local):
#   curl -fsSL https://open-compute.dev/install.sh | sh
#
# System-wide alternative:
#   curl -fsSL https://open-compute.dev/install.sh | sudo sh
#
# Environment (optional):
#   OPEN_COMPUTE_RELEASE_TAG          exact tag (vX.Y.Z); default = latest stable
#   OPEN_COMPUTE_INSTALL_PREFIX       default $HOME/.local for users, /usr/local for root
#   OPEN_COMPUTE_RELEASE_DOWNLOAD_BASE  default
#     https://github.com/elliothux/open-compute/releases/download
#   OPEN_COMPUTE_GITHUB_API_BASE      default https://api.github.com
#   OPEN_COMPUTE_INSTALL_DEST         override exact binary destination path
#
# Dry review: set the injectable bases to a local file:// or https fixture
# directory that serves the same asset names as a GitHub Release tag folder.
set -eu

REPO="elliothux/open-compute"
DOWNLOAD_BASE="${OPEN_COMPUTE_RELEASE_DOWNLOAD_BASE:-https://github.com/${REPO}/releases/download}"
API_BASE="${OPEN_COMPUTE_GITHUB_API_BASE:-https://api.github.com}"
CALLER_UID=$(id -u)
host_os=$(uname -s)
if [ "${CALLER_UID}" -eq 0 ]; then
  case "${SUDO_UID:-}:${SUDO_GID:-}" in
    *[!0-9:]* | :* | *:) printf 'install.sh: sudo UID/GID are required\n' >&2; exit 1 ;;
  esac
  [ "${SUDO_UID}" -ne 0 ] \
    || { printf 'install.sh: system install requires sudo from a non-root user\n' >&2; exit 1; }
  OCD_ROOT=/var/lib/open-compute
else
  case "${host_os}" in
    Darwin) passwd_entry=$(id -P) ; user_home=$(printf '%s\n' "${passwd_entry}" | awk -F: '{print $9}') ;;
    Linux) command -v getent >/dev/null 2>&1 || { printf 'install.sh: getent is required\n' >&2; exit 1; } ; passwd_entry=$(getent passwd "${CALLER_UID}") ; user_home=$(printf '%s\n' "${passwd_entry}" | awk -F: '{print $6}') ;;
    *) printf 'install.sh: unsupported operating system\n' >&2; exit 1 ;;
  esac
  case "${user_home}" in
    /*) ;;
    *) printf 'install.sh: running UID has no absolute home directory\n' >&2; exit 1 ;;
  esac
  OCD_ROOT="${user_home}/.open-compute"
fi
if [ -n "${OPEN_COMPUTE_INSTALL_PREFIX:-}" ]; then
  PREFIX=${OPEN_COMPUTE_INSTALL_PREFIX}
elif [ "${CALLER_UID}" -eq 0 ]; then
  PREFIX=/usr/local
else
  PREFIX="${user_home}/.local"
fi
DEST="${OPEN_COMPUTE_INSTALL_DEST:-${PREFIX}/bin/ocd}"
RECEIPT="${OCD_ROOT}/install-receipt.json"

umask 077

die() {
  printf 'install.sh: %s\n' "$*" >&2
  exit 1
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

path_permission_error() {
  label=$1
  directory=$2
  printf 'install.sh: cannot write %s directory: %s\n' "${label}" "${directory}" >&2
  printf 'install.sh: per-user install: sh install.sh\n' >&2
  printf 'install.sh: system-wide install: sudo sh install.sh\n' >&2
  exit 1
}

configure_user_path() {
  [ "${CALLER_UID}" -ne 0 ] || return 0
  [ "${DEST}" = "${user_home}/.local/bin/ocd" ] || return 0
  case ":${PATH}:" in
    *:"${user_home}/.local/bin":*) return 0 ;;
  esac

  export_line='export PATH="$HOME/.local/bin:$PATH"'
  shell_name=${SHELL##*/}
  case "${shell_name}" in
    zsh) shell_rc="${user_home}/.zshrc" ;;
    bash) shell_rc="${user_home}/.bashrc" ;;
    sh) shell_rc="${user_home}/.profile" ;;
    *)
      printf 'install.sh: add ocd to PATH: %s\n' "${export_line}" >&2
      return 0
      ;;
  esac
  if [ -L "${shell_rc}" ] || { [ -e "${shell_rc}" ] && [ ! -f "${shell_rc}" ]; }; then
    printf 'install.sh: add ocd to PATH: %s\n' "${export_line}" >&2
    return 0
  fi
  if [ -f "${shell_rc}" ]; then
    if grep -F '$HOME/.local/bin' "${shell_rc}" >/dev/null 2>&1 \
      || grep -F "${user_home}/.local/bin" "${shell_rc}" >/dev/null 2>&1; then
      return 0
    fi
  fi
  if printf '\n# open-compute\n%s\n' "${export_line}" >>"${shell_rc}" 2>/dev/null; then
    printf 'install.sh: added %s to PATH in %s; restart your shell\n' "${user_home}/.local/bin" "${shell_rc}" >&2
  else
    printf 'install.sh: add ocd to PATH: %s\n' "${export_line}" >&2
  fi
}

preflight_writable_directory() {
  label=$1
  directory=$2
  mkdir -p "${directory}" 2>/dev/null || path_permission_error "${label}" "${directory}"
  [ -d "${directory}" ] || die "${label} directory is not a directory: ${directory}"
  probe=$(mktemp "${directory}/.open-compute-install-write.XXXXXX" 2>/dev/null) \
    || path_permission_error "${label}" "${directory}"
  rm -f "${probe}" || die "failed to remove install preflight file from ${directory}"
}

preflight_install_paths() {
  bin_dir=$(dirname "${DEST}")
  preflight_writable_directory "binary" "${bin_dir}"
  if [ ! -e "${OCD_ROOT}" ]; then
    mkdir -p "${OCD_ROOT}" 2>/dev/null || path_permission_error "receipt" "${OCD_ROOT}"
    if [ "${CALLER_UID}" -eq 0 ]; then
      chown "${SUDO_UID}:${SUDO_GID}" "${OCD_ROOT}" || die "failed to assign system OCD root owner"
    fi
  fi
  [ ! -L "${OCD_ROOT}" ] && [ -d "${OCD_ROOT}" ] || die "OCD root must be a real directory"
  if [ "${CALLER_UID}" -eq 0 ]; then
    case "${host_os}" in
      Darwin) root_uid=$(stat -f %u "${OCD_ROOT}") ;;
      Linux) root_uid=$(stat -c %u "${OCD_ROOT}") ;;
    esac
    [ "${root_uid}" = "${SUDO_UID}" ] || die "system OCD root belongs to a different user"
  else
    [ -O "${OCD_ROOT}" ] || die "OCD root must be owned by the running user"
  fi
  case "${host_os}" in
    Darwin) root_mode=$(stat -f %Lp "${OCD_ROOT}") ;;
    Linux) root_mode=$(stat -c %a "${OCD_ROOT}") ;;
  esac
  [ "${root_mode}" = 700 ] || die "OCD root must have mode 0700"
  preflight_writable_directory "receipt" "${OCD_ROOT}"
  if [ ! -e "${OCD_ROOT}/tmp" ]; then
    mkdir "${OCD_ROOT}/tmp" 2>/dev/null || path_permission_error "temp" "${OCD_ROOT}/tmp"
    if [ "${CALLER_UID}" -eq 0 ]; then
      chown "${SUDO_UID}:${SUDO_GID}" "${OCD_ROOT}/tmp" || die "failed to assign OCD temp owner"
    fi
  fi
  [ ! -L "${OCD_ROOT}/tmp" ] && [ -d "${OCD_ROOT}/tmp" ] || die "OCD temp must be a real directory"
  if [ "${CALLER_UID}" -eq 0 ]; then
    case "${host_os}" in
      Darwin) temp_uid=$(stat -f %u "${OCD_ROOT}/tmp") ;;
      Linux) temp_uid=$(stat -c %u "${OCD_ROOT}/tmp") ;;
    esac
    [ "${temp_uid}" = "${SUDO_UID}" ] || die "OCD temp belongs to a different user"
  else
    [ -O "${OCD_ROOT}/tmp" ] || die "OCD temp must be owned by the running user"
  fi
  preflight_writable_directory "temp" "${OCD_ROOT}/tmp"
  case "${host_os}" in
    Darwin) temp_mode=$(stat -f %Lp "${OCD_ROOT}/tmp") ;;
    Linux) temp_mode=$(stat -c %a "${OCD_ROOT}/tmp") ;;
  esac
  [ "${temp_mode}" = 700 ] || die "OCD temp must have mode 0700"
}

detect_target() {
  os=$(uname -s | tr '[:upper:]' '[:lower:]')
  arch=$(uname -m)
  case "${os}" in
    darwin)
      case "${arch}" in
        arm64 | aarch64) printf 'darwin-arm64\n' ;;
        *) die "unsupported macOS CPU '${arch}' (official releases: darwin-arm64 only)" ;;
      esac
      ;;
    linux)
      case "${arch}" in
        x86_64 | amd64) printf 'linux-x64\n' ;;
        aarch64 | arm64) printf 'linux-arm64\n' ;;
        *) die "unsupported Linux CPU '${arch}' (official releases: linux-x64, linux-arm64)" ;;
      esac
      ;;
    *)
      die "unsupported OS '${os}' (official releases: macOS arm64, Linux x64/arm64)"
      ;;
  esac
}

resolve_tag() {
  if [ -n "${OPEN_COMPUTE_RELEASE_TAG:-}" ]; then
    printf '%s\n' "${OPEN_COMPUTE_RELEASE_TAG}"
    return 0
  fi
  # Latest stable: GitHub Releases API; reject prerelease drafts via API fields.
  need_cmd curl
  body=$(curl -fsSL --max-time 30 \
    -H "Accept: application/vnd.github+json" \
    -H "User-Agent: open-compute-install" \
    "${API_BASE}/repos/${REPO}/releases/latest") || die "failed to resolve latest release from GitHub API"
  tag=$(printf '%s' "${body}" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
  [ -n "${tag}" ] || die "GitHub latest release response missing tag_name"
  printf '%s\n' "${tag}"
}

is_stable_tag() {
  printf '%s' "$1" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
}

sha256_file() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    die "need sha256sum or shasum"
  fi
}

lookup_sum() {
  # SHA256SUMS lines: "<hex>  <filename>"
  sums_file=$1
  name=$2
  awk -v name="${name}" '$2 == name { print $1; found=1 } END { exit !found }' "${sums_file}"
}

refuse_foreign_destination() {
  [ ! -L "${RECEIPT}" ] || die "refusing symlink install receipt"
  if [ ! -e "${DEST}" ] && [ ! -L "${DEST}" ]; then
    return 0
  fi
  if [ -L "${DEST}" ]; then
    die "refusing to overwrite symlink at ${DEST}"
  fi
  if [ -f "${RECEIPT}" ]; then
    method=$(sed -n 's/.*"method"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "${RECEIPT}" | head -n 1)
    case "${method}" in
      install.sh | manual) ;;
      *)
        die "refusing to overwrite package-manager-owned install (receipt method=${method:-unknown})"
        ;;
    esac
    receipt_bin=$(sed -n 's/.*"binary_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "${RECEIPT}" | head -n 1)
    if [ -n "${receipt_bin}" ] && [ "${receipt_bin}" != "${DEST}" ]; then
      die "existing receipt owns a different binary path (${receipt_bin})"
    fi
    return 0
  fi
  case "${DEST}" in
    */Cellar/* | */opt/homebrew/* | */linuxbrew/* | /usr/bin/ocd)
      die "refusing to overwrite package-manager path ${DEST} without an open-compute install receipt"
      ;;
  esac
  die "refusing to overwrite existing ${DEST} without a matching install receipt; remove it or set OPEN_COMPUTE_INSTALL_DEST"
}

main() {
  need_cmd curl
  need_cmd mktemp
  need_cmd mkdir
  need_cmd mv
  need_cmd chmod
  need_cmd awk
  need_cmd sed
  need_cmd grep
  need_cmd head
  need_cmd dirname
  need_cmd chown
  need_cmd rm
  need_cmd uname
  need_cmd tr

  target=$(detect_target)
  refuse_foreign_destination
  preflight_install_paths
  tag=$(resolve_tag)
  is_stable_tag "${tag}" || die "release tag must be stable SemVer vX.Y.Z (got ${tag})"
  version=${tag#v}
  asset="ocd-${tag}-${target}"
  base="${DOWNLOAD_BASE}/${tag}"

  work=$(mktemp -d "${OCD_ROOT}/tmp/install.XXXXXX")
  trap 'rm -rf "${work}"' EXIT INT TERM

  printf 'install.sh: fetching %s (%s)\n' "${tag}" "${target}" >&2
  curl -fsSL --max-time 60 -o "${work}/release.json" "${base}/release.json" \
    || die "failed to download release.json"
  curl -fsSL --max-time 60 -o "${work}/SHA256SUMS" "${base}/SHA256SUMS" \
    || die "failed to download SHA256SUMS"
  curl -fsSL --max-time 600 -o "${work}/${asset}" "${base}/${asset}" \
    || die "failed to download ${asset}"

  expected_manifest=$(lookup_sum "${work}/SHA256SUMS" "release.json") \
    || die "SHA256SUMS missing release.json"
  actual_manifest=$(sha256_file "${work}/release.json")
  [ "${expected_manifest}" = "${actual_manifest}" ] || die "release.json checksum mismatch"

  expected_bin=$(lookup_sum "${work}/SHA256SUMS" "${asset}") \
    || die "SHA256SUMS missing ${asset}"
  actual_bin=$(sha256_file "${work}/${asset}")
  [ "${expected_bin}" = "${actual_bin}" ] || die "binary checksum mismatch"

  # release.json identity checks (fail closed on mismatch).
  manifest_version=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "${work}/release.json" | head -n 1)
  manifest_tag=$(sed -n 's/.*"tag"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "${work}/release.json" | head -n 1)
  [ "${manifest_version}" = "${version}" ] || die "release.json version mismatch"
  [ "${manifest_tag}" = "${tag}" ] || die "release.json tag mismatch"
  printf '%s' "$(cat "${work}/release.json")" | grep -q "\"target\"[[:space:]]*:[[:space:]]*\"${target}\"" \
    || die "release.json does not list target ${target}"

  chmod 755 "${work}/${asset}"
  got_version=$("${work}/${asset}" --version 2>/dev/null | head -n 1) \
    || die "staged ocd --version failed"
  printf '%s' "${got_version}" | grep -q "${version}" \
    || die "staged binary version output does not contain ${version}: ${got_version}"

  staged="${DEST}.new.$$"
  mv "${work}/${asset}" "${staged}"
  chmod 755 "${staged}"
  # Best-effort directory sync when available.
  if command -v sync >/dev/null 2>&1; then
    sync "${staged}" 2>/dev/null || sync || true
  fi
  mv -f "${staged}" "${DEST}"
  if command -v sync >/dev/null 2>&1; then
    sync "${bin_dir}" 2>/dev/null || sync || true
  fi

  now_ms=$(date +%s)000 2>/dev/null || now_ms=0
  cat >"${RECEIPT}.tmp.$$" <<EOF
{
  "schema_version": 1,
  "version": "${version}",
  "sha256": "${actual_bin}",
  "target": "${target}",
  "binary_path": "${DEST}",
  "method": "install.sh",
  "source": "${base}/${asset}",
  "installed_at_ms": ${now_ms}
}
EOF
  mv -f "${RECEIPT}.tmp.$$" "${RECEIPT}"
  chmod 600 "${RECEIPT}"
  if [ "${CALLER_UID}" -eq 0 ]; then
    chown "${SUDO_UID}:${SUDO_GID}" "${RECEIPT}" || die "failed to assign install receipt owner"
  fi

  printf 'install.sh: installed %s -> %s\n' "${version}" "${DEST}" >&2
  printf 'install.sh: receipt %s\n' "${RECEIPT}" >&2
  configure_user_path
  printf 'install.sh: next: ocd setup --yes   # creates config/data/service; not done by install\n' >&2
}

main "$@"
