#!/usr/bin/env bash

set -eo pipefail

repo_root="$(cd "$(dirname "$0")/.." && pwd -P)"
vps_host="${PI_AGI_VPS_HOST:-kortix-prod}"
remote_root="${PI_AGI_VPS_ROOT:-/root/pi-agi-vps-test}"
project="${PI_AGI_SANDBOX_PROJECT:-pi-agi-mission-prod}"
tmux_session="${PI_AGI_SANDBOX_TMUX_SESSION:-agi-open-mission-prod}"
configured_agent_dir="${PI_AGI_SANDBOX_AGENT_DIR:-}"
source_archive="${PI_AGI_VPS_SOURCE_ARCHIVE:-}"
release_keep="${PI_AGI_VPS_RELEASE_KEEP:-6}"
ssh_args=(-o BatchMode=yes)

die() {
	printf 'pi-agi VPS sandbox: %s\n' "$*" >&2
	exit 1
}

usage() {
	cat <<'EOF'
Usage: npm run sandbox:mission -- <command> [argument]

Default target: the SSH host alias `kortix-prod`.

Commands:
  preflight          Upload this checkout and run the isolated Docker checks.
  start              Upload source + dedicated model config and start the TUI.
  run                Submit the ordinary user mission to the remote TUI.
  attach             Attach interactively to the remote tmux TUI.
  status             Show remote container, process, goal, wake, and trace state.
  monitor [seconds]  Monitor remotely until the goal reports met.
  repair-proxy       Sync and repair only the proxy; preserve agent and TUI.
  nudge [message]    Continue an active idle goal after infrastructure recovery.
  recover-session [message]
                     Fresh Pi conversation; preserve remote goal and workspace.
  capture            Print the complete remote TUI scrollback.
  export [dir]       Export the Docker workspace to a directory on the VPS.
  stop               Stop only the exact owned remote Compose project.
  sync               Upload this checkout without starting containers.

Required for start:
  PI_AGI_SANDBOX_AGENT_DIR=/absolute/path/to/a/Pi-config-source

The VPS uploader copies only auth.json, models.json, and settings.json. It never
uploads extensions, browser data, trust/history, run history, or package caches.

Optional:
  PI_AGI_VPS_HOST              SSH alias (default kortix-prod)
  PI_AGI_VPS_ROOT              VPS directory (default /root/pi-agi-vps-test)
  PI_AGI_VPS_SOURCE_ARCHIVE    Absolute path to an npm package tarball to deploy
                               instead of packing the checkout again
  PI_AGI_SANDBOX_PROJECT       Compose project (default pi-agi-mission-prod)
  PI_AGI_SANDBOX_TMUX_SESSION  TUI session (default agi-open-mission-prod)
  PI_AGI_VPS_RELEASE_KEEP      Source releases retained after sync (default 6)

Use `npm run sandbox:mission:local -- ...` for the optional local Docker path.
EOF
}

require_command() {
	command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

validate_inputs() {
	[[ "$vps_host" =~ ^[A-Za-z0-9._-]+$ ]] || die "unsafe VPS host alias: $vps_host"
	[[ "$remote_root" =~ ^/root/[A-Za-z0-9._/-]+$ && "$remote_root" != "/root/" ]] ||
		die "unsafe VPS root: $remote_root"
	[[ "$project" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || die "unsafe Compose project: $project"
	[[ "$tmux_session" =~ ^[A-Za-z0-9_.-]+$ ]] || die "unsafe tmux session: $tmux_session"
	[[ "$release_keep" =~ ^[1-9][0-9]?$ && "$release_keep" -le 50 ]] ||
		die "PI_AGI_VPS_RELEASE_KEEP must be an integer from 1 to 50"
	if [[ -n "$source_archive" ]]; then
		[[ "$source_archive" = /* ]] || die "PI_AGI_VPS_SOURCE_ARCHIVE must be an absolute path"
		[[ -f "$source_archive" ]] || die "source archive does not exist: $source_archive"
		local archive_entries
		archive_entries="$(tar -tzf "$source_archive")" || die "source archive is not a readable tarball"
		grep -Fqx 'package/package.json' <<<"$archive_entries" || die "source archive is missing package/package.json"
		grep -Fqx 'package/src/index.ts' <<<"$archive_entries" || die "source archive is missing package/src/index.ts"
		if grep -Eq '(^|/)\.\.(/|$)|^/' <<<"$archive_entries"; then
			die "source archive contains an unsafe path"
		fi
	fi
}

remote() {
	ssh "${ssh_args[@]}" "$vps_host" "$@"
}

ensure_remote_dependencies() {
	if remote 'docker compose version >/dev/null 2>&1 && command -v jq >/dev/null && command -v curl >/dev/null'; then
		return
	fi
	printf 'Installing VPS runner prerequisites on %s...\n' "$vps_host"
	remote 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-compose-v2 jq curl'
	remote 'docker compose version >/dev/null 2>&1 && command -v jq >/dev/null && command -v curl >/dev/null' ||
		die "VPS runner prerequisites are unavailable on $vps_host"
}

deploy_source() {
	local release_id remote_release
	release_id="$(date -u +%Y%m%dT%H%M%SZ)-$$"
	remote_release="$remote_root/releases/$release_id"
	remote "install -d -m 0700 '$remote_root' '$remote_root/releases' '$remote_release'"
	if [[ -n "$source_archive" ]]; then
		remote "tar -xzf - --strip-components=1 -C '$remote_release'" <"$source_archive"
	else
		tar -C "$repo_root" \
			--exclude=.git \
			--exclude=node_modules \
			--exclude=.pi \
			--exclude=sessions \
			--exclude='*.tgz' \
			-czf - . | remote "tar -xzf - -C '$remote_release'"
	fi
	remote "ln -sfn '$remote_release' '$remote_root/current'"
	prune_remote_releases
	printf 'Uploaded %s to %s:%s\n' "${source_archive:-source checkout}" "$vps_host" "$remote_release"
}

prune_remote_releases() {
	# Releases are immutable upload directories. Keep a bounded recent set so
	# repeated test iteration cannot fill the VPS. Resolve and protect `current`,
	# then validate every deletion candidate's exact parent and generated basename.
	remote "set -eu
		releases='$remote_root/releases'
		current=\$(readlink -f '$remote_root/current')
		find \"\$releases\" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\\n' |
			sort -nr | awk 'NR > $release_keep { print \$2 }' |
		while IFS= read -r candidate; do
			[ -n \"\$candidate\" ] || continue
			[ \"\$candidate\" != \"\$current\" ] || continue
			parent=\$(dirname -- \"\$candidate\")
			base=\$(basename -- \"\$candidate\")
			[ \"\$parent\" = \"\$releases\" ] || { echo \"refusing release prune outside \$releases: \$candidate\" >&2; exit 1; }
			printf '%s\\n' \"\$base\" | grep -Eq '^[0-9]{8}T[0-9]{6}Z-[0-9]+$' || { echo \"refusing unexpected release name: \$candidate\" >&2; exit 1; }
			rm -rf -- \"\$candidate\"
		done"
}

require_model_config_source() {
	[[ -n "$configured_agent_dir" ]] || die "set PI_AGI_SANDBOX_AGENT_DIR to a Pi model config source"
	[[ -d "$configured_agent_dir" ]] || die "agent config directory does not exist: $configured_agent_dir"
	[[ -f "$configured_agent_dir/auth.json" || -f "$configured_agent_dir/models.json" ]] ||
		die "Pi config source has neither auth.json nor models.json"
}

upload_agent_config() {
	require_model_config_source
	local resolved remote_config file
	local config_files=()
	resolved="$(cd "$configured_agent_dir" && pwd -P)"
	for file in auth.json models.json settings.json; do
		[[ -f "$resolved/$file" ]] && config_files+=("$file")
	done
	remote_config="$remote_root/agent-config-private"
	remote "install -d -m 0700 '$remote_config'"
	tar -C "$resolved" -czf - "${config_files[@]}" |
		remote "tar -xzf - -C '$remote_config' && chmod -R go-rwx '$remote_config'"
	printf 'Uploaded only auth.json/models.json/settings.json over SSH to the private release directory.\n'
}

require_remote_release() {
	remote "test -d '$remote_root/current'" || die "no deployed VPS release; run preflight, start, or sync first"
}

remote_runner() {
	local action="$1"
	shift
	local quoted_action quoted_args argument quoted_argument
	printf -v quoted_action '%q' "$action"
	quoted_args=""
	for argument in "$@"; do
		printf -v quoted_argument '%q' "$argument"
		quoted_args+=" $quoted_argument"
	done
	remote "cd '$remote_root/current' && PI_AGI_SANDBOX_AGENT_DIR='$remote_root/agent-config-private' PI_AGI_SANDBOX_PROJECT='$project' PI_AGI_SANDBOX_TMUX_SESSION='$tmux_session' bash scripts/agi-sandbox.sh $quoted_action$quoted_args"
}

preflight() {
	ensure_remote_dependencies
	deploy_source
	remote_runner preflight
}

start() {
	ensure_remote_dependencies
	deploy_source
	upload_agent_config
	remote_runner start
}

attach() {
	require_remote_release
	exec ssh -t "${ssh_args[@]}" "$vps_host" \
		"cd '$remote_root/current' && PI_AGI_SANDBOX_AGENT_DIR='$remote_root/agent-config-private' PI_AGI_SANDBOX_PROJECT='$project' PI_AGI_SANDBOX_TMUX_SESSION='$tmux_session' bash scripts/agi-sandbox.sh attach"
}

sync_only() {
	ensure_remote_dependencies
	deploy_source
}

repair_proxy() {
	ensure_remote_dependencies
	deploy_source
	if [[ -n "$configured_agent_dir" ]]; then
		upload_agent_config
	else
		remote "test -d '$remote_root/agent-config-private'" ||
			die "persistent VPS model config is missing; set PI_AGI_SANDBOX_AGENT_DIR"
	fi
	remote_runner repair-proxy
}

require_command ssh
require_command tar
validate_inputs

action="${1:-help}"
if [[ $# -gt 0 ]]; then shift; fi

case "$action" in
	preflight) preflight ;;
	start) start ;;
	attach) attach ;;
	run | status | capture | stop)
		require_remote_release
		remote_runner "$action"
		;;
	monitor | export | nudge | recover-session)
		require_remote_release
		remote_runner "$action" "${1:-}"
		;;
	repair-proxy) repair_proxy ;;
	sync) sync_only ;;
	help | -h | --help) usage ;;
	*) usage; die "unknown command: $action" ;;
esac
