#!/bin/sh
# ontoref installer — fetch a per-platform OCI bundle and install the CLI + daemon.
#
# Generated by `ore workflow generate` from install/install.sh.tmpl. The registry
# and version values below come from the distribution catalog in
# .ontoref/ontology/workflow.ncl — do not edit this file; edit the .tmpl and
# regenerate.
#
# Usage:
#   curl -fsSL @@INSTALLER_URL@@ | sh
#   curl -fsSL @@INSTALLER_URL@@ | sh -s -- --prefix ~/.local --version 0.1.6
#   sh install.sh --uninstall [--purge]
#
# The daemon is an OPTIONAL accelerator (ADR-029). This installs a CLI + data
# layer that work without it; run the daemon via the container image if wanted:
#   docker run --rm -p 7891:7891 @@REGISTRY@@/@@IMAGE_REPO@@:@@DEFAULT_VERSION@@
set -eu

REGISTRY="@@REGISTRY@@"
BUNDLE_REPO="@@BUNDLE_REPO@@"
IMAGE_REPO="@@IMAGE_REPO@@"
DEFAULT_VERSION="@@DEFAULT_VERSION@@"

PREFIX="${HOME}/.local"
DATA_DIR="${HOME}/.local/share/ontoref"
CONFIG_DIR="${HOME}/.config/ontoref"
VERSION="${DEFAULT_VERSION}"
DO_UNINSTALL=0
DO_PURGE=0

log()  { printf '  %s\n' "$*"; }
ok()   { printf '\033[32m✓\033[0m %s\n' "$*"; }
warn() { printf '\033[33mwarn\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[31merror\033[0m %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<EOF
ontoref installer

  --prefix DIR     install binaries under DIR/bin   (default: ${HOME}/.local)
  --data-dir DIR   install the data layer in DIR     (default: ${HOME}/.local/share/ontoref)
  --version VER    bundle version tag                (default: ${DEFAULT_VERSION})
  --uninstall      remove installed binaries + wrappers (keeps config/data)
  --purge          with --uninstall, also remove data + config
  -h, --help       show this help
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --prefix)    PREFIX="$2"; shift 2 ;;
    --data-dir)  DATA_DIR="$2"; shift 2 ;;
    --version)   VERSION="$2"; shift 2 ;;
    --uninstall) DO_UNINSTALL=1; shift ;;
    --purge)     DO_PURGE=1; shift ;;
    -h|--help)   usage; exit 0 ;;
    *)           die "unknown argument: $1 (try --help)" ;;
  esac
done

BIN_DIR="${PREFIX}/bin"

# ── Uninstall ─────────────────────────────────────────────────────────────────
if [ "$DO_UNINSTALL" -eq 1 ]; then
  for b in ontoref ontoref-daemon ontoref-daemon.bin ontoref-tier nickel; do
    [ -e "${BIN_DIR}/${b}" ] && rm -f "${BIN_DIR}/${b}" && log "removed ${BIN_DIR}/${b}"
  done
  if [ "$DO_PURGE" -eq 1 ]; then
    rm -rf "$DATA_DIR" && log "removed ${DATA_DIR}"
    rm -rf "$CONFIG_DIR" && log "removed ${CONFIG_DIR}"
  else
    log "kept data (${DATA_DIR}) and config (${CONFIG_DIR}) — use --purge to remove"
  fi
  ok "uninstalled"
  exit 0
fi

# ── Platform detection ────────────────────────────────────────────────────────
os="$(uname -s)"
case "$os" in
  Linux) ;;
  Darwin)
    die "no macOS bundle is published (Linux only). Options:
    • build from source:  cargo build --release -p ontoref-daemon && nu install/install.nu
    • run the daemon in a container:
        docker run --rm -p 7891:7891 ${REGISTRY}/${IMAGE_REPO}:${DEFAULT_VERSION}" ;;
  *) die "unsupported OS: ${os} (Linux only)" ;;
esac

machine="$(uname -m)"
case "$machine" in
  x86_64|amd64)  arch="amd64" ;;
  aarch64|arm64) arch="arm64" ;;
  *) die "unsupported architecture: ${machine}" ;;
esac

REF="${REGISTRY}/${BUNDLE_REPO}:${VERSION}-linux-${arch}"
TARBALL_NAME="ontoref-linux-${arch}.tar.gz"
log "platform: linux/${arch}   ref: ${REF}"

need() { command -v "$1" >/dev/null 2>&1; }
need tar  || die "tar is required"
need curl || die "curl is required"

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT INT TERM

# ── Fetch the bundle: prefer oras, fall back to the OCI Distribution API ──────
fetch_with_oras() {
  log "fetching via oras"
  ( cd "$WORK" && oras pull "$REF" ) >/dev/null 2>&1
}

# Minimal anonymous-pull client over the OCI Distribution API (curl + grep/sed).
# Handles the optional Bearer-token dance for public namespaces.
registry_token() {
  _repo="$1"
  _chal="$(curl -fsSL -o /dev/null -D - "https://${REGISTRY}/v2/" 2>/dev/null \
            | tr -d '\r' | grep -i '^www-authenticate:' || true)"
  [ -z "$_chal" ] && { echo ""; return 0; }
  _realm="$(printf '%s' "$_chal" | sed -n 's/.*realm="\([^"]*\)".*/\1/p')"
  _service="$(printf '%s' "$_chal" | sed -n 's/.*service="\([^"]*\)".*/\1/p')"
  [ -z "$_realm" ] && { echo ""; return 0; }
  curl -fsSL "${_realm}?service=${_service}&scope=repository:${_repo}:pull" 2>/dev/null \
    | grep -o '"token":"[^"]*"' | head -1 | sed 's/"token":"\([^"]*\)"/\1/'
}

fetch_with_curl() {
  log "oras not found — fetching via OCI Distribution API (curl)"
  _tag="${VERSION}-linux-${arch}"
  _tok="$(registry_token "$BUNDLE_REPO")"
  _auth=""
  [ -n "$_tok" ] && _auth="Authorization: Bearer ${_tok}"

  _man="${WORK}/manifest.json"
  curl -fsSL ${_auth:+-H "$_auth"} \
    -H "Accept: application/vnd.oci.image.manifest.v1+json" \
    -H "Accept: application/vnd.oci.artifact.manifest.v1+json" \
    "https://${REGISTRY}/v2/${BUNDLE_REPO}/manifests/${_tag}" -o "$_man" \
    || die "manifest fetch failed for ${REF}"

  # Pull each layer blob, naming it by its title annotation when present.
  # Layers appear as {"mediaType":..,"digest":"sha256:..","annotations":{"org.opencontainers.image.title":"name"}}
  printf '%s' "$(cat "$_man")" | tr '}' '}\n' | grep '"digest"' | while IFS= read -r line; do
    _dig="$(printf '%s' "$line" | grep -o 'sha256:[0-9a-f]\{64\}' | head -1)"
    [ -z "$_dig" ] && continue
    _title="$(printf '%s' "$line" | sed -n 's/.*"org.opencontainers.image.title":"\([^"]*\)".*/\1/p')"
    [ -z "$_title" ] && _title="$_dig"
    curl -fsSL ${_auth:+-H "$_auth"} \
      "https://${REGISTRY}/v2/${BUNDLE_REPO}/blobs/${_dig}" -o "${WORK}/${_title}" \
      || die "blob fetch failed: ${_dig}"
  done
}

if need oras; then
  fetch_with_oras
else
  fetch_with_curl
fi

TARBALL="${WORK}/${TARBALL_NAME}"
[ -f "$TARBALL" ] || die "bundle ${TARBALL_NAME} not present after fetch"

# ── Verify integrity ──────────────────────────────────────────────────────────
if [ -f "${TARBALL}.sha256" ]; then
  expected="$(awk '{print $1}' "${TARBALL}.sha256")"
  if need sha256sum; then
    actual="$(sha256sum "$TARBALL" | awk '{print $1}')"
  elif need shasum; then
    actual="$(shasum -a 256 "$TARBALL" | awk '{print $1}')"
  else
    actual=""
    warn "no sha256 tool — skipping checksum verification"
  fi
  if [ -n "$actual" ]; then
    [ "$actual" = "$expected" ] || die "checksum mismatch: expected ${expected}, got ${actual}"
    ok "checksum verified"
  fi
else
  warn "no .sha256 sidecar — skipping checksum verification"
fi

# Optional signature verification (best-effort; needs the public key out of band).
if need cosign && [ -f "${WORK}/cosign.pub" ]; then
  cosign verify-blob --key "${WORK}/cosign.pub" --signature "${TARBALL}.sig" "$TARBALL" \
    >/dev/null 2>&1 && ok "cosign signature verified" || warn "cosign verification failed (continuing)"
fi

# ── Extract ───────────────────────────────────────────────────────────────────
STAGE="${WORK}/stage"
mkdir -p "$STAGE"
tar -xzf "$TARBALL" -C "$STAGE"
[ -f "${STAGE}/bin/ontoref-daemon.bin" ] || die "bundle missing bin/ontoref-daemon.bin"

# ── Upgrade-safe: stop a running daemon before replacing its binary ───────────
if need pkill; then
  pkill -x ontoref-daemon.bin 2>/dev/null || true
  pkill -x ontoref-daemon     2>/dev/null || true
fi

# ── Place binaries + wrappers ─────────────────────────────────────────────────
mkdir -p "$BIN_DIR"
install -m 0755 "${STAGE}/bin/ontoref-daemon.bin"  "${BIN_DIR}/ontoref-daemon.bin"
install -m 0755 "${STAGE}/bin/ontoref-daemon-boot" "${BIN_DIR}/ontoref-daemon"
install -m 0755 "${STAGE}/bin/nickel"              "${BIN_DIR}/nickel"
[ -f "${STAGE}/bin/ontoref-tier" ] && install -m 0755 "${STAGE}/bin/ontoref-tier" "${BIN_DIR}/ontoref-tier"

# Bake the data dir as ONTOREF_ROOT so the wrapper is self-contained (mirrors
# install.nu §3): replace the placeholder default with the resolved DATA_DIR.
sed "s|ONTOREF_ROOT=\"\${ONTOREF_ROOT:-ontoref}\"|ONTOREF_ROOT=\"\${ONTOREF_ROOT:-${DATA_DIR}}\"|" \
  "${STAGE}/bin/ontoref-global" > "${BIN_DIR}/ontoref"
chmod 0755 "${BIN_DIR}/ontoref"
ok "binaries → ${BIN_DIR}"

# ── Place the data layer ──────────────────────────────────────────────────────
mkdir -p "$DATA_DIR"
# cp -a preserves the tree; we replace reflection/ontology/etc wholesale so an
# upgrade never leaves stale files behind in those subtrees.
for sub in reflection ontology domains templates public install; do
  if [ -d "${STAGE}/data/${sub}" ]; then
    rm -rf "${DATA_DIR:?}/${sub}"
    cp -a "${STAGE}/data/${sub}" "${DATA_DIR}/${sub}"
  fi
done
ok "data → ${DATA_DIR}"

# ── Seed the config skeleton (idempotent — never clobber existing config) ─────
mkdir -p "$CONFIG_DIR"
if [ -f "${STAGE}/config/config.ncl" ]; then
  cp "${STAGE}/config/config.ncl" "${CONFIG_DIR}/config.ncl.example"
  [ -f "${CONFIG_DIR}/config.ncl" ] || cp "${STAGE}/config/config.ncl" "${CONFIG_DIR}/config.ncl"
fi
for f in projects.ncl remote-projects.ncl streams.json; do
  [ -f "${STAGE}/config/${f}" ] && [ ! -f "${CONFIG_DIR}/${f}" ] && cp "${STAGE}/config/${f}" "${CONFIG_DIR}/${f}"
done
if [ -d "${STAGE}/config/schemas" ]; then
  mkdir -p "${CONFIG_DIR}/schemas"
  for f in "${STAGE}/config/schemas/"*; do
    [ -e "$f" ] || continue
    bn="$(basename "$f")"
    [ -f "${CONFIG_DIR}/schemas/${bn}" ] || cp "$f" "${CONFIG_DIR}/schemas/${bn}"
  done
fi
ok "config → ${CONFIG_DIR}"

# ── Runtime dependency: nushell (required by the CLI wrapper) ─────────────────
if ! need nu; then
  warn "Nushell (nu) >= 0.110 is required by the 'ontoref' CLI but was not found."
  warn "Install it: https://www.nushell.sh/book/installation.html  (the daemon itself does not need it)"
fi

case ":${PATH}:" in
  *":${BIN_DIR}:"*) ;;
  *) warn "${BIN_DIR} is not on PATH — add it: export PATH=\"${BIN_DIR}:\$PATH\"" ;;
esac

printf '\n'
ok "ontoref ${VERSION} installed"
log "next:  ontoref describe project   (run inside an ontoref-onboarded project)"
log "       ontoref-daemon             (optional accelerator — caches NCL export)"
