#!/bin/sh
# Elicana installer — macOS + Linux, one file for two jobs (build order rev 2,
# Ameet 2026-09-14: one-command install for all three OSes, no code signing).
#
# Job 1: served at https://elicana.ai/install.sh and run as:
#   curl -fsSL https://elicana.ai/install.sh | sh
# In this mode there is no binary beside the script, so it detects OS/arch,
# downloads the right release tarball through the counting redirect
# (https://api.elicana.ai/api/v1/dl/<platform>), verifies it against the
# published SHA256SUMS, and extracts it.
#
# Job 2: ships INSIDE every macOS/Linux release tarball
# (elicana-vX.Y.Z-{linux,darwin}-{amd64,arm64}.tar.gz — specs/TRIAL_BUILD.md
# §4) next to a binary literally named "elicana". Run as `sh install.sh` (or
# `bash install.sh`) from the extracted directory, it finds that binary
# beside itself and skips the network entirely — the tarball's own
# SHA256SUMS entry (verified separately, e.g. `sha256sum -c SHA256SUMS
# --ignore-missing`, per the tarball README) is the user's verification step
# for that path, not a second in-script fetch.
#
# Usage:
#   sh install.sh                 # auto-detect: local binary beside the
#                                  #   script, else download for this host
#   sh install.sh /path/to/elicana  # explicit binary (skips detection)
#   sh install.sh --system        # prefer /usr/local/bin over ~/.local/bin
#                                  #   (only if it's writable — never sudo)
#
# Env overrides:
#   ELICANA_INSTALL_DIR    — install location, overrides --system and defaults
#   ELICANA_DL_BASE        — base for the counting download redirect
#                             (default https://api.elicana.ai/api/v1/dl)
#   ELICANA_SUMS_URL       — SHA256SUMS location to verify against
#                             (default https://downloads.elicana.ai/latest/SHA256SUMS)
#   ELICANA_DL_FALLBACK_BASE — direct-download base used only if ELICANA_DL_BASE
#                             fails outright (default https://downloads.elicana.ai/latest)
# These exist for testing this script end to end against a local fake
# server, without touching the real network or the real release.
#
# POSIX sh, no bashisms (dash/ash compatible), no sudo, idempotent —
# re-running upgrades the binary in place.
#
# ── Dry-run test fixture ────────────────────────────────────────────────────
# Exercised locally (see PLAN.md TR.5) with a fake server standing in for
# both api.elicana.ai and downloads.elicana.ai:
#
#   fixture=$(mktemp -d) && cd "$fixture"
#   mkdir -p dl/linux
#   cp /path/to/test-elicana-linux-amd64.tar.gz dl/linux/index.html   # or a
#     # tiny handler script — anything python3 -m http.server can serve at
#     # /dl/linux and /latest/{SHA256SUMS,elicana-linux-amd64.tar.gz}
#   python3 -m http.server 8000 &
#
#   # 1. Curl-pipe fresh install (no local binary):
#   ELICANA_DL_BASE=http://localhost:8000/dl \
#   ELICANA_SUMS_URL=http://localhost:8000/latest/SHA256SUMS \
#   ELICANA_INSTALL_DIR="$fixture/bin" \
#   ELICANA_TELEMETRY=off \
#     sh -c 'cat build/install.sh | sh'   # $0 becomes "sh"; must download,
#                                          # never look beside itself
#
#   # 2. Stray-binary regression (the fix for the BLOCKER above): put an
#   # unrelated, unverified ./elicana in the CWD, then pipe-install again —
#   # it MUST be ignored and the real binary downloaded instead:
#   echo 'echo IF THIS RUNS THE STRAY BINARY WAS USED' > ./elicana
#   chmod +x ./elicana
#   ELICANA_DL_BASE=http://localhost:8000/dl \
#   ELICANA_SUMS_URL=http://localhost:8000/latest/SHA256SUMS \
#   ELICANA_INSTALL_DIR="$fixture/bin2" \
#   ELICANA_TELEMETRY=off \
#     sh -c 'cat build/install.sh | sh'
#   # Assert: no "IF THIS RUNS..." in the output, and bin2/elicana's sha256
#   # matches the downloaded tarball's SHA256SUMS entry, not ./elicana.
#
#   # 3. Tarball mode: `cp build/install.sh elicana .` then `sh install.sh`
#   # (SCRIPT_DIR now resolves — a real file — and must find ./elicana
#   # beside it, skipping the network).
#
#   # 4. Idempotent re-run: run case 1 twice, confirm no error and the binary
#   # is simply overwritten.
#
#   # 5. Checksum-mismatch abort: corrupt the served tarball or SHA256SUMS
#   # entry and confirm the script exits non-zero before any copy to
#   # ELICANA_INSTALL_DIR.
#
#   kill %1  # stop the fixture server
set -eu

for _dep in curl tar; do
  if ! command -v "$_dep" >/dev/null 2>&1; then
    echo "Error: '$_dep' is required but was not found on PATH." >&2
    exit 1
  fi
done
unset _dep

DL_BASE="${ELICANA_DL_BASE:-https://api.elicana.ai/api/v1/dl}"
SUMS_URL="${ELICANA_SUMS_URL:-https://downloads.elicana.ai/latest/SHA256SUMS}"
# Fallback direct-download base, used only if the counting redirect
# ($DL_BASE) fails outright — e.g. api.elicana.ai is down. Checksum
# verification against $SUMS_URL still applies either way; this only skips
# the download-count tracking, which we tell the user about explicitly.
DL_FALLBACK_BASE="${ELICANA_DL_FALLBACK_BASE:-https://downloads.elicana.ai/latest}"

SYSTEM_INSTALL=0
BINARY_ARG=""
for arg in "$@"; do
  case "$arg" in
    --system)
      SYSTEM_INSTALL=1
      ;;
    -h|--help)
      echo "Usage: sh install.sh [path/to/elicana] [--system]"
      exit 0
      ;;
    -*)
      echo "Unknown flag: $arg" >&2
      exit 1
      ;;
    *)
      BINARY_ARG="$arg"
      ;;
  esac
done

# Resolve the directory this script lives in (works whether invoked as
# `sh install.sh`, `./install.sh`, or via an absolute/relative path) — but
# ONLY when $0 actually points at a real file. When piped in (`curl -fsSL
# .../install.sh | sh`), $0 is just "sh" (or "-"), there is no script file on
# disk, and `dirname "$0"` silently resolves to the caller's CWD. Without this
# guard a stray `./elicana` sitting in the CWD would be picked up by the
# "beside this script" check below and installed/exec'd unverified — never
# downloaded, never checksummed. Leave SCRIPT_DIR empty in that case so only
# the explicit-arg and download paths remain.
if [ -f "$0" ]; then
  SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
else
  SCRIPT_DIR=""
fi

OS_NAME="$(uname -s)"

WORKDIR=""
CLEANED_UP=0
cleanup() {
  # Idempotent: called explicitly before the final `exec` (which replaces
  # this process and would otherwise skip the EXIT trap) AND still runs via
  # the trap on every other exit path (errors, early `exit`).
  if [ "$CLEANED_UP" -eq 1 ]; then
    return 0
  fi
  CLEANED_UP=1
  if [ -n "$WORKDIR" ] && [ -d "$WORKDIR" ]; then
    rm -rf "$WORKDIR"
  fi
}
trap cleanup EXIT

download_and_verify() {
  arch_raw="$(uname -m)"

  case "$OS_NAME" in
    Darwin) os_key="darwin" ;;
    Linux)  os_key="linux" ;;
    *)
      echo "Error: unsupported OS '$OS_NAME'." >&2
      echo "Windows users: irm https://elicana.ai/install.ps1 | iex  (in PowerShell)" >&2
      exit 1
      ;;
  esac

  case "$arch_raw" in
    arm64|aarch64) arch_key="arm64" ;;
    x86_64|amd64)  arch_key="amd64" ;;
    *)
      echo "Error: unsupported architecture '$arch_raw'." >&2
      exit 1
      ;;
  esac

  # platform: the §2.9 /dl/{platform} key. filename: the §4 canonical
  # "latest/" object name — these are NOT the same string (e.g. platform
  # "linux" downloads filename "elicana-linux-amd64.tar.gz"), so each is set
  # explicitly per combination rather than derived from the other.
  if [ "$os_key" = "linux" ] && [ "$arch_key" = "amd64" ]; then
    platform="linux"
    filename="elicana-linux-amd64.tar.gz"
  elif [ "$os_key" = "linux" ] && [ "$arch_key" = "arm64" ]; then
    platform="linux-arm64"
    filename="elicana-linux-arm64.tar.gz"
  elif [ "$os_key" = "darwin" ] && [ "$arch_key" = "arm64" ]; then
    platform="darwin-arm64"
    filename="elicana-darwin-arm64.tar.gz"
  elif [ "$os_key" = "darwin" ] && [ "$arch_key" = "amd64" ]; then
    platform="darwin-amd64"
    filename="elicana-darwin-amd64.tar.gz"
  else
    echo "Error: unsupported OS/arch combination: $OS_NAME/$arch_raw" >&2
    exit 1
  fi

  WORKDIR="$(mktemp -d)"
  archive="$WORKDIR/$filename"

  echo "Detected $OS_NAME/$arch_raw -> platform '$platform'"
  echo "Downloading Elicana..."
  if ! curl -fsSL "$DL_BASE/$platform" -o "$archive"; then
    echo "Download via $DL_BASE failed; retrying from $DL_FALLBACK_BASE (download counter skipped)." >&2
    curl -fsSL "$DL_FALLBACK_BASE/$filename" -o "$archive"
  fi

  echo "Verifying checksum against $SUMS_URL ..."
  sums_file="$WORKDIR/SHA256SUMS"
  curl -fsSL "$SUMS_URL" -o "$sums_file"

  expected="$(awk -v f="$filename" '$2==f || $2=="*"f {print $1}' "$sums_file" | head -n1)"
  if [ -z "$expected" ]; then
    echo "Error: no checksum entry for '$filename' in $SUMS_URL" >&2
    exit 1
  fi

  if command -v sha256sum >/dev/null 2>&1; then
    actual="$(sha256sum "$archive" | awk '{print $1}')"
  elif command -v shasum >/dev/null 2>&1; then
    actual="$(shasum -a 256 "$archive" | awk '{print $1}')"
  else
    echo "Error: neither sha256sum nor shasum is available to verify the download." >&2
    exit 1
  fi

  if [ "$expected" != "$actual" ]; then
    echo "Error: checksum mismatch for $filename" >&2
    echo "  expected: $expected" >&2
    echo "  actual:   $actual" >&2
    exit 1
  fi
  echo "Checksum OK ($filename)"

  echo "Extracting..."
  tar -xzf "$archive" -C "$WORKDIR"

  BINARY="$WORKDIR/elicana"
  if [ ! -f "$BINARY" ]; then
    echo "Error: 'elicana' binary not found inside $filename" >&2
    exit 1
  fi
}

# ── Locate the binary: explicit arg > beside this script (tarball mode) > download (curl-pipe mode) ──
if [ -n "$BINARY_ARG" ]; then
  BINARY="$BINARY_ARG"
  if [ ! -f "$BINARY" ]; then
    echo "Error: binary not found at $BINARY" >&2
    exit 1
  fi
elif [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/elicana" ]; then
  BINARY="$SCRIPT_DIR/elicana"
else
  download_and_verify
fi

# ── Install location ────────────────────────────────────────────────────────
if [ -n "${ELICANA_INSTALL_DIR:-}" ]; then
  INSTALL_DIR="$ELICANA_INSTALL_DIR"
elif [ "$SYSTEM_INSTALL" -eq 1 ] && [ -w /usr/local/bin ] 2>/dev/null; then
  INSTALL_DIR="/usr/local/bin"
else
  INSTALL_DIR="${HOME:?cannot determine home directory; set ELICANA_INSTALL_DIR}/.local/bin"
fi
TARGET="$INSTALL_DIR/elicana"

mkdir -p "$INSTALL_DIR"
cp "$BINARY" "$TARGET"
chmod +x "$TARGET"

echo "Elicana installed to: $TARGET"

case ":$PATH:" in
  *":$INSTALL_DIR:"*) ;;
  *)
    echo ""
    echo "NOTE: $INSTALL_DIR is not on your PATH."
    echo "Add this to your shell profile (~/.bashrc, ~/.zshrc, ~/.zprofile, etc.):"
    echo "  export PATH=\"$INSTALL_DIR:\$PATH\""
    echo "Then reload your shell, or open a new terminal."
    ;;
esac

# ── Linux only: XDG .desktop launcher (app menu / dock integration) ────────
if [ "$OS_NAME" = "Linux" ]; then
  DESKTOP_DIR="$HOME/.local/share/applications"
  DESKTOP_FILE="$DESKTOP_DIR/elicana.desktop"

  CREATE_LAUNCHER=1
  if [ -t 0 ] && [ -t 1 ]; then
    echo ""
    printf "Create application launcher entry (recommended for GNOME/KDE)? [Y/n] "
    read -r ANSWER
    case "${ANSWER:-Y}" in
      [Nn]*) CREATE_LAUNCHER=0 ;;
      *) CREATE_LAUNCHER=1 ;;
    esac
  fi
  # Non-interactive (curl | sh): stdin is the script itself, so there is
  # nothing to prompt on — default to creating the launcher, matching the
  # interactive default, rather than hanging or misreading script bytes.

  if [ "$CREATE_LAUNCHER" -eq 1 ]; then
    mkdir -p "$DESKTOP_DIR"
    cat > "$DESKTOP_FILE" <<DESKTOP
[Desktop Entry]
Version=1.0
Type=Application
Name=Elicana
Comment=Personal knowledge intelligence — compile, search, and synthesize your documents
Exec=$TARGET open admin
Icon=$TARGET
Terminal=false
Categories=Office;Knowledge;
Keywords=notes;knowledge;AI;search;
Actions=Research;

[Desktop Action Research]
Name=Open Research Chat
Exec=$TARGET open research
DESKTOP
    chmod 644 "$DESKTOP_FILE"
    if command -v update-desktop-database >/dev/null 2>&1; then
      update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
    fi
    echo "Launcher created at: $DESKTOP_FILE"
  fi
fi

# ── First-run setup ──────────────────────────────────────────────────────────
echo ""
echo "Starting first-time setup wizard..."
echo "(The wizard opens in your browser. If it doesn't open automatically,"
echo " copy the URL printed below and paste it into your browser.)"
echo ""
# `exec` replaces this shell process, so the EXIT trap never runs — clean up
# the temp workdir explicitly first (cleanup() is idempotent, so this is safe
# even if the trap already ran for some other reason).
cleanup
exec "$TARGET" setup
