#!/usr/bin/env bash
#
# Ch∆In — POSIX install script.
#
# Usage:
#     curl -fsSL https://get.chain.flemming.ai | sh
#     curl -fsSL https://get.chain.flemming.ai | sh -s -- --channel beta
#     curl -fsSL https://get.chain.flemming.ai | sh -s -- --no-bootstrap
#
# What it does:
#   1. Detects host OS + architecture and maps to a manifest key.
#   2. Fetches the channel's `manifest.json` (override with
#      `CHAIN_MANIFEST_URL=<url>`).
#   3. Downloads the matching binary, verifies its sha256 against
#      the manifest, drops it at `~/.local/bin/chain` (or a custom
#      `CHAIN_INSTALL_DIR`).
#   4. Runs `chain bootstrap` so `~/.chain/channels/<channel>/bin/chain`
#      exists, the `~/.chain/bin/chain` shim is in place, and your
#      shell rc has `~/.chain/bin` on PATH.
#
# Environment overrides:
#   CHAIN_CHANNEL          dev | beta | production (default: production)
#   CHAIN_MANIFEST_URL     full URL to manifest.json
#   CHAIN_INSTALL_DIR      where to put the entry-point binary
#                        (default: $HOME/.local/bin)
#   CHAIN_VERSION          force a specific version instead of latest
#   CHAIN_FORCE            1 = skip the "already installed" confirmation
#
# Exit codes:
#   0  installed cleanly
#   1  unsupported platform / missing dependency
#   2  manifest fetch failed
#   3  binary download / verification failed
#   4  bootstrap failed
#   6  an existing install was found and the operator declined
#      (or the run was non-interactive without CHAIN_FORCE=1)
#
# This script is shipped at `installer/install.sh` in the platform
# repo and mirrored as the body of <https://get.chain.flemming.ai>.

set -eu

# -- configuration -------------------------------------------------

CHANNEL="${CHAIN_CHANNEL:-production}"
INSTALL_DIR="${CHAIN_INSTALL_DIR:-$HOME/.local/bin}"
DEFAULT_MANIFEST_HOST="https://releases.chain.flemming.ai"
MANIFEST_URL="${CHAIN_MANIFEST_URL:-${DEFAULT_MANIFEST_HOST}/${CHANNEL}/manifest.json}"
DO_BOOTSTRAP=1
# Skip the "an existing chain is already installed" confirmation.
FORCE="${CHAIN_FORCE:-0}"

# Allow flags after `sh -s --` via `curl ... | sh -s -- --flag`.
while [ $# -gt 0 ]; do
  case "$1" in
    --channel)        CHANNEL="$2"; MANIFEST_URL="${DEFAULT_MANIFEST_HOST}/${CHANNEL}/manifest.json"; shift 2 ;;
    --manifest-url)   MANIFEST_URL="$2"; shift 2 ;;
    --install-dir)    INSTALL_DIR="$2"; shift 2 ;;
    --no-bootstrap)   DO_BOOTSTRAP=0; shift ;;
    --force)          FORCE=1; shift ;;
    --help|-h)
      sed -n '3,40p' "$0"
      exit 0
      ;;
    *)
      echo "unknown option: $1" >&2
      exit 1
      ;;
  esac
done

# -- prerequisites -------------------------------------------------

need() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "fatal: '$1' is required but not on PATH" >&2
    exit 1
  }
}
need curl
need uname
# sha256 tool: prefer sha256sum (Linux), fall back to shasum (macOS)
if command -v sha256sum >/dev/null 2>&1; then
  SHA_CMD="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
  SHA_CMD="shasum -a 256"
else
  echo "fatal: neither sha256sum nor shasum is available" >&2
  exit 1
fi
# openssl is used to verify the binary's ECDSA P-256 signature
# against the published Flemming.AI signing key. Optional — when
# missing we skip the signature check but still enforce sha256.
# Operators who want enforced verification can set
# CHAIN_REQUIRE_SIGNATURE=1.
HAVE_OPENSSL=0
if command -v openssl >/dev/null 2>&1; then HAVE_OPENSSL=1; fi
REQUIRE_SIG="${CHAIN_REQUIRE_SIGNATURE:-0}"
# Embedded Flemming.AI release-signing pubkey (ECDSA P-256).
# Kept inline rather than fetched at runtime so a MITM with a
# valid wildcard cert for *.flemming.ai cannot substitute the
# key together with a malicious binary. Mirrors the same
# pattern as the Rust hub's `include_str!`. Rotation procedure:
# update `infra/cosign/official.pub` AND this constant in
# install.sh + install.ps1 in the same commit; users running
# old installers will reject the new release, and the version
# bump is the intended forcing function.
SIG_PUBKEY_PEM='-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8SsjXx7VcvjvEbg4qrTag2GRn4kL
PUCZm85YCe0udF5qqKep1aeaTjmkvm9UutlDW+bUmtVSC54Qme5h3NNkFA==
-----END PUBLIC KEY-----'
# Optional override only for offline test fixtures + key
# rotation grace periods — do NOT rely on this for normal
# operation. When set, the URL takes precedence over the
# embedded key, which defeats the MITM defence by design.
SIG_PUBKEY_URL="${CHAIN_SIG_PUBKEY_URL:-}"

# -- platform detection -------------------------------------------

OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$OS/$ARCH" in
  linux/x86_64)        KEY="linux-x86_64"   ; EXT=""    ;;
  linux/aarch64|linux/arm64)
                       KEY="linux-aarch64"  ; EXT=""    ;;
  darwin/arm64)        KEY="macos-aarch64"  ; EXT=""    ;;
  darwin/x86_64)       KEY="macos-x86_64"   ; EXT=""    ;;
  *)
    echo "fatal: unsupported platform $OS/$ARCH" >&2
    echo "Supported: linux-x86_64, linux-aarch64, macos-aarch64, macos-x86_64." >&2
    echo "For Windows use installer/install.ps1." >&2
    exit 1
    ;;
esac

echo "Ch∆In install — platform: $KEY, channel: $CHANNEL"
echo "  manifest: $MANIFEST_URL"

# -- detect prior installs from other sources ---------------------
#
# We install the entry-point binary to $INSTALL_DIR/chain. A
# `chain` from a *different* source (Homebrew, cargo, a dev build,
# an earlier custom --install-dir) lands elsewhere and will shadow
# or be shadowed by this one depending on PATH order — and later
# uninstalling one source silently leaves the other behind. So we
# look for every `chain` on PATH plus the well-known package-manager
# locations, and make the operator confirm before adding a copy.
TARGET_BIN="$INSTALL_DIR/chain$EXT"

scan_existing_chain() {
  # Every `chain` reachable on PATH ...
  _ifs_save=$IFS
  IFS=:
  for d in $PATH; do
    [ -n "$d" ] || d="."
    [ -x "$d/chain$EXT" ] && printf '%s\n' "$d/chain$EXT"
  done
  IFS=$_ifs_save
  # ... plus well-known dirs that may be off the current PATH.
  for d in /opt/homebrew/bin /usr/local/bin "$HOME/.cargo/bin" "$HOME/.local/bin"; do
    [ -x "$d/chain$EXT" ] && printf '%s\n' "$d/chain$EXT"
  done
}

chain_version_of() {
  _v=$("$1" --version 2>/dev/null | head -n1) || _v=""
  [ -n "$_v" ] && printf '%s' "$_v" || printf 'version unknown'
}

removal_hint_for() {
  case "$1" in
    /opt/homebrew/bin/chain*|/usr/local/bin/chain*)
      echo "      if installed via Homebrew:  brew uninstall chain" >&2
      echo "      otherwise:                  rm $1" >&2 ;;
    "$HOME/.cargo/bin/chain"*)
      echo "      remove with:  cargo uninstall chain   (or: rm $1)" >&2 ;;
    *)
      echo "      remove with:  rm $1" >&2 ;;
  esac
}

EXISTING=$(scan_existing_chain | sort -u)
SELF_FOUND=0
FOREIGN_FOUND=0
for p in $EXISTING; do
  if [ "$p" = "$TARGET_BIN" ]; then
    SELF_FOUND=1
  else
    if [ "$FOREIGN_FOUND" = 0 ]; then
      echo >&2
      echo "warning: Ch∆In appears to be installed already from another source." >&2
      echo "         Having two copies means the one that runs depends on your" >&2
      echo "         PATH order, and removing one later leaves the other behind." >&2
      echo >&2
    fi
    FOREIGN_FOUND=1
    echo "  • $p  ($(chain_version_of "$p"))" >&2
    removal_hint_for "$p"
  fi
done

if [ "$SELF_FOUND" = 1 ] && [ "$FOREIGN_FOUND" = 0 ]; then
  echo "note: reinstalling/upgrading the existing copy at $TARGET_BIN" \
       "($(chain_version_of "$TARGET_BIN"))."
fi

if [ "$FOREIGN_FOUND" = 1 ]; then
  echo >&2
  if [ "$FORCE" = 1 ]; then
    echo "  (CHAIN_FORCE=1 / --force set — installing anyway)" >&2
  elif { exec 3<>/dev/tty; } 2>/dev/null; then
    # A controlling terminal is reachable even under `curl | sh`
    # (stdin is the script, so we read the answer from /dev/tty).
    printf 'Install another copy at %s anyway? [y/N] ' "$TARGET_BIN" >&3
    read _ans <&3 || _ans=""
    exec 3>&- 3<&-
    case "$_ans" in
      y|Y|yes|YES) : ;;
      *) echo "Aborted — no changes made." >&2; exit 6 ;;
    esac
  else
    echo "fatal: existing install found and this run is non-interactive." >&2
    echo "       Re-run with CHAIN_FORCE=1 to install anyway, or remove the" >&2
    echo "       copy shown above first." >&2
    exit 6
  fi
fi

# -- fetch manifest -----------------------------------------------

TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

MANIFEST_FILE="$TMPDIR/manifest.json"
if ! curl -fsSL "$MANIFEST_URL" -o "$MANIFEST_FILE"; then
  echo "fatal: could not fetch manifest from $MANIFEST_URL" >&2
  exit 2
fi

# Verify the manifest's own signature against the embedded
# pubkey BEFORE reading any binary entries. Closes the
# manifest-host-compromise attack vector where an attacker
# who controls the manifest could swap every binary entry +
# its sha256 + its signature_url at once.
MANIFEST_SIG_URL="${MANIFEST_URL}.sig"
MANIFEST_VERIFIED=0
if [ "$HAVE_OPENSSL" = 1 ]; then
  MANIFEST_SIG_FILE="$TMPDIR/manifest.json.sig"
  MANIFEST_PUBKEY_FILE="$TMPDIR/manifest.pub"
  if curl -fsSL "$MANIFEST_SIG_URL" -o "$MANIFEST_SIG_FILE" 2>/dev/null; then
    if [ -n "$SIG_PUBKEY_URL" ]; then
      curl -fsSL "$SIG_PUBKEY_URL" -o "$MANIFEST_PUBKEY_FILE" \
        || { echo "fatal: override pubkey fetch failed" >&2; exit 3; }
    else
      printf '%s\n' "$SIG_PUBKEY_PEM" > "$MANIFEST_PUBKEY_FILE"
    fi
    MANIFEST_SIG_RAW="$TMPDIR/manifest.json.sig.raw"
    if base64 -d < "$MANIFEST_SIG_FILE" > "$MANIFEST_SIG_RAW" 2>/dev/null \
       && openssl dgst -sha256 \
            -verify "$MANIFEST_PUBKEY_FILE" \
            -signature "$MANIFEST_SIG_RAW" \
            "$MANIFEST_FILE" >/dev/null 2>&1; then
      MANIFEST_VERIFIED=1
      echo "  manifest:  verified"
    else
      echo "fatal: manifest signature is invalid — refusing to read $MANIFEST_URL" >&2
      exit 2
    fi
  else
    echo "warning: manifest signature ($MANIFEST_SIG_URL) not available; binary sha256 + sig will still be enforced where present" >&2
  fi
fi
if [ "$MANIFEST_VERIFIED" = 0 ] && [ "$REQUIRE_SIG" = 1 ]; then
  echo "fatal: CHAIN_REQUIRE_SIGNATURE=1 but manifest was not signature-verified" >&2
  exit 2
fi

# Crude JSON extraction without a hard jq dep — looks for the
# `<key>` block inside `binaries`. If jq is available, prefer it.
if command -v jq >/dev/null 2>&1; then
  URL=$(jq -r ".binaries[\"$KEY\"].url // empty" "$MANIFEST_FILE")
  SHA=$(jq -r ".binaries[\"$KEY\"].sha256 // empty" "$MANIFEST_FILE")
  SIG_URL=$(jq -r ".binaries[\"$KEY\"].signature_url // empty" "$MANIFEST_FILE")
  VERSION=$(jq -r ".version // empty" "$MANIFEST_FILE")
else
  # Fallback: grep + sed. Manifest is small + machine-generated by
  # the release CI so the regex stays predictable. If you hand-edit
  # the manifest, install jq.
  BLOCK=$(grep -A3 "\"$KEY\"" "$MANIFEST_FILE" || true)
  URL=$(printf '%s' "$BLOCK" | sed -n 's/.*"url":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  SHA=$(printf '%s' "$BLOCK" | sed -n 's/.*"sha256":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  SIG_URL=$(printf '%s' "$BLOCK" | sed -n 's/.*"signature_url":[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
  VERSION=$(sed -n 's/.*"version":[[:space:]]*"\([^"]*\)".*/\1/p' "$MANIFEST_FILE" | head -1)
fi

if [ -z "$URL" ] || [ -z "$SHA" ] || [ -z "$VERSION" ]; then
  echo "fatal: manifest does not advertise a binary for $KEY" >&2
  echo "manifest body:" >&2
  cat "$MANIFEST_FILE" >&2
  exit 2
fi

echo "  version:  $VERSION"
echo "  binary:   $URL"

# -- download + verify --------------------------------------------

BIN_TMP="$TMPDIR/chain$EXT"
if ! curl -fsSL "$URL" -o "$BIN_TMP"; then
  echo "fatal: download failed: $URL" >&2
  exit 3
fi

ACTUAL_SHA=$($SHA_CMD "$BIN_TMP" | awk '{print $1}')
if [ "$(printf '%s' "$ACTUAL_SHA" | tr '[:upper:]' '[:lower:]')" != \
     "$(printf '%s' "$SHA"        | tr '[:upper:]' '[:lower:]')" ]; then
  echo "fatal: sha256 mismatch" >&2
  echo "  expected: $SHA"      >&2
  echo "  got:      $ACTUAL_SHA" >&2
  exit 3
fi
chmod +x "$BIN_TMP"
echo "  sha256:   verified"

# -- signature verification (defence-in-depth) ---------------------
#
# When the manifest advertises a signature_url and openssl is
# available, verify the ECDSA P-256 signature against the
# Flemming.AI publishing key. A failed check aborts the install.
#
# When openssl is missing or signature_url is empty, behaviour
# depends on CHAIN_REQUIRE_SIGNATURE:
#   0 (default) — skip with a warning, continue
#   1           — abort
SIG_VERIFIED=0
if [ -n "$SIG_URL" ]; then
  if [ "$HAVE_OPENSSL" = 1 ]; then
    SIG_FILE="$TMPDIR/chain.sig"
    PUBKEY_FILE="$TMPDIR/official.pub"
    # Pubkey source: embedded constant by default; URL only
    # when CHAIN_SIG_PUBKEY_URL is set (offline-test/rotation
    # grace-period escape hatch, NOT for normal operation).
    if [ -n "$SIG_PUBKEY_URL" ]; then
      curl -fsSL "$SIG_PUBKEY_URL" -o "$PUBKEY_FILE" \
        || { echo "fatal: could not fetch override pubkey from $SIG_PUBKEY_URL" >&2; exit 3; }
      PUBKEY_SOURCE="$SIG_PUBKEY_URL"
    else
      printf '%s\n' "$SIG_PUBKEY_PEM" > "$PUBKEY_FILE"
      PUBKEY_SOURCE="embedded"
    fi
    if curl -fsSL "$SIG_URL" -o "$SIG_FILE"; then
      SIG_RAW="$TMPDIR/chain.sig.raw"
      if base64 -d < "$SIG_FILE" > "$SIG_RAW" 2>/dev/null; then
        if openssl dgst -sha256 \
             -verify "$PUBKEY_FILE" \
             -signature "$SIG_RAW" \
             "$BIN_TMP" >/dev/null 2>&1; then
          SIG_VERIFIED=1
          echo "  signature: verified (key: $PUBKEY_SOURCE)"
        else
          echo "fatal: signature verification failed for $URL" >&2
          echo "  signed-by-key: $PUBKEY_SOURCE" >&2
          exit 3
        fi
      else
        echo "fatal: signature sidecar is not valid base64: $SIG_URL" >&2
        exit 3
      fi
    else
      echo "warning: signature_url present but download failed; sha256 alone enforced" >&2
    fi
  else
    echo "warning: openssl missing; cannot verify signature (sha256 still enforced)" >&2
  fi
fi
if [ "$SIG_VERIFIED" = 0 ] && [ "$REQUIRE_SIG" = 1 ]; then
  echo "fatal: CHAIN_REQUIRE_SIGNATURE=1 but no signature was verified" >&2
  exit 3
fi

# -- install entry-point binary -----------------------------------

mkdir -p "$INSTALL_DIR"
INSTALLED_BIN="$INSTALL_DIR/chain$EXT"
mv -f "$BIN_TMP" "$INSTALLED_BIN"
echo "installed: $INSTALLED_BIN"

# -- self-bootstrap ------------------------------------------------

if [ "$DO_BOOTSTRAP" = 1 ]; then
  echo "running bootstrap ..."
  if ! "$INSTALLED_BIN" bootstrap --channel "$CHANNEL"; then
    echo "warning: bootstrap reported a non-zero status" >&2
    echo "        the entry-point binary is installed at $INSTALLED_BIN" >&2
    echo "        run 'chain bootstrap' manually to finish setup" >&2
    exit 4
  fi
fi

echo
echo "Ch∆In $VERSION installed on channel '$CHANNEL'."
echo "Open a new terminal (or 'source' your shell rc), then:"
echo "  chain daemon start"
echo "  chain admin doctor"
