#!/usr/bin/env bash
#
# Managello self-hosted installer — served from https://get.managello.com
#   curl -sSL https://get.managello.com | bash
#
# The one-liner is UX; the licensing gate is WHAT it is allowed to pull (BUILD_PLAN §3).
# This script:
#   1. collects a LICENSE_KEY,
#   2. mints a stable install_uuid,
#   3. bootstraps against the license server (POST /api/v1/bootstrap),
#   4. docker-logins the PRIVATE registry with the returned short-lived credential and
#      pulls the exact image DIGEST it was handed — a revoked/expired license gets 403
#      here and never receives a credential (Layer 1),
#   5. writes .env (incl. the baked Ed25519 public key) + the stack compose, brings it up.
#
# It is intentionally idempotent-ish and fails loudly. No secret is ever echoed.
set -euo pipefail

# --- configuration (overridable by env) -------------------------------------------------
LICENSE_SERVER_URL="${LICENSE_SERVER_URL:-https://license.managello.com}"
ASSETS_URL="${ASSETS_URL:-https://get.managello.com/stack}"   # published compose template
INSTALL_DIR="${INSTALL_DIR:-/opt/managello}"
LICENSE_KEY="${LICENSE_KEY:-}"

# --- tiny helpers -----------------------------------------------------------------------
log()  { printf '\033[1;36m▶\033[0m %s\n' "$*"; }
ok()   { printf '\033[1;32m✔\033[0m %s\n' "$*"; }
die()  { printf '\033[1;31m✖\033[0m %s\n' "$*" >&2; exit 1; }

# Extract a (possibly nested, dot-path) field from a JSON blob on stdin. Prefers jq, then
# python3 — both are near-universal on servers. We never pass secrets on the command line.
json_get() {
    local path="$1" body="$2"
    if command -v jq >/dev/null 2>&1; then
        printf '%s' "$body" | jq -r --arg p "$path" '
            reduce ($p / ".")[] as $k (.; if . == null then null else .[$k] end)
            | if . == null then "" else . end'
    elif command -v python3 >/dev/null 2>&1; then
        printf '%s' "$body" | PYPATH="$path" python3 -c '
import json,os,sys
d=json.load(sys.stdin)
for k in os.environ["PYPATH"].split("."):
    d = (d or {}).get(k) if isinstance(d,dict) else None
print("" if d is None else d)'
    else
        die "need jq or python3 to parse the bootstrap response — please install one and re-run"
    fi
}

# --- preflight --------------------------------------------------------------------------
command -v docker >/dev/null 2>&1 || die "Docker is required. Install Docker first: https://docs.docker.com/engine/install/"
docker compose version >/dev/null 2>&1 || die "Docker Compose v2 is required (docker compose)."
command -v curl >/dev/null 2>&1 || die "curl is required."

# --- collect the license key ------------------------------------------------------------
if [ -z "$LICENSE_KEY" ]; then
    if [ -t 0 ]; then
        printf 'Enter your Managello license key (MNGL-XXXX-XXXX-XXXX): '
        read -r LICENSE_KEY
    else
        die "No license key. Re-run with:  LICENSE_KEY=MNGL-... bash <(curl -sSL $LICENSE_SERVER_URL/get.sh)"
    fi
fi
[ -n "$LICENSE_KEY" ] || die "A license key is required."

# --- stable install fingerprint (generated ONCE, reused across updates) -----------------
mkdir -p "$INSTALL_DIR"
UUID_FILE="$INSTALL_DIR/.install_uuid"
if [ -s "$UUID_FILE" ]; then
    INSTALL_UUID="$(cat "$UUID_FILE")"
else
    if command -v uuidgen >/dev/null 2>&1; then
        INSTALL_UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
    else
        INSTALL_UUID="$(cat /proc/sys/kernel/random/uuid)"
    fi
    printf '%s' "$INSTALL_UUID" > "$UUID_FILE"
fi
ok "install fingerprint ready"

# --- bootstrap against the license server -----------------------------------------------
log "validating license + requesting a pull credential…"
BOOT_BODY="$(printf '{"license_key":"%s","install_uuid":"%s"}' "$LICENSE_KEY" "$INSTALL_UUID")"
HTTP_CODE="$(curl -sS -o /tmp/mngl_boot.json -w '%{http_code}' \
    -H 'Content-Type: application/json' -H 'Accept: application/json' \
    -X POST "$LICENSE_SERVER_URL/api/v1/bootstrap" -d "$BOOT_BODY" || echo 000)"
BOOT_JSON="$(cat /tmp/mngl_boot.json 2>/dev/null || true)"; rm -f /tmp/mngl_boot.json

case "$HTTP_CODE" in
    200) ok "license valid — credential issued" ;;
    403) die "license invalid, expired, or revoked (HTTP 403). Contact your Managello provider." ;;
    000) die "could not reach the license server at $LICENSE_SERVER_URL" ;;
    *)   die "bootstrap failed (HTTP $HTTP_CODE)." ;;
esac

IMAGE_REF="$(json_get image_ref "$BOOT_JSON")"
REG_HOST="$(json_get registry_credential.registry "$BOOT_JSON")"
REG_USER="$(json_get registry_credential.username "$BOOT_JSON")"
REG_PASS="$(json_get registry_credential.password "$BOOT_JSON")"
HMAC_SECRET="$(json_get hmac_secret "$BOOT_JSON")"
SIGNING_PUBLIC="$(json_get signing_public_key "$BOOT_JSON")"

[ -n "$IMAGE_REF" ] || die "license server did not return an image to install (no published release yet?)."
[ -n "$REG_PASS" ] || die "license server did not return a registry credential (registry not configured server-side)."

# --- credentialed pull of the exact digest (Layer 1) ------------------------------------
log "authenticating to the private registry…"
printf '%s' "$REG_PASS" | docker login "$REG_HOST" -u "$REG_USER" --password-stdin >/dev/null \
    || die "registry login failed."
log "pulling $IMAGE_REF …"
docker pull "$IMAGE_REF" >/dev/null || die "image pull failed."
docker logout "$REG_HOST" >/dev/null 2>&1 || true
ok "image pulled"

# --- write .env + stack, then bring it up -----------------------------------------------
log "writing configuration to $INSTALL_DIR …"
if [ ! -f "$INSTALL_DIR/.env" ]; then
    APP_KEY="base64:$(head -c 32 /dev/urandom | base64)"
    DB_PASSWORD="$(head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)"
    umask 077
    cat > "$INSTALL_DIR/.env" <<ENV
APP_KEY=$APP_KEY
APP_URL=http://localhost
MANAGELLO_IMAGE=$IMAGE_REF
LICENSE_KEY=$LICENSE_KEY
LICENSE_SERVER_URL=$LICENSE_SERVER_URL
LICENSE_SIGNING_PUBLIC=$SIGNING_PUBLIC
INSTALL_UUID=$INSTALL_UUID
LICENSE_BIND_SECRET=$HMAC_SECRET
DB_PASSWORD=$DB_PASSWORD
ENV
    ok ".env written (APP_KEY + DB password generated)"
else
    # Existing install (e.g. re-run/update): just point at the new digest.
    if grep -q '^MANAGELLO_IMAGE=' "$INSTALL_DIR/.env"; then
        sed -i "s#^MANAGELLO_IMAGE=.*#MANAGELLO_IMAGE=$IMAGE_REF#" "$INSTALL_DIR/.env"
    else
        printf 'MANAGELLO_IMAGE=%s\n' "$IMAGE_REF" >> "$INSTALL_DIR/.env"
    fi
    ok ".env preserved; image pinned to $IMAGE_REF"
fi

log "fetching the stack compose…"
curl -fsSL "$ASSETS_URL/docker-compose.yml" -o "$INSTALL_DIR/docker-compose.yml" \
    || die "could not fetch the stack compose from $ASSETS_URL/docker-compose.yml"

log "starting Managello…"
( cd "$INSTALL_DIR" && docker compose up -d )
# First-run app bootstrap (idempotent): key is already set; run migrations + storage link.
( cd "$INSTALL_DIR" && docker compose exec -T app php artisan migrate --force ) || true
( cd "$INSTALL_DIR" && docker compose exec -T app php artisan storage:link ) >/dev/null 2>&1 || true

ok "Managello is up. Open your APP_URL and complete the setup wizard."
printf '\nInstall dir: %s\nManage with: cd %s && docker compose ...\n' "$INSTALL_DIR" "$INSTALL_DIR"
