#!/usr/bin/env bash
set -Eeuo pipefail
readonly DEFAULT_BASE_URL="https://api.bobrouter.com/v1"
readonly DEFAULT_MODEL="gpt-5.6-sol"
mode=configure target_kind= target_value= model="$DEFAULT_MODEL" key_env=BOBROUTER_API_KEY apply=false assume_yes=false backup_dir=
usage() { cat <<'EOF'
Safely configure an explicit Hermes profile/home for BobRouter.
Usage: configure-bobrouter.sh [configure|rollback|uninstall] (--profile NAME|--hermes-home DIR) [options]
Options: --model ID --key-env NAME --backup DIR --apply --yes
EOF
}
die() { printf 'Error: %s\n' "$*" >&2; exit 1; }
info() { printf '%s\n' "$*" >&2; }
quote_cmd() { printf '  '; printf '%q ' "$@"; printf '\n'; }
while (($#)); do case "$1" in
  configure|rollback|uninstall) mode=$1; shift;;
  --profile) (($#>=2))||die '--profile requires a value'; [[ -z $target_kind ]]||die 'choose one target'; target_kind=profile; target_value=$2; shift 2;;
  --hermes-home) (($#>=2))||die '--hermes-home requires a value'; [[ -z $target_kind ]]||die 'choose one target'; target_kind=home; target_value=$2; shift 2;;
  --model) (($#>=2))||die '--model requires a value'; model=$2; shift 2;;
  --key-env) (($#>=2))||die '--key-env requires a value'; key_env=$2; shift 2;;
  --backup) (($#>=2))||die '--backup requires a value'; backup_dir=$2; shift 2;;
  --apply) apply=true; shift;; --yes) assume_yes=true; shift;;
  -h|--help) usage; exit 0;; *) die "unknown argument: $1";; esac; done
command -v hermes >/dev/null || die 'hermes is not on PATH'
[[ -n $target_kind ]] || die 'an explicit --profile or --hermes-home is required'
[[ $assume_yes == false || $apply == true ]] || die '--yes requires --apply'
[[ $key_env =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die 'invalid --key-env name'
if [[ $target_kind == profile ]]; then
  [[ $target_value =~ ^[a-z0-9][a-z0-9_-]*$ ]] || die 'invalid profile name'
  hermes_cmd=(env -u HERMES_HOME hermes --profile "$target_value")
else
  [[ $target_value == /* && -d $target_value ]] || die 'Hermes home must be an existing absolute directory'
  hermes_cmd=(env HERMES_HOME="$target_value" hermes)
fi
config_path="$("${hermes_cmd[@]}" config path)" || die 'could not resolve target config path'
env_path="$("${hermes_cmd[@]}" config env-path)" || die 'could not resolve target .env path'
reject_symlinks() {
  local path=$1 part current=/; [[ $path == /* ]] || die "path is not absolute: $path"
  IFS=/ read -ra parts <<< "${path#/}"
  for part in "${parts[@]}"; do [[ -z $part ]] && continue; current="${current%/}/$part"; [[ ! -L $current ]] || die "symlink path component is not allowed: $current"; done
}
reject_symlinks "$config_path"; reject_symlinks "$env_path"
target_home="$(dirname "$config_path")"; reject_symlinks "$target_home"
target_home="$(cd "$target_home" && pwd -P)"; config_path="$target_home/config.yaml"; env_path="$target_home/.env"
[[ ! -L $config_path && ! -L $env_path ]] || die 'config or .env symlink is not allowed'
[[ $(stat -c %u "$target_home") == "$(id -u)" ]] || die 'target home is not owned by current user'
modebits=$(stat -c %a "$target_home"); [[ ${modebits: -1} < 2 && ${modebits: -2:1} < 2 ]] || die 'target home is group/other writable'
exec 9>"$target_home/.bobrouter.lock"; flock -n 9 || die 'target Hermes home is busy'
managed_file="$target_home/.bobrouter-managed"
confirm() { [[ $assume_yes == true ]] && return; [[ -t 0 ]] || die 'confirmation requires a terminal; pass --yes'; read -r -p "$1 [y/N] " answer; [[ $answer == y || $answer == Y ]] || die cancelled; }
atomic_copy() { local source=$1 destination=$2 tmp; [[ ! -L $destination ]] || die "refusing symlink destination: $destination"; tmp=$(mktemp "$target_home/.bobrouter.tmp.XXXXXX"); cp -p -- "$source" "$tmp"; chmod 600 "$tmp"; mv -f -- "$tmp" "$destination"; }
remove_file() { [[ ! -L $1 ]] || die "refusing symlink removal: $1"; rm -f -- "$1"; }
make_backup() {
  local root stamp; root="${XDG_STATE_HOME:-$HOME/.local/state}/bobrouter-hermes/backups"; mkdir -p "$root"; stamp="$(date -u +%Y%m%dT%H%M%SZ)-$$"; backup_dir="$root/$stamp"; (umask 077; mkdir "$backup_dir") || die 'could not create exclusive backup'
  [[ ! -L $backup_dir ]] || die 'backup directory is a symlink'
  if [[ -f $config_path ]]; then [[ ! -L $config_path ]]||die 'config is a symlink'; cp -p -- "$config_path" "$backup_dir/config.yaml"; : >"$backup_dir/config.existed"; fi
  if [[ -f $env_path ]]; then [[ ! -L $env_path ]]||die '.env is a symlink'; cp -p -- "$env_path" "$backup_dir/.env"; chmod 600 "$backup_dir/.env"; : >"$backup_dir/env.existed"; fi
  { printf 'format=2\ntarget_home=%s\nconfig_path=%s\nenv_path=%s\n' "$target_home" "$config_path" "$env_path"; } >"$backup_dir/metadata"; chmod 600 "$backup_dir/metadata"; chmod go-rwx "$backup_dir"
}
check_backup() { local h c e; [[ -f $backup_dir/metadata ]] || die 'invalid backup: metadata missing'; h=$(sed -n 's/^target_home=//p' "$backup_dir/metadata"); c=$(sed -n 's/^config_path=//p' "$backup_dir/metadata"); e=$(sed -n 's/^env_path=//p' "$backup_dir/metadata"); [[ $h == "$target_home" && $c == "$config_path" && $e == "$env_path" ]] || die 'backup does not belong to target'; }
restore_backup() { check_backup; if [[ -f $backup_dir/config.existed ]]; then [[ -f $backup_dir/config.yaml ]]||die 'backup config missing'; atomic_copy "$backup_dir/config.yaml" "$config_path"; else remove_file "$config_path"; fi; if [[ -f $backup_dir/env.existed ]]; then [[ -f $backup_dir/.env ]]||die 'backup .env missing'; atomic_copy "$backup_dir/.env" "$env_path"; else remove_file "$env_path"; fi; }
write_marker() { local tmp; tmp=$(mktemp "$target_home/.bobrouter-marker.XXXXXX"); printf 'format=1\nmodel=%s\nbase_url=%s\nprovider=custom\nkey=OPENAI_API_KEY\n' "$model" "$DEFAULT_BASE_URL" >"$tmp"; chmod 600 "$tmp"; mv -f "$tmp" "$managed_file"; }
info "Target Hermes home: $target_home"; info "Config path: $config_path"; info "Secret path: $env_path"
if [[ $mode == rollback ]]; then
  [[ -n $backup_dir ]] || die 'rollback requires --backup DIR'; backup_dir=$(cd "$backup_dir" 2>/dev/null && pwd -P)||die 'backup directory not found'; [[ $apply == true ]] || { info 'Dry run only.'; exit 0; }; confirm 'Restore this backup?'; selected=$backup_dir; make_backup; safety=$backup_dir; backup_dir=$selected
  if ! restore_backup || ! "${hermes_cmd[@]}" config check; then backup_dir=$safety; restore_backup || die "safety restore failed: $safety"; die "selected backup failed validation; restored safety backup: $safety"; fi
  info 'Rollback complete.'; exit 0
fi
if [[ $mode == uninstall ]]; then
  [[ -z $backup_dir ]] || die '--backup is valid only with rollback'; [[ -f $managed_file && ! -L $managed_file ]] || die 'no BobRouter ownership marker found'; [[ $apply == true ]] || { info 'Dry run only. Uninstall would remove managed values.'; exit 0; }; confirm 'Remove BobRouter-owned values?'; make_backup
  # The marker is the ownership proof; unset only values still equal to our marker.
  expected_model=$(sed -n 's/^model=//p' "$managed_file"); expected_base=$(sed -n 's/^base_url=//p' "$managed_file"); [[ $("${hermes_cmd[@]}" config get model.default 2>/dev/null || true) == "$expected_model" ]] && "${hermes_cmd[@]}" config unset model.default || true
  [[ $("${hermes_cmd[@]}" config get model.provider 2>/dev/null || true) == custom ]] && "${hermes_cmd[@]}" config unset model.provider || true
  [[ $("${hermes_cmd[@]}" config get model.base_url 2>/dev/null || true) == "$expected_base" ]] && "${hermes_cmd[@]}" config unset model.base_url || true
  if [[ -f $env_path && ! -L $env_path ]]; then tmp=$(mktemp "$target_home/.bobrouter.env.XXXXXX"); awk '!/^OPENAI_API_KEY=/' "$env_path" >"$tmp"; chmod 600 "$tmp"; mv -f "$tmp" "$env_path"; fi
  remove_file "$managed_file"; "${hermes_cmd[@]}" config check; info "Uninstall complete. Backup: $backup_dir"; exit 0
fi
[[ -z $backup_dir ]] || die '--backup is valid only with rollback'; info "Model: $model"; info 'Secret source: environment variable (value will not be printed)'; quote_cmd "${hermes_cmd[@]}" config set model.default "$model"; quote_cmd "${hermes_cmd[@]}" config set model.provider custom; quote_cmd "${hermes_cmd[@]}" config set model.base_url "$DEFAULT_BASE_URL"; info 'Planned secret command: <redacted>'
[[ $apply == true ]] || { info 'Dry run only.'; exit 0; }
api_key="${!key_env-}"; [[ -n $api_key ]] || die "$key_env is empty or unset"; [[ $api_key != *$'\n'* && $api_key != *$'\r'* ]] || die "$key_env contains a newline"; confirm 'Back up and configure?'; make_backup; info "Backup created: $backup_dir"
rollback_on_error() { local status=$?; trap - ERR; info 'Configuration failed; restoring backup.'; restore_backup || info "Automatic restore failed; use rollback: $backup_dir"; exit "$status"; }; trap rollback_on_error ERR
"${hermes_cmd[@]}" config set model.default "$model"; "${hermes_cmd[@]}" config set model.provider custom; "${hermes_cmd[@]}" config set model.base_url "$DEFAULT_BASE_URL"; "${hermes_cmd[@]}" config set OPENAI_API_KEY "$api_key"; unset api_key; chmod 600 "$env_path"; "${hermes_cmd[@]}" config check; write_marker; trap - ERR
info "Configuration complete. Backup: $backup_dir"; info 'The Hermes CLI currently accepts config secrets only as argv values; same-user process inspection can briefly observe the key. No stdin secret-input option was available during verification.'
