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

# Master installer/generator for the PostgreSQL HA stack.
# It asks questions, generates per-host env files, and can optionally run installs.
#
# Features:
# - WireGuard installation/config generation
# - DB node installer env generation for db1/db2/db3
# - Proxy installer env generation for proxy1
# - Optional backup repo env generation for backup1
# - Optional remote execution of install-node.sh
#
# Usage:
#   chmod +x master-setup.sh
#   ./master-setup.sh
#
# Output:
#   ./output/<timestamp>/

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:-}"
  local value
  if [[ -n "$default" ]]; then
    read -r -p "$prompt [$default]: " value
    value="${value:-$default}"
  else
    read -r -p "$prompt: " value
  fi
  printf -v "$var" '%s' "$value"
}
ask_secret() {
  local var="$1" prompt="$2"
  local value
  read -r -s -p "$prompt: " value
  echo
  printf -v "$var" '%s' "$value"
}
must_not_empty() {
  local name="$1" value="$2"
  [[ -n "$value" ]] || { echo "Missing required value: $name" >&2; exit 1; }
}

generate_caddy_hash() {
  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_name="$1" hostname="$2" wg_addr="$3" privkey="$4"
  cat > "${OUT_DIR}/envs/${node_name}.env" <<EOF
ROLE=db
HOSTNAME_FQDN=${hostname}
NODE_NAME=${node_name}

WG_ADDRESS=${wg_addr}/24
WG_PORT=${WG_PORT}
WG_PRIVATE_KEY=${privkey}

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=${PROXY1_PRIVKEY}

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_backup_envs() {
  cat > "${OUT_DIR}/envs/backup-repo.env" <<EOF
ROLE=repo
REPO_HOSTNAME=${BACKUP1_HOSTNAME}

STANZA=${BACKUP_STANZA}
REPO_HOST=${BACKUP1_HOSTNAME}
REPO_PATH=${BACKUP_REPO_PATH}
REPO_RETENTION_FULL=${BACKUP_RETENTION_FULL}
REPO_RETENTION_DIFF=${BACKUP_RETENTION_DIFF}

DB1_HOST=db1
DB2_HOST=db2
DB3_HOST=db3

PG_SUPERUSER=${PATRONI_SUPERUSER_USERNAME}
PG_SUPERUSER_PASSWORD=${POSTGRES_PASSWORD}
EOF

  for node in db1 db2 db3; do
    cat > "${OUT_DIR}/envs/${node}-backup.env" <<EOF
ROLE=db
NODE_NAME=${node}

STANZA=${BACKUP_STANZA}
REPO_HOST=${BACKUP1_HOSTNAME}
REPO_PATH=${BACKUP_REPO_PATH}
REPO_RETENTION_FULL=${BACKUP_RETENTION_FULL}
REPO_RETENTION_DIFF=${BACKUP_RETENTION_DIFF}

DB1_HOST=db1
DB2_HOST=db2
DB3_HOST=db3

PG_SUPERUSER=${PATRONI_SUPERUSER_USERNAME}
PG_SUPERUSER_PASSWORD=${POSTGRES_PASSWORD}

PATRONI_CONFIG_PATH=/opt/db-stack/patroni.yml
PGBACKREST_DB_PATH=/var/lib/postgresql/data
EOF
  done
}

write_wireguard_conf() {
  local host="$1" addr="$2" privkey="$3" out="${OUT_DIR}/wireguard/${host}-wg0.conf"
  cat > "$out" <<EOF
[Interface]
Address = ${addr}/24
ListenPort = ${WG_PORT}
PrivateKey = ${privkey}
SaveConfig = false
EOF

  if [[ "$host" != "db1" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${DB1_PUBKEY}
AllowedIPs = ${DB1_WG_IP}/32
Endpoint = ${DB1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi

  if [[ "$host" != "db2" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${DB2_PUBKEY}
AllowedIPs = ${DB2_WG_IP}/32
Endpoint = ${DB2_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi

  if [[ "$host" != "db3" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${DB3_PUBKEY}
AllowedIPs = ${DB3_WG_IP}/32
Endpoint = ${DB3_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi

  if [[ "$host" != "proxy1" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${PROXY1_PUBKEY}
AllowedIPs = ${PROXY1_WG_IP}/32
Endpoint = ${PROXY1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi

  if [[ "${ENABLE_BACKUP}" == "yes" && "$host" != "backup1" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${BACKUP1_PUBKEY}
AllowedIPs = ${BACKUP1_WG_IP}/32
Endpoint = ${BACKUP1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi

  if [[ "${ENABLE_BACKUP}" == "yes" && "$host" == "backup1" ]]; then
    cat >> "$out" <<EOF

[Peer]
PublicKey = ${DB1_PUBKEY}
AllowedIPs = ${DB1_WG_IP}/32
Endpoint = ${DB1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${DB2_PUBKEY}
AllowedIPs = ${DB2_WG_IP}/32
Endpoint = ${DB2_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${DB3_PUBKEY}
AllowedIPs = ${DB3_WG_IP}/32
Endpoint = ${DB3_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25

[Peer]
PublicKey = ${PROXY1_PUBKEY}
AllowedIPs = ${PROXY1_WG_IP}/32
Endpoint = ${PROXY1_PUBLIC_IP}:${WG_PORT}
PersistentKeepalive = 25
EOF
  fi
}

write_runbook() {
  cat > "${OUT_DIR}/README_START_HERE.md" <<EOF
# Master Generated Bundle

Generated: ${TIMESTAMP}

## Files
- envs/db1.env
- envs/db2.env
- envs/db3.env
- envs/proxy1.env
$( [[ "${ENABLE_BACKUP}" == "yes" ]] && echo "- envs/backup-repo.env
- envs/db1-backup.env
- envs/db2-backup.env
- envs/db3-backup.env" )
- wireguard/db1-wg0.conf
- wireguard/db2-wg0.conf
- wireguard/db3-wg0.conf
- wireguard/proxy1-wg0.conf
$( [[ "${ENABLE_BACKUP}" == "yes" ]] && echo "- wireguard/backup1-wg0.conf" )

## Install order

### 1. Copy /etc/hosts entries to every host
${DB1_WG_IP} db1
${DB2_WG_IP} db2
${DB3_WG_IP} db3
${PROXY1_WG_IP} proxy1
$( [[ "${ENABLE_BACKUP}" == "yes" ]] && echo "${BACKUP1_WG_IP} backup1" )

### 2. Install DB nodes
On db1:
  sudo bash scripts/install-node.sh envs/db1.env
On db2:
  sudo bash scripts/install-node.sh envs/db2.env
On db3:
  sudo bash scripts/install-node.sh envs/db3.env

### 3. Install proxy node
On proxy1:
  sudo bash scripts/install-node.sh envs/proxy1.env

### 4. Initialize database
psql -h ${PROXY1_PUBLIC_IP} -p 5432 -U ${PATRONI_SUPERUSER_USERNAME}
CREATE DATABASE ${PATRONI_DB};
\\c ${PATRONI_DB}
CREATE EXTENSION IF NOT EXISTS pg_cron;

### 5. Optional backup setup
$( if [[ "${ENABLE_BACKUP}" == "yes" ]]; then cat <<EOB
On backup1:
  sudo bash scripts/setup-backup.sh envs/backup-repo.env

On db1:
  sudo bash scripts/setup-backup.sh envs/db1-backup.env
On db2:
  sudo bash scripts/setup-backup.sh envs/db2-backup.env
On db3:
  sudo bash scripts/setup-backup.sh envs/db3-backup.env
EOB
fi)

## Optional cluster operations
- scripts/cluster-node-haproxy.sh
- scripts/cluster-node.sh

EOF
}

copy_bundled_scripts() {
  # from previous generated zip contents if present
  import_dir="${ROOT_DIR}/imported"
  mkdir -p "$import_dir"
}

remote_install_prompt() {
  ask RUN_REMOTE "Do you want this script to SSH and run installs now? (yes/no)" "no"
  if [[ "$RUN_REMOTE" != "yes" ]]; then
    return
  fi

  ask DB1_SSH "SSH for db1" "root@db1"
  ask DB2_SSH "SSH for db2" "root@db2"
  ask DB3_SSH "SSH for db3" "root@db3"
  ask PROXY1_SSH "SSH for proxy1" "root@proxy1"

  say "Copying generated files and running installers"
  for host in "$DB1_SSH" "$DB2_SSH" "$DB3_SSH" "$PROXY1_SSH"; do
    ssh -o BatchMode=yes "$host" "mkdir -p /root/pg-master-installer"
    scp -r "${ROOT_DIR}/scripts" "$host:/root/pg-master-installer/"
  done

  scp "${OUT_DIR}/envs/db1.env" "$DB1_SSH:/root/pg-master-installer/"
  scp "${OUT_DIR}/envs/db2.env" "$DB2_SSH:/root/pg-master-installer/"
  scp "${OUT_DIR}/envs/db3.env" "$DB3_SSH:/root/pg-master-installer/"
  scp "${OUT_DIR}/envs/proxy1.env" "$PROXY1_SSH:/root/pg-master-installer/"

  ssh "$DB1_SSH" "bash /root/pg-master-installer/scripts/install-node.sh /root/pg-master-installer/db1.env"
  ssh "$DB2_SSH" "bash /root/pg-master-installer/scripts/install-node.sh /root/pg-master-installer/db2.env"
  ssh "$DB3_SSH" "bash /root/pg-master-installer/scripts/install-node.sh /root/pg-master-installer/db3.env"
  ssh "$PROXY1_SSH" "bash /root/pg-master-installer/scripts/install-node.sh /root/pg-master-installer/proxy1.env"
}

main() {
  say "PostgreSQL HA master setup"
  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"

  echo "Paste WireGuard private keys"
  ask_secret DB1_PRIVKEY "db1 private key"
  ask_secret DB2_PRIVKEY "db2 private key"
  ask_secret DB3_PRIVKEY "db3 private key"
  ask_secret PROXY1_PRIVKEY "proxy1 private key"

  echo "Paste WireGuard public keys"
  ask DB1_PUBKEY "db1 public key"
  ask DB2_PUBKEY "db2 public key"
  ask DB3_PUBKEY "db3 public key"
  ask PROXY1_PUBKEY "proxy1 public key"

  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 name" "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"

  say "Generating Caddy bcrypt hashes with Docker if available"
  PGADMIN_BASIC_AUTH_HASH="$(generate_caddy_hash "${PGADMIN_BASIC_AUTH_PLAIN}")"
  API_BASIC_AUTH_HASH="$(generate_caddy_hash "${API_BASIC_AUTH_PLAIN}")"
  if [[ -z "${PGADMIN_BASIC_AUTH_HASH}" || -z "${API_BASIC_AUTH_HASH}" ]]; then
    echo "Could not generate Caddy hashes automatically."
    echo "Run on a machine with Docker:"
    echo "  docker run --rm caddy:2 caddy hash-password --plaintext 'your-password'"
    ask PGADMIN_BASIC_AUTH_HASH "Paste pgAdmin bcrypt hash"
    ask API_BASIC_AUTH_HASH "Paste API bcrypt hash"
  fi

  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}"

  ask ENABLE_BACKUP "Generate backup repo configs too? (yes/no)" "yes"
  if [[ "${ENABLE_BACKUP}" == "yes" ]]; then
    ask BACKUP1_PUBLIC_IP "backup1 public IP"
    ask BACKUP1_WG_IP "backup1 WireGuard IP" "10.20.0.31"
    ask BACKUP1_HOSTNAME "backup1 hostname" "backup1"
    ask_secret BACKUP1_PRIVKEY "backup1 private key"
    ask BACKUP1_PUBKEY "backup1 public key"
    ask BACKUP_STANZA "pgBackRest stanza" "main"
    ask BACKUP_REPO_PATH "Backup repo path" "/var/lib/pgbackrest"
    ask BACKUP_RETENTION_FULL "Retention full count" "7"
    ask BACKUP_RETENTION_DIFF "Retention diff count" "14"
  fi

  must_not_empty DB1_PUBLIC_IP "$DB1_PUBLIC_IP"
  must_not_empty DB2_PUBLIC_IP "$DB2_PUBLIC_IP"
  must_not_empty DB3_PUBLIC_IP "$DB3_PUBLIC_IP"
  must_not_empty PROXY1_PUBLIC_IP "$PROXY1_PUBLIC_IP"

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

  write_wireguard_conf db1 "$DB1_WG_IP" "$DB1_PRIVKEY"
  write_wireguard_conf db2 "$DB2_WG_IP" "$DB2_PRIVKEY"
  write_wireguard_conf db3 "$DB3_WG_IP" "$DB3_PRIVKEY"
  write_wireguard_conf proxy1 "$PROXY1_WG_IP" "$PROXY1_PRIVKEY"

  if [[ "${ENABLE_BACKUP}" == "yes" ]]; then
    write_backup_envs
    write_wireguard_conf backup1 "$BACKUP1_WG_IP" "$BACKUP1_PRIVKEY"
  fi

  write_runbook

  cat > "${OUT_DIR}/notes/hosts.txt" <<EOF
${DB1_WG_IP} db1
${DB2_WG_IP} db2
${DB3_WG_IP} db3
${PROXY1_WG_IP} proxy1
$( [[ "${ENABLE_BACKUP}" == "yes" ]] && echo "${BACKUP1_WG_IP} backup1" )
EOF

  say "Generated output in ${OUT_DIR}"
  remote_install_prompt
  say "Done"
}

main "$@"
