#!/usr/bin/env bash
set -euo pipefail

# Master installer v2
# Idempotent behavior:
# - reuses existing WireGuard keys on hosts
# - rewrites env/config files safely
# - can re-push wg0.conf and restart wg-quick
# - can re-run install-node.sh on all nodes

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUT_DIR="${ROOT_DIR}/output/${TIMESTAMP}"
mkdir -p "${OUT_DIR}"/{envs,wireguard,notes}

say(){ echo "[*] $*"; }
ask(){ local var="$1" prompt="$2" default="${3:-}" val; if [[ -n "$default" ]]; then read -r -p "$prompt [$default]: " val; val="${val:-$default}"; else read -r -p "$prompt: " val; fi; printf -v "$var" '%s' "$val"; }
ask_secret(){ local var="$1" prompt="$2" val; read -r -s -p "$prompt: " val; echo; printf -v "$var" '%s' "$val"; }
yesno(){ local var="$1" prompt="$2" default="${3:-yes}" val; read -r -p "$prompt [$default]: " val; val="${val:-$default}"; printf -v "$var" '%s' "$val"; }

hash_with_caddy(){
  local plaintext="$1"
  if command -v docker >/dev/null 2>&1; then
    docker run --rm caddy:2 caddy hash-password --plaintext "$plaintext" 2>/dev/null || true
  fi
}

write_db_env() {
  local node="$1" hostname="$2" wgip="$3"
  cat > "${OUT_DIR}/envs/${node}.env" <<EOF
ROLE=db
HOSTNAME_FQDN=${hostname}
NODE_NAME=${node}
WG_ADDRESS=${wgip}/24
WG_PORT=${WG_PORT}
WG_PRIVATE_KEY=GENERATED_ON_HOST
DB1_PUBLIC_IP=${DB1_PUBLIC_IP}
DB2_PUBLIC_IP=${DB2_PUBLIC_IP}
DB3_PUBLIC_IP=${DB3_PUBLIC_IP}
PROXY1_PUBLIC_IP=${PROXY1_PUBLIC_IP}
DB1_WG_IP=${DB1_WG_IP}
DB2_WG_IP=${DB2_WG_IP}
DB3_WG_IP=${DB3_WG_IP}
PROXY1_WG_IP=${PROXY1_WG_IP}
DB1_PUBKEY=${DB1_PUBKEY}
DB2_PUBKEY=${DB2_PUBKEY}
DB3_PUBKEY=${DB3_PUBKEY}
PROXY1_PUBKEY=${PROXY1_PUBKEY}
PATRONI_SCOPE=${PATRONI_SCOPE}
PATRONI_NAMESPACE=${PATRONI_NAMESPACE}
PATRONI_SUPERUSER_USERNAME=${PATRONI_SUPERUSER_USERNAME}
PATRONI_REPLICATION_USERNAME=${PATRONI_REPLICATION_USERNAME}
PATRONI_DB=${PATRONI_DB}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
REPLICATION_PASSWORD=${REPLICATION_PASSWORD}
EOF
}

write_proxy_env() {
  cat > "${OUT_DIR}/envs/proxy1.env" <<EOF
ROLE=proxy
HOSTNAME_FQDN=${PROXY1_HOSTNAME}
WG_ADDRESS=${PROXY1_WG_IP}/24
WG_PORT=${WG_PORT}
WG_PRIVATE_KEY=GENERATED_ON_HOST
DB1_PUBLIC_IP=${DB1_PUBLIC_IP}
DB2_PUBLIC_IP=${DB2_PUBLIC_IP}
DB3_PUBLIC_IP=${DB3_PUBLIC_IP}
PROXY1_PUBLIC_IP=${PROXY1_PUBLIC_IP}
DB1_WG_IP=${DB1_WG_IP}
DB2_WG_IP=${DB2_WG_IP}
DB3_WG_IP=${DB3_WG_IP}
PROXY1_WG_IP=${PROXY1_WG_IP}
DB1_PUBKEY=${DB1_PUBKEY}
DB2_PUBKEY=${DB2_PUBKEY}
DB3_PUBKEY=${DB3_PUBKEY}
PROXY1_PUBKEY=${PROXY1_PUBKEY}
PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
POSTGREST_DB_URI=${POSTGREST_DB_URI}
POSTGREST_DB_SCHEMAS=${POSTGREST_DB_SCHEMAS}
POSTGREST_DB_ANON_ROLE=${POSTGREST_DB_ANON_ROLE}
CADDY_EMAIL=${CADDY_EMAIL}
PGADMIN_DOMAIN=${PGADMIN_DOMAIN}
API_DOMAIN=${API_DOMAIN}
PGADMIN_BASIC_AUTH_USER=${PGADMIN_BASIC_AUTH_USER}
PGADMIN_BASIC_AUTH_HASH=${PGADMIN_BASIC_AUTH_HASH}
API_BASIC_AUTH_USER=${API_BASIC_AUTH_USER}
API_BASIC_AUTH_HASH=${API_BASIC_AUTH_HASH}
EOF
}

write_wg_cfg() {
  local host="$1" ip="$2" file="${OUT_DIR}/wireguard/${host}-wg0.conf"
  cat > "$file" <<EOF
[Interface]
Address = ${ip}/24
ListenPort = ${WG_PORT}
PrivateKey = GENERATED_ON_HOST
SaveConfig = false
EOF
  add_peer(){ cat >> "$file" <<EOF

[Peer]
PublicKey = $1
AllowedIPs = $2/32
Endpoint = $3:${WG_PORT}
PersistentKeepalive = 25
EOF
  }
  [[ "$host" != "db1" ]] && add_peer "$DB1_PUBKEY" "$DB1_WG_IP" "$DB1_PUBLIC_IP"
  [[ "$host" != "db2" ]] && add_peer "$DB2_PUBKEY" "$DB2_WG_IP" "$DB2_PUBLIC_IP"
  [[ "$host" != "db3" ]] && add_peer "$DB3_PUBKEY" "$DB3_WG_IP" "$DB3_PUBLIC_IP"
  [[ "$host" != "proxy1" ]] && add_peer "$PROXY1_PUBKEY" "$PROXY1_WG_IP" "$PROXY1_PUBLIC_IP"
}

configure_wireguard_over_ssh() {
  yesno RUN_WG "Configure WireGuard over SSH now?" "yes"
  [[ "$RUN_WG" == "yes" ]] || return 0

  ask DB1_SSH "SSH for db1" "root@${DB1_PUBLIC_IP}"
  ask DB2_SSH "SSH for db2" "root@${DB2_PUBLIC_IP}"
  ask DB3_SSH "SSH for db3" "root@${DB3_PUBLIC_IP}"
  ask PROXY1_SSH "SSH for proxy1" "root@${PROXY1_PUBLIC_IP}"

  for host in "$DB1_SSH" "$DB2_SSH" "$DB3_SSH" "$PROXY1_SSH"; do
    ssh -o BatchMode=yes "$host" "export DEBIAN_FRONTEND=noninteractive; apt-get update -y >/dev/null && apt-get install -y wireguard wireguard-tools >/dev/null && mkdir -p /etc/wireguard && chmod 700 /etc/wireguard && if [ ! -f /etc/wireguard/privatekey ]; then wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey >/dev/null; fi"
  done

  DB1_PUBKEY="$(ssh "$DB1_SSH" 'cat /etc/wireguard/publickey')"
  DB2_PUBKEY="$(ssh "$DB2_SSH" 'cat /etc/wireguard/publickey')"
  DB3_PUBKEY="$(ssh "$DB3_SSH" 'cat /etc/wireguard/publickey')"
  PROXY1_PUBKEY="$(ssh "$PROXY1_SSH" 'cat /etc/wireguard/publickey')"

  write_db_env db1 "$DB1_HOSTNAME" "$DB1_WG_IP"
  write_db_env db2 "$DB2_HOSTNAME" "$DB2_WG_IP"
  write_db_env db3 "$DB3_HOSTNAME" "$DB3_WG_IP"
  write_proxy_env
  write_wg_cfg db1 "$DB1_WG_IP"
  write_wg_cfg db2 "$DB2_WG_IP"
  write_wg_cfg db3 "$DB3_WG_IP"
  write_wg_cfg proxy1 "$PROXY1_WG_IP"

  cat > "${OUT_DIR}/notes/hosts.txt" <<EOF
${DB1_WG_IP} db1
${DB2_WG_IP} db2
${DB3_WG_IP} db3
${PROXY1_WG_IP} proxy1
EOF

  for pair in \
    "$DB1_SSH db1" \
    "$DB2_SSH db2" \
    "$DB3_SSH db3" \
    "$PROXY1_SSH proxy1"
  do
    set -- $pair
    ssh_host="$1"
    name="$2"
    scp "${OUT_DIR}/wireguard/${name}-wg0.conf" "${ssh_host}:/tmp/wg0.conf"
    scp "${OUT_DIR}/notes/hosts.txt" "${ssh_host}:/tmp/hosts.add"
    ssh "$ssh_host" "cp /tmp/wg0.conf /etc/wireguard/wg0.conf && chmod 600 /etc/wireguard/wg0.conf && while read -r line; do grep -qF \"\$line\" /etc/hosts || echo \"\$line\" >> /etc/hosts; done < /tmp/hosts.add && systemctl enable wg-quick@wg0 >/dev/null 2>&1 || true && systemctl restart wg-quick@wg0"
  done
}

run_installs() {
  yesno RUN_INSTALLS "Run install-node.sh on the hosts now?" "no"
  [[ "$RUN_INSTALLS" == "yes" ]] || return 0
  : "${DB1_SSH:=root@${DB1_PUBLIC_IP}}"
  : "${DB2_SSH:=root@${DB2_PUBLIC_IP}}"
  : "${DB3_SSH:=root@${DB3_PUBLIC_IP}}"
  : "${PROXY1_SSH:=root@${PROXY1_PUBLIC_IP}}"

  for host in "$DB1_SSH" "$DB2_SSH" "$DB3_SSH" "$PROXY1_SSH"; do
    ssh "$host" "mkdir -p /root/pg-master-v2"
    scp "${ROOT_DIR}/install-node.sh" "$host:/root/pg-master-v2/install-node.sh"
  done
  scp "${OUT_DIR}/envs/db1.env" "$DB1_SSH:/root/pg-master-v2/node.env"
  ssh "$DB1_SSH" "bash /root/pg-master-v2/install-node.sh /root/pg-master-v2/node.env"
  scp "${OUT_DIR}/envs/db2.env" "$DB2_SSH:/root/pg-master-v2/node.env"
  ssh "$DB2_SSH" "bash /root/pg-master-v2/install-node.sh /root/pg-master-v2/node.env"
  scp "${OUT_DIR}/envs/db3.env" "$DB3_SSH:/root/pg-master-v2/node.env"
  ssh "$DB3_SSH" "bash /root/pg-master-v2/install-node.sh /root/pg-master-v2/node.env"
  scp "${OUT_DIR}/envs/proxy1.env" "$PROXY1_SSH:/root/pg-master-v2/node.env"
  ssh "$PROXY1_SSH" "bash /root/pg-master-v2/install-node.sh /root/pg-master-v2/node.env"
}

main() {
  ask WG_PORT "WireGuard UDP port" "51820"
  ask DB1_PUBLIC_IP "db1 public IP"
  ask DB2_PUBLIC_IP "db2 public IP"
  ask DB3_PUBLIC_IP "db3 public IP"
  ask PROXY1_PUBLIC_IP "proxy1 public IP"
  ask DB1_WG_IP "db1 WireGuard IP" "10.20.0.11"
  ask DB2_WG_IP "db2 WireGuard IP" "10.20.0.12"
  ask DB3_WG_IP "db3 WireGuard IP" "10.20.0.13"
  ask PROXY1_WG_IP "proxy1 WireGuard IP" "10.20.0.21"
  ask DB1_HOSTNAME "db1 hostname" "db1"
  ask DB2_HOSTNAME "db2 hostname" "db2"
  ask DB3_HOSTNAME "db3 hostname" "db3"
  ask PROXY1_HOSTNAME "proxy1 hostname" "proxy1"
  ask PATRONI_SCOPE "Patroni scope" "pg-ha-demo"
  ask PATRONI_NAMESPACE "Patroni namespace" "/service/"
  ask PATRONI_SUPERUSER_USERNAME "Postgres superuser" "postgres"
  ask PATRONI_REPLICATION_USERNAME "Replication user" "replicator"
  ask PATRONI_DB "Main app database" "appdb"
  ask_secret POSTGRES_PASSWORD "Postgres superuser password"
  ask_secret REPLICATION_PASSWORD "Replication password"
  ask PGADMIN_DEFAULT_EMAIL "pgAdmin login email" "admin@example.com"
  ask_secret PGADMIN_DEFAULT_PASSWORD "pgAdmin login password"
  ask CADDY_EMAIL "Caddy ACME email" "you@example.com"
  ask PGADMIN_DOMAIN "pgAdmin domain" "pgadmin.example.com"
  ask API_DOMAIN "API domain" "api.example.com"
  ask POSTGREST_DB_SCHEMAS "PostgREST schemas" "api"
  ask POSTGREST_DB_ANON_ROLE "PostgREST anon role" "web_anon"
  ask PGADMIN_BASIC_AUTH_USER "pgAdmin basic auth user" "admin"
  ask API_BASIC_AUTH_USER "API basic auth user" "apiuser"
  ask_secret PGADMIN_BASIC_AUTH_PLAIN "pgAdmin basic auth password"
  ask_secret API_BASIC_AUTH_PLAIN "API basic auth password"
  PGADMIN_BASIC_AUTH_HASH="$(hash_with_caddy "$PGADMIN_BASIC_AUTH_PLAIN")"
  API_BASIC_AUTH_HASH="$(hash_with_caddy "$API_BASIC_AUTH_PLAIN")"
  [[ -n "$PGADMIN_BASIC_AUTH_HASH" ]] || ask PGADMIN_BASIC_AUTH_HASH "Paste pgAdmin bcrypt hash"
  [[ -n "$API_BASIC_AUTH_HASH" ]] || ask API_BASIC_AUTH_HASH "Paste API bcrypt hash"
  ask POSTGREST_DB_USER "PostgREST DB login user" "app_api"
  ask_secret POSTGREST_DB_PASSWORD "PostgREST DB login password"
  POSTGREST_DB_URI="postgres://${POSTGREST_DB_USER}:${POSTGREST_DB_PASSWORD}@127.0.0.1:5432/${PATRONI_DB}"

  DB1_PUBKEY="TO_BE_COLLECTED"
  DB2_PUBKEY="TO_BE_COLLECTED"
  DB3_PUBKEY="TO_BE_COLLECTED"
  PROXY1_PUBKEY="TO_BE_COLLECTED"

  write_db_env db1 "$DB1_HOSTNAME" "$DB1_WG_IP"
  write_db_env db2 "$DB2_HOSTNAME" "$DB2_WG_IP"
  write_db_env db3 "$DB3_HOSTNAME" "$DB3_WG_IP"
  write_proxy_env

  configure_wireguard_over_ssh
  run_installs

  echo "Generated output in: ${OUT_DIR}"
}
main "$@"
