#!/bin/sh
set -eu

INSTALLER_VERSION="v1"
DOWNLOAD_ROOT="https://downloads.runku.dev/latest"
CONFIG_DIR="/etc/runku"
CONFIG_PATH="$CONFIG_DIR/config.yaml"
ENV_PATH="$CONFIG_DIR/runku.env"
DATA_DIR="/var/lib/runku"
BIN_PATH="/usr/local/bin/runku"

say() { printf '\n%s\n' "$*"; }
die() { printf 'Error: %s\n' "$*" >&2; exit 1; }
prompt() {
  label=$1
  default=${2-}
  if [ -n "$default" ]; then printf '%s [%s]: ' "$label" "$default" > /dev/tty; else printf '%s: ' "$label" > /dev/tty; fi
  IFS= read -r answer < /dev/tty || die "interactive terminal required"
  if [ -z "$answer" ]; then answer=$default; fi
  printf '%s' "$answer"
}
secret_prompt() {
  label=$1
  printf '%s: ' "$label" > /dev/tty
  stty -echo < /dev/tty
  IFS= read -r answer < /dev/tty || { stty echo < /dev/tty; die "interactive terminal required"; }
  stty echo < /dev/tty
  printf '\n' > /dev/tty
  printf '%s' "$answer"
}
confirm() {
  answer=$(prompt "$1 (y/N)" "n")
  [ "$answer" = "y" ] || [ "$answer" = "Y" ]
}
yaml_quote() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/''/g")"; }
env_quote() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
require() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; }
as_root() {
  if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi
}

[ -r /dev/tty ] || die "run this installer from an interactive terminal"
require curl
require tar
require sed
require awk
require mktemp

OS=$(uname -s)
ARCH=$(uname -m)
case "$OS" in
  Linux) artifact_os="linux" ;;
  Darwin) artifact_os="darwin" ;;
  *) die "unsupported operating system: $OS (use install.ps1 on Windows)" ;;
esac
case "$ARCH" in
  x86_64|amd64) artifact_arch="amd64" ;;
  arm64|aarch64) artifact_arch="arm64" ;;
  *) die "unsupported architecture: $ARCH" ;;
esac

artifact="runku_${artifact_os}_${artifact_arch}.tar.gz"
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/runku-install.XXXXXX")
trap 'rm -rf "$tmp_dir"' EXIT HUP INT TERM

say "Runku MCP Forge quick install ($INSTALLER_VERSION)"
printf 'Detected: %s %s\n' "$OS" "$ARCH"
printf 'This wizard installs the stable binary, creates configuration and registers a background service.\n'

say "1/6 Download and verify"
curl --fail --location --silent --show-error "$DOWNLOAD_ROOT/$artifact" -o "$tmp_dir/$artifact"
curl --fail --location --silent --show-error "$DOWNLOAD_ROOT/checksums.txt" -o "$tmp_dir/checksums.txt"
expected=$(awk -v name="$artifact" '$2 == name || $2 == "*" name { print $1; exit }' "$tmp_dir/checksums.txt")
[ -n "$expected" ] || die "checksum for $artifact was not published"
if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$tmp_dir/$artifact" | awk '{print $1}')
else actual=$(shasum -a 256 "$tmp_dir/$artifact" | awk '{print $1}'); fi
[ "$actual" = "$expected" ] || die "checksum verification failed"
tar -xzf "$tmp_dir/$artifact" -C "$tmp_dir"
[ -f "$tmp_dir/runku" ] || die "download did not contain the runku binary"
as_root install -m 0755 "$tmp_dir/runku" "$BIN_PATH"
"$BIN_PATH" version

say "2/6 Service and storage"
port=$(prompt "HTTP port" "8080")
case "$port" in ''|*[!0-9]*) die "port must be numeric" ;; esac
store_kind=$(prompt "Admin store (postgres/sqlite)" "postgres")
store_dsn=""
case "$store_kind" in
  postgres)
    store_dsn=$(secret_prompt "Dedicated PostgreSQL admin-store DSN")
    [ -n "$store_dsn" ] || die "the PostgreSQL admin-store DSN is required"
    ;;
  sqlite) ;;
  *) die "admin store must be postgres or sqlite" ;;
esac

say "3/6 First connector"
connector_id=$(prompt "Connector id" "primary")
case "$connector_id" in ''|*[!a-zA-Z0-9_-]*|[-_]*) die "connector id may contain letters, numbers, underscore and hyphen, and must start with a letter or number" ;; esac
connector_driver=$(prompt "Source engine (postgres/mysql/sqlserver)" "postgres")
case "$connector_driver" in postgres|mysql|sqlserver) ;; *) die "unsupported connector engine" ;; esac
connector_dsn=$(secret_prompt "Source database DSN")
[ -n "$connector_dsn" ] || die "the source DSN is required"

say "4/6 Authentication"
idp=$(prompt "Authentication (api-key/google/entra/keycloak)" "api-key")
issuer=""; audience=""; portal_client_id=""; prefer_id_token="false"
case "$idp" in
  api-key) ;;
  google)
    issuer="https://accounts.google.com"
    audience=$(prompt "Google OAuth client ID" "")
    portal_client_id=$(prompt "Portal public client ID" "$audience")
    prefer_id_token="true"
    ;;
  entra)
    tenant=$(prompt "Microsoft Entra tenant ID" "")
    [ -n "$tenant" ] || die "tenant ID is required"
    issuer="https://login.microsoftonline.com/$tenant/v2.0"
    audience=$(prompt "API audience (App ID or api:// URI)" "")
    portal_client_id=$(prompt "Portal public client ID" "")
    ;;
  keycloak)
    issuer=$(prompt "Keycloak realm issuer URL" "")
    audience=$(prompt "Token audience" "runku-mcp")
    portal_client_id=$(prompt "Portal public client ID" "runku-portal")
    ;;
  *) die "authentication must be api-key, google, entra or keycloak" ;;
esac
if [ "$idp" != "api-key" ]; then
  [ -n "$issuer" ] && [ -n "$audience" ] && [ -n "$portal_client_id" ] || die "issuer, audience and portal client are required"
fi

as_root mkdir -p "$CONFIG_DIR" "$DATA_DIR"
config_tmp="$tmp_dir/config.yaml"
{
  printf '# Generated by the Runku %s quick installer.\n' "$INSTALLER_VERSION"
  printf 'server:\n  # Portal and MCP connector listener.\n  port: %s\n\n' "$port"
  printf '# Private Runku state; never a source exposed to agents.\nstore:\n'
  if [ "$store_kind" = "postgres" ]; then
    printf '  # Recommended production backend. DSN comes from the service environment.\n  driver: postgres\n  dsn: ${RUNKU_MCP_STORE_DSN}\n\n'
  else
    printf '  # Embedded backend for a small single-instance installation.\n  driver: sqlite\n  path: %s/runku.db\n\n' "$DATA_DIR"
  fi
  printf '# External source published as a governed MCP endpoint.\nconnectors:\n'
  printf '  - id: %s\n    path: /mcp/%s\n    database:\n      driver: %s\n      # Source credentials are separate from the admin store.\n      dsn: ${RUNKU_CONNECTOR_DSN}\n\n' "$(yaml_quote "$connector_id")" "$connector_id" "$connector_driver"
  printf '# Local audit buffer used by the portal and reports.\naudit:\n  sqlite_path: %s/runku-audit.db\n\n' "$DATA_DIR"
  printf '# Authentication verifies callers; authorization remains in the admin store.\nauth:\n  enabled: true\n'
  if [ "$idp" != "api-key" ]; then
    printf '  oidc:\n    issuer: %s\n    audience: %s\n    portal_client_id: %s\n    prefer_id_token: %s\n' "$(yaml_quote "$issuer")" "$(yaml_quote "$audience")" "$(yaml_quote "$portal_client_id")" "$prefer_id_token"
  fi
} > "$config_tmp"
as_root install -m 0640 "$config_tmp" "$CONFIG_PATH"

env_tmp="$tmp_dir/runku.env"
{
  printf 'RUNKU_CONNECTOR_DSN=%s\n' "$(env_quote "$connector_dsn")"
  if [ "$store_kind" = "postgres" ]; then printf 'RUNKU_MCP_STORE_DSN=%s\n' "$(env_quote "$store_dsn")"; fi
} > "$env_tmp"
as_root install -m 0600 "$env_tmp" "$ENV_PATH"

say "5/6 Initialize identity and validate the connector"
as_root env RUNKU_CONNECTOR_DSN="$connector_dsn" RUNKU_MCP_STORE_DSN="$store_dsn" "$BIN_PATH" auth init --config "$CONFIG_PATH" < /dev/tty
token_path="$CONFIG_DIR/.runku-init-token"
if as_root test -f "$token_path"; then
  say "Initial API key (shown once; store it in a password manager or secrets manager):"
  as_root cat "$token_path"
  confirm "Have you saved this API key securely" || die "credential left at $token_path; service was not started"
  as_root rm -f "$token_path"
else
  printf 'OIDC configured: use your identity provider to sign in. No bootstrap API key was generated.\n'
fi

say "6/6 Register and start the service"
if [ "$OS" = "Linux" ]; then
  command -v systemctl >/dev/null 2>&1 || die "systemd is required for automatic service installation"
  if ! id runku >/dev/null 2>&1; then as_root useradd --system --home "$DATA_DIR" --shell /usr/sbin/nologin runku; fi
  as_root chown -R runku:runku "$DATA_DIR"
  as_root chown root:runku "$CONFIG_PATH" "$ENV_PATH"
  unit_tmp="$tmp_dir/runku.service"
  cat > "$unit_tmp" <<EOF
[Unit]
Description=Runku MCP Forge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=runku
Group=runku
EnvironmentFile=$ENV_PATH
ExecStart=$BIN_PATH serve --config $CONFIG_PATH
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF
  as_root install -m 0644 "$unit_tmp" /etc/systemd/system/runku.service
  as_root systemctl daemon-reload
  as_root systemctl enable --now runku.service
  service_status="systemctl status runku --no-pager"
else
  current_user=$(id -un)
  as_root chown -R "$current_user" "$DATA_DIR"
  as_root chown "$current_user" "$CONFIG_PATH" "$ENV_PATH"
  as_root chmod 0600 "$CONFIG_PATH" "$ENV_PATH"
  launcher_tmp="$tmp_dir/runku-service.sh"
  cat > "$launcher_tmp" <<EOF
#!/bin/sh
set -a
. "$ENV_PATH"
set +a
exec "$BIN_PATH" serve --config "$CONFIG_PATH"
EOF
  as_root install -o "$current_user" -m 0700 "$launcher_tmp" "$CONFIG_DIR/service.sh"
  plist_tmp="$tmp_dir/dev.runku.mcp.plist"
  cat > "$plist_tmp" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>dev.runku.mcp</string>
<key>ProgramArguments</key><array><string>$CONFIG_DIR/service.sh</string></array>
<key>UserName</key><string>$current_user</string>
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>$DATA_DIR/runku.log</string><key>StandardErrorPath</key><string>$DATA_DIR/runku-error.log</string>
</dict></plist>
EOF
  as_root launchctl bootout system/dev.runku.mcp >/dev/null 2>&1 || true
  as_root install -m 0644 "$plist_tmp" /Library/LaunchDaemons/dev.runku.mcp.plist
  as_root launchctl bootstrap system /Library/LaunchDaemons/dev.runku.mcp.plist
  service_status="launchctl print system/dev.runku.mcp"
fi

say "Runku MCP Forge is ready"
printf 'Portal:        http://localhost:%s/portal/\n' "$port"
printf 'MCP endpoint:  http://localhost:%s/mcp/%s\n' "$port" "$connector_id"
printf 'Configuration: %s\n' "$CONFIG_PATH"
printf 'Service check: %s\n' "$service_status"
printf 'Next: open the portal and define which tables, columns and operations the connector may expose.\n'
