#!/usr/bin/env bash
# Install the Outprint CLI (`outprint`) and paired engine (`outprint-harness`)
# for the current machine.
# Published as:
#   curl -fsSL https://outprint.app/downloads/cli/install.sh | bash
#
# Requires bash (not POSIX sh): the public one-liner pipes into bash so
# systems where /bin/sh is dash still work.
set -euo pipefail

if [[ -z "${BASH_VERSION:-}" ]]; then
  echo "this installer requires bash; run: curl -fsSL https://outprint.app/downloads/cli/install.sh | bash" >&2
  exit 1
fi

BASE_URL="${OUTPRINT_BASE_URL:-https://outprint.app}"
BASE_URL="${BASE_URL%/}"
# Primary feeds live on R2. Origin still serves the installer and the
# pre-cutover CLI feed; we fall back there only on HTTP 404, and only
# when OUTPRINT_FEED_BASE is unset. An explicit base that 404s fails.
DEFAULT_FEED_BASE="https://downloads.outprint.app/cli"
LEGACY_FEED_BASE="${BASE_URL}/downloads/cli"
FEED_BASE="${OUTPRINT_FEED_BASE:-$DEFAULT_FEED_BASE}"
FEED_BASE="${FEED_BASE%/}"
BIN_DIR="${OUTPRINT_BIN_DIR:-${HOME}/.local/bin}"
BIN_NAME=outprint
HARNESS_NAME=outprint-harness
INSTALL_ROOT="${HOME}/.outprint"
VERSIONS_DIR="${INSTALL_ROOT}/versions"

write_feed_public_key() {
  # ECDSA P-256 SPKI PEM — must match CLI_FEED_PUBLIC_KEY_PEM in updater.rs
  if [[ -n "${OUTPRINT_CLI_FEED_PUBLIC_KEY_PEM:-}" ]]; then
    printf '%s\n' "$OUTPRINT_CLI_FEED_PUBLIC_KEY_PEM" >"$1"
    return
  fi
  cat >"$1" <<'PEM'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2qDB9aHwb9arEqYD1l5oiTuuJWkv
GqKN0odDAlxi9UWvnbcqhnS0Mfp0N/d1jD6zJ8vk8kXzTKzRlk4a/PsNsg==
-----END PUBLIC KEY-----
PEM
}

for arg in "$@"; do
  case "$arg" in
    --yes|-y) ;;
    --help|-h)
      cat <<'EOF'
Install the Outprint CLI as a versioned bundle in ~/.outprint/versions/vX
(outprint and outprint-harness as siblings) and a PATH shim at ~/.local/bin
(override with OUTPRINT_BIN_DIR).

  curl -fsSL https://outprint.app/downloads/cli/install.sh | bash

Environment:
  OUTPRINT_BASE_URL   origin host for the installer and feed fallback (default https://outprint.app)
  OUTPRINT_FEED_BASE  default https://downloads.outprint.app/cli
  OUTPRINT_BIN_DIR    default $HOME/.local/bin

Ships macOS (aarch64, x86_64) and Linux (x86_64, aarch64). Verifies the signed feed + sha256.
A missing harness, bad checksum, or bad signature fails before the PATH shim changes.
EOF
      exit 0
      ;;
    *)
      echo "unknown flag: $arg (try --help)" >&2
      exit 1
      ;;
  esac
done

if [[ -z "${HOME:-}" || "$HOME" != /* ]]; then
  echo "HOME must be an absolute path" >&2
  exit 1
fi

os="$(uname -s 2>/dev/null || echo unknown)"
arch="$(uname -m 2>/dev/null || echo x86_64)"
case "$os" in
  Darwin) os_label=macos ;;
  Linux) os_label=linux ;;
  *)
    echo "unsupported OS: $os (supported: macOS, Linux)" >&2
    exit 1
    ;;
esac
case "$arch" in
  aarch64|arm64) arch_label=aarch64 ;;
  x86_64|amd64) arch_label=x86_64 ;;
  *)
    echo "unsupported architecture: $arch (supported: aarch64, x86_64)" >&2
    exit 1
    ;;
esac

feed_name="feed-cli-${os_label}-${arch_label}.json"
feed_url="${FEED_BASE}/${feed_name}"
echo "Outprint CLI installer"
echo "  feed: ${feed_url}"

need() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "$1 is required" >&2
    exit 1
  fi
}
need curl
need tar
need openssl
if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then
  echo "shasum or sha256sum is required" >&2
  exit 1
fi

tmp="$(mktemp -d)"
staging=""
cleanup() {
  rm -rf "$tmp"
  if [[ -n "${staging:-}" && -d "$staging" ]]; then
    rm -rf "$staging"
  fi
}
trap cleanup EXIT

feed_body="${tmp}/feed.json"
# Do not use curl -f: we need the status so a missing R2 feed (404) can
# fall back to the origin-hosted feed from 0.1.54 installs.
fetch_feed() {
  local url="$1"
  local code
  code="$(curl -sS -L -o "$feed_body" -w '%{http_code}' "$url" || true)"
  printf '%s' "$code"
}
http_code="$(fetch_feed "$feed_url")"
if [[ "$http_code" == "404" ]]; then
  if [[ -n "${OUTPRINT_FEED_BASE:-}" ]]; then
    echo "could not fetch signed feed at ${feed_url} (HTTP 404)" >&2
    echo "OUTPRINT_FEED_BASE is set; not falling back to ${LEGACY_FEED_BASE}" >&2
    exit 1
  fi
  legacy_url="${LEGACY_FEED_BASE}/${feed_name}"
  if [[ "$feed_url" != "$legacy_url" ]]; then
    echo "  primary feed HTTP 404; trying legacy ${legacy_url}"
    feed_url="$legacy_url"
    http_code="$(fetch_feed "$feed_url")"
  fi
fi
if [[ "$http_code" != "200" ]]; then
  echo "could not fetch signed feed at ${feed_url} (HTTP ${http_code:-000})" >&2
  exit 1
fi

if command -v python3 >/dev/null 2>&1; then
  version="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["version"])' "$feed_body")"
  url="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["url"])' "$feed_body")"
  expected_sha="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["sha256"])' "$feed_body")"
  sig_hex="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8")).get("sig",""))' "$feed_body")"
else
  version="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$feed_body" | head -1)"
  url="$(sed -n 's/.*"url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$feed_body" | head -1)"
  expected_sha="$(sed -n 's/.*"sha256"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$feed_body" | head -1)"
  sig_hex="$(sed -n 's/.*"sig"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$feed_body" | head -1)"
fi
if [[ -z "${version:-}" || -z "${url:-}" || -z "${expected_sha:-}" || -z "${sig_hex:-}" ]]; then
  echo "could not parse signed feed JSON at ${feed_url}" >&2
  exit 1
fi

version_name="${version#v}"
case "$version_name" in
  ""|*/*|*..*|*$'\n'*|*$' '*|*\\*)
    echo "refusing invalid feed version: $version" >&2
    exit 1
    ;;
esac
version_dir="${VERSIONS_DIR}/v${version_name}"

# Host allowlist for package URL (bootstrap must not follow arbitrary hosts).
# Production packages live on downloads.outprint.app (R2). Feeds live there
# too (downloads.outprint.app/cli); origin is the installer and 404 fallback.
# Origin-hosted package URLs remain accepted.
case "$url" in
  https://outprint.app/*|https://www.outprint.app/*|https://downloads.outprint.app/*) ;;
  http://127.0.0.1/*|http://localhost/*|http://localhost:*/*|http://127.0.0.1:*/*) ;;
  *)
    echo "refusing package URL outside allowlisted hosts: $url" >&2
    exit 1
    ;;
esac

# Verify ECDSA P-256/SHA-256 feed signature (LibreSSL/OpenSSL portable).
msg_file="${tmp}/msg"
sig_file="${tmp}/sig"
pk_file="${tmp}/pk.pem"
printf '%s\n%s\n%s\n' "$version" "$url" "$expected_sha" >"$msg_file"
write_feed_public_key "$pk_file"
if command -v xxd >/dev/null 2>&1; then
  printf '%s' "$sig_hex" | xxd -r -p >"$sig_file"
else
  # Fallback without xxd
  python3 -c 'import sys; open(sys.argv[1],"wb").write(bytes.fromhex(sys.argv[2]))' "$sig_file" "$sig_hex"
fi
if ! openssl dgst -sha256 -verify "$pk_file" -signature "$sig_file" "$msg_file" >/dev/null 2>&1; then
  echo "feed signature verification failed" >&2
  exit 1
fi
echo "  feed signature: ok"

echo "  version: ${version}"
echo "  package: ${url}"

archive="${tmp}/outprint-cli.tar.gz"
curl -fsSL "$url" -o "$archive"

if command -v shasum >/dev/null 2>&1; then
  got_sha="$(shasum -a 256 "$archive" | awk '{print $1}')"
else
  got_sha="$(sha256sum "$archive" | awk '{print $1}')"
fi
if [[ "${got_sha}" != "${expected_sha}" ]]; then
  echo "sha256 mismatch: expected ${expected_sha}, got ${got_sha}" >&2
  exit 1
fi

extract="${tmp}/extract"
mkdir -p "$extract"
tar -xzf "$archive" -C "$extract"
if [[ ! -f "${extract}/${BIN_NAME}" ]]; then
  echo "archive did not contain ${BIN_NAME}" >&2
  exit 1
fi
if [[ ! -f "${extract}/${HARNESS_NAME}" ]]; then
  echo "archive did not contain ${HARNESS_NAME}" >&2
  exit 1
fi
chmod 755 "${extract}/${BIN_NAME}" "${extract}/${HARNESS_NAME}"

mkdir -p "$VERSIONS_DIR" "$BIN_DIR"
staging="${VERSIONS_DIR}/.staging.$$"
rm -rf "$staging"
mkdir -p "$staging"
install -m 755 "${extract}/${BIN_NAME}" "${staging}/${BIN_NAME}"
install -m 755 "${extract}/${HARNESS_NAME}" "${staging}/${HARNESS_NAME}"
if [[ ! -x "${staging}/${BIN_NAME}" || ! -x "${staging}/${HARNESS_NAME}" ]]; then
  echo "staged bundle is missing an executable ${BIN_NAME} or ${HARNESS_NAME}" >&2
  exit 1
fi

if [[ -e "$version_dir" && ! -d "$version_dir" ]]; then
  echo "refusing to replace ${version_dir}: not a directory" >&2
  exit 1
fi
if [[ -d "$version_dir" ]]; then
  old="${version_dir}.old.$$"
  rm -rf "$old"
  mv "$version_dir" "$old"
  if ! mv "$staging" "$version_dir"; then
    mv "$old" "$version_dir"
    echo "could not activate ${version_dir}" >&2
    exit 1
  fi
  rm -rf "$old"
else
  mv "$staging" "$version_dir"
fi
staging=""

product_abs="${version_dir}/${BIN_NAME}"
harness_abs="${version_dir}/${HARNESS_NAME}"
if [[ "$product_abs" != /* || "$harness_abs" != /* ]]; then
  echo "installed harness path must be absolute" >&2
  exit 1
fi
if [[ ! -x "$product_abs" || ! -x "$harness_abs" ]]; then
  echo "activated bundle is missing an executable ${BIN_NAME} or ${HARNESS_NAME}" >&2
  exit 1
fi

dest="${BIN_DIR}/${BIN_NAME}"
if [[ -d "$dest" ]]; then
  echo "refusing to replace ${dest}: is a directory" >&2
  exit 1
fi
shim_tmp="${dest}.new.$$"
rm -f "$shim_tmp"
ln -s "$product_abs" "$shim_tmp"
mv -f "$shim_tmp" "$dest"

echo "Installed ${dest} -> ${product_abs} (${version})"
echo "Harness ${harness_abs}"
case ":${PATH}:" in
  *":${BIN_DIR}:"*) ;;
  *)
    echo "Note: add ${BIN_DIR} to PATH, e.g. export PATH=\"${BIN_DIR}:\$PATH\""
    ;;
esac
echo "Next: outprint login"
echo "Upgrade later: re-run this installer"
