#!/usr/bin/env bash
# app-bootstrap — install/update an application's management scripts on a host.
#
# Ships per-app as <product>-bootstrap.sh (with PRODUCT_DEFAULT baked in by the
# app's CI) so an operator runs it with no --product. The generic engine copy
# needs --product on first install.
set -euo pipefail

# Baked per-app by the profile's CI (sed). Empty = require --product on install.
PRODUCT_DEFAULT="elza"

tmp=""
cleanup() { local rc=$?; [ -n "$tmp" ] && rm -rf "$tmp"; return "$rc"; }
trap cleanup EXIT

log()  { printf '[bootstrap] %s\n'        "$*" >&2; }
warn() { printf '[bootstrap] WARN: %s\n'  "$*" >&2; }
die()  { printf '[bootstrap] ERROR: %s\n' "$*" >&2; exit 1; }

# Self-naming help: rewrite the generic 'app-bootstrap' to this script's own name.
# Prefer the file name ($0 — set for the installed/standalone copy); fall back to
# the baked product (the piped `curl | bash` install has no file name); then to
# the generic 'app' (the engine's own unbranded copy).
usage() {
    local cmd pfx; cmd="$(basename -- "${0:-}")"
    case "$cmd" in
        *-bootstrap.sh) pfx="${cmd%-bootstrap.sh}" ;;
        *-bootstrap)    pfx="${cmd%-bootstrap}" ;;
        *)              pfx="${PRODUCT_DEFAULT:-app}" ;;
    esac
    sed "s/app-bootstrap/$pfx-bootstrap/g" <<'EOF'
app-bootstrap — download an application's management scripts into <APP_HOME>/scripts.

Usage:
  app-bootstrap.sh [--product <id>] [--home <APP_HOME>] [--channel stable|main]
                   [--base-url URL] [--force]

APP_HOME is auto-detected when --home is omitted: from $APP_HOME, else from this
script's own location when it lives in <APP_HOME>/scripts (next to common.sh),
else from the current directory if it is such a scripts dir. So an installed copy
updates itself with no arguments:
  /opt/elza/scripts/elza-bootstrap.sh

Initial install (piped — nothing installed yet, so --home is needed):
  curl -fsSL https://get.lightcomp.com/elza/elza-bootstrap.sh \
    | bash -s -- --home /opt/elza

An existing <product>-env is preserved. The channel and product are remembered
between updates. Re-running is cheap: when the published build is unchanged,
nothing is downloaded and the script reports "Already up to date".

Options:
  --product <id>    product code (elza, imwhooser, …); required on first install
                    unless baked into this script. Remembered for future updates.
  --home <dir>      target APP_HOME (default: auto-detected)
  --channel <c>     stable (default) or main; remembered for future updates
  --base-url <url>  download base (default: https://get.lightcomp.com/<product>)
  --force           reinstall even when already up to date
  -h, --help
EOF
}

# Auto-detect APP_HOME when --home is omitted, so the installed copy can update
# itself. Order: $APP_HOME; this script's own location (an <APP_HOME>/scripts dir
# is recognised by a sibling common.sh); the current dir if it is such a scripts
# dir, or an APP_HOME containing one. Empty if none match.
detect_home() {
    if [ -n "${APP_HOME:-}" ]; then printf '%s' "$APP_HOME"; return; fi
    local src="${BASH_SOURCE[0]:-}" d
    if [ -n "$src" ] && d="$(cd "$(dirname "$src")" 2>/dev/null && pwd)" && [ -f "$d/common.sh" ]; then
        ( cd "$d/.." && pwd ); return
    fi
    if [ -f "$PWD/common.sh" ] && [ "$(basename "$PWD")" = scripts ]; then
        ( cd "$PWD/.." && pwd ); return
    fi
    if [ -f "$PWD/scripts/common.sh" ]; then printf '%s' "$PWD"; return; fi
    printf ''
}

# Read PRODUCT from an installed tree (the product-defaults.sh shipped in the
# tarball, or a remembered stamp) — so updates need no --product.
detect_product() {
    local scripts="$1" p=""
    if [ -r "$scripts/.bootstrap-product" ]; then
        p="$(tr -d ' \t\r\n' < "$scripts/.bootstrap-product")"
    fi
    if [ -z "$p" ] && [ -r "$scripts/product-defaults.sh" ]; then
        p="$(sed -nE 's/^[[:space:]]*PRODUCT=([A-Za-z0-9_-]+).*/\1/p' "$scripts/product-defaults.sh" | head -1)"
    fi
    printf '%s' "$p"
}

# Rename a generic file ($1) to its product name ($2), idempotently. A plain `mv`
# aborts the bootstrap (set -e) with "are the same file" whenever the two resolve
# to one inode — a re-run, or a hard/symlink left by an earlier layout. Removing
# the destination first sidesteps that and always leaves a real file at dst.
_render_mv() {
    [ "$1" = "$2" ] && return 0                  # same path (e.g. product literally 'app')
    { [ -e "$1" ] || [ -L "$1" ]; } || return 0  # nothing to rename
    rm -f -- "$2"
    mv -f -- "$1" "$2"
}

# Turn the just-extracted GENERIC tree (app-deploy, systemd/app*.service, …) into
# product-named, host-resolved artifacts, so the host only ever shows <product>-*
# and the units are ready to copy & enable with no editing. Args: scripts dir,
# APP_HOME, product. Idempotent — safe to re-run.
render_installed() {
    local scripts="$1" home="$2" product="$3" c f u base APP_TITLE APP_USER SERVICE_NAME APP_JAR HEAP
    # identity from the extracted profile (placeholder APP_HOME while sourcing)
    local id
    id="$(
        set +u; APP_HOME="$home"
        # shellcheck disable=SC1091
        [ -r "$scripts/product-defaults.sh" ] && . "$scripts/product-defaults.sh"
        printf '%s\t%s\t%s\t%s\t%s' \
            "${APP_TITLE:-$product}" "${APP_USER:-$product}" "${SERVICE_NAME:-$product}" \
            "${APP_JAR:-app.jar}" "${HEAP:-2G}"
    )"
    IFS=$'\t' read -r APP_TITLE APP_USER SERVICE_NAME APP_JAR HEAP <<<"$id"

    # rename the CLIs app-* -> <product>-*
    for c in deploy backup health update; do
        _render_mv "$scripts/app-$c" "$scripts/$product-$c"
    done
    _render_mv "$scripts/app-bootstrap.sh" "$scripts/$product-bootstrap.sh"
    chmod +x "$scripts/$product-deploy" "$scripts/$product-backup" "$scripts/$product-health" \
             "$scripts/$product-update" "$scripts/$product-bootstrap.sh" 2>/dev/null || true
    # The --help headers are NOT rewritten here: each CLI is self-naming at runtime
    # (it derives its command name from $0), so the renamed <product>-* files — and
    # any custom CLI sourcing the libs — print their own names with no edit.

    # render the systemd unit templates: substitute identity + APP_HOME, rename.
    if [ -d "$scripts/systemd" ]; then
        for u in "$scripts"/systemd/app*.service "$scripts"/systemd/app*.timer; do
            [ -f "$u" ] || continue
            sed -i -e "s/@PRODUCT@/$product/g"   -e "s/@APP_TITLE@/$APP_TITLE/g" \
                   -e "s/@APP_USER@/$APP_USER/g" -e "s/@SERVICE_NAME@/$SERVICE_NAME/g" \
                   -e "s/@APP_JAR@/$APP_JAR/g"   -e "s/@HEAP@/$HEAP/g" \
                   -e "s|@APP_HOME@|$home|g" "$u"
            base="$(basename "$u")"
            _render_mv "$u" "$scripts/systemd/$product${base#app}"
        done
    fi
}

main() {
    local PRODUCT="$PRODUCT_DEFAULT" HOME_DIR="" CHANNEL="" BASE_URL="" home_detected=0 FORCE=0
    while [ $# -gt 0 ]; do
        case "$1" in
            --product)    PRODUCT="$2"; shift ;;
            --product=*)  PRODUCT="${1#*=}" ;;
            --home)       HOME_DIR="$2"; shift ;;
            --home=*)     HOME_DIR="${1#*=}" ;;
            --channel)    CHANNEL="$2"; shift ;;
            --channel=*)  CHANNEL="${1#*=}" ;;
            --base-url)   BASE_URL="$2"; shift ;;
            --base-url=*) BASE_URL="${1#*=}" ;;
            --force)      FORCE=1 ;;
            -h|--help)    usage; exit 0 ;;
            *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
        esac
        shift
    done

    if [ -z "$HOME_DIR" ]; then HOME_DIR="$(detect_home)"; home_detected=1; fi
    [ -n "$HOME_DIR" ] || { usage >&2; die "could not auto-detect APP_HOME — run the installed copy from <APP_HOME>/scripts, export APP_HOME, or pass --home"; }
    HOME_DIR="$(cd "$HOME_DIR" 2>/dev/null && pwd || printf '%s' "$HOME_DIR")"
    local scripts="$HOME_DIR/scripts"

    # Product: explicit --product / baked default wins; else remembered from an
    # existing install; else fail (we cannot name the tarball/URL without it).
    [ -n "$PRODUCT" ] || PRODUCT="$(detect_product "$scripts")"
    [ -n "$PRODUCT" ] || { usage >&2; die "no product set — pass --product <id> on first install"; }

    [ -n "$BASE_URL" ] || BASE_URL="https://get.lightcomp.com/$PRODUCT"

    # Channel: explicit --channel wins; else remembered; else stable.
    local channel_src=option
    if [ -z "$CHANNEL" ]; then
        if [ -r "$scripts/.bootstrap-channel" ] \
            && CHANNEL="$(tr -d ' \t\r\n' < "$scripts/.bootstrap-channel")" && [ -n "$CHANNEL" ]; then
            channel_src=remembered
        else
            CHANNEL=stable; channel_src=default
        fi
    fi

    # The channel is a DIRECTORY, not a file name: stable lives at the base URL,
    # main under snapshots/main. Same names in both.
    local file="$PRODUCT-scripts.tar.gz" src_url
    case "$CHANNEL" in
        stable) src_url="$BASE_URL" ;;
        main)   src_url="$BASE_URL/snapshots/main" ;;
        *) die "channel must be 'stable' or 'main' (got '$CHANNEL')" ;;
    esac

    [ "$home_detected" -eq 1 ] && log "Auto-detected APP_HOME: $HOME_DIR"
    tmp="$(mktemp -d)"

    # Fetch the published checksum FIRST (a tiny file): besides verifying the
    # tarball it tells us whether the installed scripts are already current.
    local stamp="$scripts/.bootstrap-sha256" published="" was_install=0 unchanged=0
    [ -f "$scripts/common.sh" ] && was_install=1
    if curl -fsSL -o "$tmp/$file.sha256" "$src_url/$file.sha256" 2>/dev/null; then
        published="$(cut -d' ' -f1 < "$tmp/$file.sha256" | tr -d '[:space:]')"
    fi
    if [ -n "$published" ] && [ -r "$stamp" ] \
        && [ "$(tr -d '[:space:]' < "$stamp")" = "$published" ]; then
        unchanged=1
    fi
    if [ "$unchanged" -eq 1 ] && [ "$FORCE" -eq 0 ]; then
        printf '%s\n' "$CHANNEL"  > "$scripts/.bootstrap-channel" 2>/dev/null || true
        printf '%s\n' "$PRODUCT"  > "$scripts/.bootstrap-product" 2>/dev/null || true
        log "Already up to date ($PRODUCT, $CHANNEL channel) — nothing downloaded (--force to reinstall)."
        return 0
    fi

    log "Downloading $file ($PRODUCT, $CHANNEL channel) from $src_url"
    curl -fsSL -o "$tmp/$file" "$src_url/$file" || die "download failed: $src_url/$file"

    if [ -n "$published" ]; then
        if ( cd "$tmp" && sha256sum -c "$file.sha256" ) >/dev/null 2>&1; then
            log "Checksum verified"
        else
            die "checksum verification failed for $file"
        fi
    else
        warn "no .sha256 published; skipping checksum verification"
    fi

    mkdir -p "$scripts"
    # -m (touch): set extraction mtime to now, avoiding clock-skew warnings.
    tar xzmf "$tmp/$file" -C "$scripts"
    # The tarball ships GENERIC names (app-deploy, systemd/app*.service). Render
    # them into product-named, host-resolved artifacts so the host only ever shows
    # <product>-* and the units are ready to copy & enable with no hand-editing.
    render_installed "$scripts" "$HOME_DIR" "$PRODUCT"

    # Migration: remove stale top-level unit copies left by older tarballs (real
    # units belong in /etc/systemd/system; current ones live in systemd/).
    local f2
    for f2 in "$scripts"/*.service "$scripts"/*.timer; do
        [ -f "$f2" ] && rm -f "$f2" && log "removed legacy top-level unit $(basename "$f2")"
    done

    # Seed <product>-env on first install; never overwrite an existing one.
    local env_file="$scripts/$PRODUCT-env" config_note
    if [ ! -f "$env_file" ] && [ -f "$scripts/$PRODUCT-env.example" ]; then
        cp "$scripts/$PRODUCT-env.example" "$env_file"
        config_note="seeded $env_file from example  (EDIT before use)"
    elif [ -f "$env_file" ]; then
        config_note="kept existing $env_file"
    else
        config_note="no $PRODUCT-env.example shipped; create $env_file manually"
    fi

    printf '%s\n' "$CHANNEL" > "$scripts/.bootstrap-channel" 2>/dev/null || true
    printf '%s\n' "$PRODUCT" > "$scripts/.bootstrap-product" 2>/dev/null || true
    sha256sum "$tmp/$file" | cut -d' ' -f1 > "$stamp" 2>/dev/null \
        || warn "could not write $stamp; the next run will download again"

    local status_note="updated to the latest published build"
    [ "$unchanged" -eq 1 ] && status_note="reinstalled the current build (--force)"
    [ "$was_install" -eq 0 ] && status_note="fresh installation"
    log "Installed:"
    log "  product : $PRODUCT"
    log "  channel : $CHANNEL ($channel_src)"
    log "  status  : $status_note"
    log "  scripts : $scripts"
    log "  config  : $config_note"
    log "Next:"
    log "  1. review the config:  $env_file"
    log "  2. check it:           $scripts/$PRODUCT-deploy --show-config"
    log "  3. install services:   sudo cp $scripts/systemd/$PRODUCT.service $scripts/systemd/$PRODUCT-*.service $scripts/systemd/$PRODUCT-*.timer /etc/systemd/system/ && sudo systemctl daemon-reload"
}

# main() is fully parsed before it runs, so re-running this file to update itself
# (tar overwriting app-bootstrap.sh mid-run) is safe.
main "$@"
