#!/usr/bin/bash
# Manage optional features in the Exaba Dashboard config.json, and the
# analytics client switches in analytics.config.json.
#
# Features are read/written under the top-level "features" property of
# config.json. The list of known features is regenerated from
# lib/model/config/feature_control.dart at RPM build time - see
# packaging/build_rpm.sh. The values in the FEATURES=(...) line below
# are a snapshot from the last regeneration; running from the repo
# without a fresh build may show stale values if FeatureControl has
# changed since.
#
# Analytics switches are top-level booleans in a different file,
# analytics.config.json, which sits at the web root rather than under
# assets/assets (analytics.js fetches it itself, so it is not a Flutter
# asset). Its flag set is fixed by that file, not derived from Dart, so it
# is not auto-generated. See docs/analytics.md for what each one does.
#
# The build ships no api_key, so analytics is inert on a fresh install
# whatever the switches say. --api-key sets it; that plus `-e enabled` is
# the whole opt-in.

set -e

# === BEGIN AUTO-GENERATED FEATURES ===
FEATURES=(fail_drives fail_nodes wipe_drives workflows debug_mode)
# === END AUTO-GENERATED FEATURES ===

# The four booleans in analytics.config.json. host is a string, not a toggle:
# shown for context, never written by this script. api_key is also a string but
# has its own option (--api-key), since nothing is collected without it.
ANALYTICS_FLAGS=(enabled analytics error_tracking debug)

# A PostHog public project key. The ingestion endpoint accepts no other form, so
# an obviously wrong paste (a personal `phx_` key) is worth flagging.
API_KEY_PREFIX=phc_

INSTALLED_CONFIG=/usr/share/exaba/dashboard/assets/assets/config.json
DEV_CONFIG=./assets/config.json
INSTALLED_ANALYTICS=/usr/share/exaba/dashboard/analytics.config.json
DEV_ANALYTICS=./web/analytics.config.json

usage() {
  cat <<EOF
Usage: $(basename "$0") [-h] [--dev] [--analytics] [-e NAME | -d NAME]
       [--api-key KEY]

Manage optional features (config.json) and the analytics client switches
(analytics.config.json) for the Exaba Dashboard.

  -h, --help     Show this help.
  -e NAME        Enable NAME.
  -d NAME        Disable NAME.
  --analytics    Make -e/-d act on an analytics switch (a top-level boolean
                 in analytics.config.json) instead of a feature.
  --api-key KEY  Set the analytics api_key in analytics.config.json. Nothing is
                 collected until this is set - a fresh install ships without a
                 key. Pass an empty string to clear it, which makes the client
                 inert again whatever the switches say. Always acts on
                 analytics.config.json, with or without --analytics.
  --dev          Target the in-repo $DEV_CONFIG and
                 $DEV_ANALYTICS instead of the installed
                 copies under /usr/share/exaba/dashboard.

With no -e/-d/--api-key options, prints the current state of both files. The
key itself is never printed, only whether one is set.

Known features (config.json, under "features"):
$(printf '  %s\n' "${FEATURES[@]}")

Known analytics switches (analytics.config.json, top-level):
$(printf '  %s\n' "${ANALYTICS_FLAGS[@]}")

Examples:
  $(basename "$0") -e workflows
  $(basename "$0") --api-key ${API_KEY_PREFIX}xxxxxxxx --analytics -e enabled
  $(basename "$0") --analytics -e enabled
  $(basename "$0") --analytics -d error_tracking
  $(basename "$0") --api-key ''                  # clear the key
EOF
}

DEV=0
ANALYTICS_MODE=0
ENABLE=""
DISABLE=""
# An empty key is a legitimate value (it clears), so "was it given" is its own
# flag rather than an emptiness test on API_KEY.
API_KEY_SET=0
API_KEY=""

while [ $# -gt 0 ]; do
  case "$1" in
    -h|--help) usage; exit 0 ;;
    --dev) DEV=1 ;;
    --analytics) ANALYTICS_MODE=1 ;;
    --api-key)
      shift
      [ $# -eq 0 ] && { echo "Error: --api-key requires a value (pass '' to clear)" >&2; exit 1; }
      API_KEY="$1"
      API_KEY_SET=1
      ;;
    -e)
      shift
      [ -z "${1:-}" ] && { echo "Error: -e requires a name" >&2; exit 1; }
      ENABLE="$1"
      ;;
    -d)
      shift
      [ -z "${1:-}" ] && { echo "Error: -d requires a name" >&2; exit 1; }
      DISABLE="$1"
      ;;
    *) echo "Error: unknown argument: $1" >&2; usage >&2; exit 1 ;;
  esac
  shift
done

if [ -n "$ENABLE" ] && [ -n "$DISABLE" ]; then
  echo "Error: -e and -d are mutually exclusive" >&2
  exit 1
fi

# A key with whitespace in it is a mis-paste (a quoted line, a stray newline),
# and would be written verbatim and then silently rejected by the endpoint.
if [ "$API_KEY_SET" -eq 1 ] && [ -n "$API_KEY" ]; then
  case "$API_KEY" in
    *[[:space:]]*)
      echo "Error: api_key must not contain whitespace" >&2
      exit 1
      ;;
  esac
fi

if [ "$DEV" -eq 1 ]; then
  CONFIG="$DEV_CONFIG"
  ANALYTICS="$DEV_ANALYTICS"
  MISSING_HINT="run from the exaba_dashboard repo root"
else
  CONFIG="$INSTALLED_CONFIG"
  ANALYTICS="$INSTALLED_ANALYTICS"
  MISSING_HINT="exaba-dashboard is not installed as expected"
fi

# Only a write needs its file to exist; printing reports a missing one instead.
TARGET="$CONFIG"
[ "$ANALYTICS_MODE" -eq 1 ] && TARGET="$ANALYTICS"
if [ -n "$ENABLE$DISABLE" ] && [ ! -f "$TARGET" ]; then
  echo "Error: $TARGET not found - $MISSING_HINT" >&2
  exit 1
fi
# --api-key always writes the analytics file, whatever -e/-d were aimed at.
if [ "$API_KEY_SET" -eq 1 ] && [ ! -f "$ANALYTICS" ]; then
  echo "Error: $ANALYTICS not found - $MISSING_HINT" >&2
  exit 1
fi

is_known() {
  local name="$1" known=("${FEATURES[@]}") k
  [ "$ANALYTICS_MODE" -eq 1 ] && known=("${ANALYTICS_FLAGS[@]}")
  for k in "${known[@]}"; do
    [ "$k" = "$name" ] && return 0
  done
  return 1
}

for name in "$ENABLE" "$DISABLE"; do
  if [ -n "$name" ] && ! is_known "$name"; then
    if [ "$ANALYTICS_MODE" -eq 1 ]; then
      echo "Error: unknown analytics switch '$name'" >&2
      echo "Known analytics switches: ${ANALYTICS_FLAGS[*]}" >&2
    else
      echo "Error: unknown feature '$name'" >&2
      echo "Known features: ${FEATURES[*]}" >&2
    fi
    exit 1
  fi
done

TMP=""
cleanup() {
  [ -n "$TMP" ] && rm -f "$TMP"
  return 0
}
trap cleanup EXIT

# jq to a temp file, then write the bytes back through the original path.
# `mv` would give the config mktemp's 0600 and the caller's ownership, which
# for a web-served file means the httpd user can no longer read it.
write_back() {
  local file="$1"
  cat "$TMP" > "$file"
  rm -f "$TMP"
  TMP=""
}

apply_feature() {
  local key="$1" value="$2"
  TMP=$(mktemp)
  jq --arg key "$key" --argjson value "$value" \
     '.features = (.features // {}) | .features[$key] = $value' \
     "$CONFIG" > "$TMP"
  write_back "$CONFIG"
}

apply_analytics() {
  local key="$1" value="$2"
  TMP=$(mktemp)
  jq --arg key "$key" --argjson value "$value" \
     '.[$key] = $value' "$ANALYTICS" > "$TMP"
  write_back "$ANALYTICS"
  [ "$value" = true ] && warn_if_inert "$key"
  return 0
}

# --arg, so the key is written as a JSON string and never interpreted.
apply_api_key() {
  local value="$1"
  TMP=$(mktemp)
  jq --arg value "$value" '.api_key = $value' "$ANALYTICS" > "$TMP"
  write_back "$ANALYTICS"
  if [ -z "$value" ]; then
    echo "api_key cleared - analytics is now inert regardless of the switches."
    return 0
  fi
  case "$value" in
    "$API_KEY_PREFIX"*) ;;
    *) echo "Warning: api_key does not start with '$API_KEY_PREFIX' - the" \
            "ingestion endpoint expects a public project key." >&2 ;;
  esac
  return 0
}

# The key as analytics.js sees it: a non-blank *string*, else nothing at all.
# Reporting a hand-edited `"   "` or a number as "set" would describe a config
# the client treats as keyless (and so dormant).
effective_api_key() {
  jq -r 'if (.api_key | type) == "string"
         then (.api_key | gsub("^\\s+|\\s+$"; ""))
         else "" end' "$ANALYTICS"
}

# An enabled switch does nothing unless the master gate is open and a key is
# present (see docs/analytics.md), so say so rather than let it look applied.
warn_if_inert() {
  local key="$1" api_key enabled
  api_key=$(effective_api_key)
  enabled=$(jq -r 'if has("enabled") then .enabled else false end' "$ANALYTICS")
  if [ -z "$api_key" ]; then
    echo "Warning: api_key is empty - analytics stays dormant regardless of these switches." >&2
  fi
  if [ "$key" != "enabled" ] && [ "$enabled" != "true" ]; then
    echo "Warning: enabled is false - '$key' has no effect until analytics is enabled." >&2
  fi
}

# The key goes first, so warn_if_inert below sees the key this run just set
# rather than warning about the one it replaced.
[ "$API_KEY_SET" -eq 1 ] && apply_api_key "$API_KEY"

if [ "$ANALYTICS_MODE" -eq 1 ]; then
  [ -n "$ENABLE" ]  && apply_analytics "$ENABLE"  true
  [ -n "$DISABLE" ] && apply_analytics "$DISABLE" false
else
  [ -n "$ENABLE" ]  && apply_feature "$ENABLE"  true
  [ -n "$DISABLE" ] && apply_feature "$DISABLE" false
fi

# A key alone collects nothing; read the file back so this reflects any -e in
# the same invocation.
if [ "$API_KEY_SET" -eq 1 ] && [ -n "$API_KEY" ]; then
  if [ "$(jq -r 'if has("enabled") then .enabled else false end' "$ANALYTICS")" != "true" ]; then
    echo "Warning: enabled is false - nothing is collected until you run" \
         "'--analytics -e enabled'." >&2
  fi
fi

print_features() {
  if [ ! -f "$CONFIG" ]; then
    echo "Features: $CONFIG not found (skipped) - $MISSING_HINT"
    return 0
  fi
  echo "Features in $CONFIG:"
  local f val
  for f in "${FEATURES[@]}"; do
    val=$(jq -r --arg key "$f" '.features[$key] // false' "$CONFIG")
    printf '  %-20s %s\n' "$f" "$val"
  done
}

# The per-stream toggles default to true when absent, the gates to false.
default_for() {
  case "$1" in
    analytics|error_tracking) echo true ;;
    *) echo false ;;
  esac
}

print_analytics() {
  if [ ! -f "$ANALYTICS" ]; then
    echo "Analytics: $ANALYTICS not found (skipped) - $MISSING_HINT"
    return 0
  fi
  echo "Analytics in $ANALYTICS:"
  local f val host api_key
  for f in "${ANALYTICS_FLAGS[@]}"; do
    val=$(jq -r --arg key "$f" --argjson def "$(default_for "$f")" \
      'if has($key) then .[$key] else $def end' "$ANALYTICS")
    printf '  %-20s %s\n' "$f" "$val"
  done
  api_key=$(effective_api_key)
  host=$(jq -r '.host // ""' "$ANALYTICS")
  printf '  %-20s %s\n' "api_key" "$([ -n "$api_key" ] && echo set || echo '(empty)')"
  printf '  %-20s %s\n' "host" "${host:-(unset)}"
}

print_features
echo
print_analytics
