#!/usr/bin/env bash
#
# Install ClickHouse server + client on Debian/Ubuntu from packages.clickhouse.com.
# Avoids Ubuntu "universe" ClickHouse 18.x, which is outdated and often misconfigured.
#
# Usage (from repo root or any path):
#   bash scripts/setup-clickhouse.sh
#   bash scripts/setup-clickhouse.sh --purge-existing   # one line; do not split after "--"
#   bash scripts/setup-clickhouse.sh --listen-all-interfaces   # HTTP 8123 / native 9000 on 0.0.0.0 (see firewall)
#   CLICKHOUSE_CHANNEL=lts bash scripts/setup-clickhouse.sh
#
# Env:
#   CLICKHOUSE_CHANNEL        stable (default) or lts — see https://clickhouse.com/docs/knowledgebase/production
#   WAIT_FOR_APT_LOCK_SEC     max seconds to wait for apt/dpkg lock (default: 900)
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
WAIT_FOR_APT_LOCK_SEC="${WAIT_FOR_APT_LOCK_SEC:-900}"
APT_LOCK_SLEEP_SEC="${APT_LOCK_SLEEP_SEC:-5}"

# Wait until dpkg/apt can run (avoids E: Could not get lock ... lock-frontend).
# Uses non-blocking flock(1) on the same lock files apt/dpkg use — not fuser on lists/archives
# locks, which can look "busy" for a long time and cause endless waits.
wait_for_apt_lock() {
  local waited=0
  local last_holder_line=-1
  while true; do
    if _dpkg_locks_available; then
      return 0
    fi
    if (( waited >= WAIT_FOR_APT_LOCK_SEC )); then
      echo "Error: timed out after ${WAIT_FOR_APT_LOCK_SEC}s waiting for apt/dpkg lock." >&2
      echo "Check what holds the lock:" >&2
      echo "  sudo fuser -v /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock 2>&1 | head -20" >&2
      echo "  sudo ps aux | egrep '[a]pt|[d]pkg'" >&2
      echo "  sudo systemctl status unattended-upgrades" >&2
      echo "Then re-run, or set WAIT_FOR_APT_LOCK_SEC=3600 to wait longer." >&2
      exit 1
    fi
    echo "==> Waiting for apt/dpkg lock to clear (${waited}s / ${WAIT_FOR_APT_LOCK_SEC}s max)..."
    if (( waited >= 60 && waited != last_holder_line && waited % 60 == 0 )); then
      last_holder_line=$waited
      if command -v fuser >/dev/null 2>&1; then
        echo "    (lock holders: $(sudo fuser /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock 2>/dev/null | tr -s ' ' || true))" >&2
      fi
    fi
    sleep "$APT_LOCK_SLEEP_SEC"
    waited=$((waited + APT_LOCK_SLEEP_SEC))
  done
}

# True if we can take an exclusive flock on dpkg locks right now (same mechanism apt uses).
_dpkg_locks_available() {
  if ! command -v flock >/dev/null 2>&1; then
    echo "Error: flock(1) not found (install util-linux)." >&2
    exit 1
  fi
  local f
  for f in /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock; do
    [[ -e "$f" ]] || continue
    if ! sudo flock -n "$f" true 2>/dev/null; then
      return 1
    fi
  done
  return 0
}

apt_get() {
  wait_for_apt_lock
  sudo apt-get "$@"
}
CLICKHOUSE_LIST="/etc/apt/sources.list.d/clickhouse.list"
KEYRING="/usr/share/keyrings/clickhouse-keyring.gpg"
CHANNEL="${CLICKHOUSE_CHANNEL:-stable}"
PURGE_EXISTING=false
LISTEN_ALL_INTERFACES=false

for arg in "$@"; do
  case "$arg" in
    --purge-existing) PURGE_EXISTING=true ;;
    --listen-all-interfaces) LISTEN_ALL_INTERFACES=true ;;
    -h|--help)
      echo "Usage: $0 [--purge-existing] [--listen-all-interfaces]"
      echo "  --purge-existing           apt purge existing clickhouse* packages before installing (destructive for old installs)."
      echo "  --listen-all-interfaces   bind HTTP (8123) and native (9000) on 0.0.0.0 — open cloud NSG/ufw + use strong passwords."
      echo "  Env: CLICKHOUSE_CHANNEL=stable|lts (default: stable), WAIT_FOR_APT_LOCK_SEC (default: 900)"
      exit 0
      ;;
    *)
      echo "Unknown option: $arg (try --help)" >&2
      exit 1
      ;;
  esac
done

if [[ ! -f /etc/os-release ]]; then
  echo "Error: /etc/os-release not found. This script supports Debian/Ubuntu only." >&2
  exit 1
fi
# shellcheck source=/dev/null
source /etc/os-release
case "${ID:-},${ID_LIKE:-}" in
  debian,*|ubuntu,*|*,*debian*|*,*ubuntu*) ;;
  *)
    echo "Warning: OS ID=${ID:-unknown} may not be Debian/Ubuntu. Continuing if apt works." >&2
    ;;
esac

if [[ "$CHANNEL" != "stable" && "$CHANNEL" != "lts" ]]; then
  echo "Error: CLICKHOUSE_CHANNEL must be stable or lts, got: $CHANNEL" >&2
  exit 1
fi

if [[ "$PURGE_EXISTING" == "true" ]]; then
  echo "==> Purging existing ClickHouse packages (--purge-existing)..."
  sudo systemctl stop clickhouse-server 2>/dev/null || true
  sudo systemctl stop clickhouse-keeper 2>/dev/null || true
  apt_get purge -y 'clickhouse*' || true
  apt_get autoremove -y || true
fi

echo "==> Installing prerequisites..."
export DEBIAN_FRONTEND=noninteractive
apt_get update -qq
apt_get install -y apt-transport-https ca-certificates curl gnupg

echo "==> Adding ClickHouse APT repo (channel: $CHANNEL)..."
sudo mkdir -p /usr/share/keyrings
if [[ ! -f "$KEYRING" ]]; then
  curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' \
    | sudo gpg --dearmor -o "$KEYRING"
fi

ARCH="$(dpkg --print-architecture)"
echo "deb [signed-by=${KEYRING} arch=${ARCH}] https://packages.clickhouse.com/deb ${CHANNEL} main" \
  | sudo tee "$CLICKHOUSE_LIST" >/dev/null

echo "==> Installing clickhouse-server and clickhouse-client..."
apt_get update -qq
apt_get install -y clickhouse-server clickhouse-client

echo "==> Enabling and starting clickhouse-server..."
sudo systemctl enable --now clickhouse-server
sudo systemctl --no-pager --full status clickhouse-server || true

if [[ "$LISTEN_ALL_INTERFACES" == "true" ]]; then
  echo "==> Binding ClickHouse to all IPv4 interfaces (0.0.0.0) for remote HTTP/native..."
  sudo tee /etc/clickhouse-server/config.d/99-listen-all-interfaces.xml >/dev/null <<'EOF'
<clickhouse>
    <!-- Default install often listens only on loopback; this allows e.g. http://SERVER_IP:8123 from other hosts. -->
    <listen_host>0.0.0.0</listen_host>
</clickhouse>
EOF
  sudo systemctl restart clickhouse-server
  sudo systemctl --no-pager --full status clickhouse-server || true
  echo "    Open inbound TCP 8123 (and 9000 if you use native clients) in your cloud NSG / security group and ufw."
fi

echo "==> Verifying..."
if clickhouse-client -q "SELECT version(), 1" 2>/dev/null; then
  echo "ClickHouse responded OK."
else
  echo "Note: clickhouse-client check failed (password required?). Try:  clickhouse-client --password -q \"SELECT 1\"" >&2
  echo "Logs: sudo tail -50 /var/log/clickhouse-server/clickhouse-server.err.log" >&2
  exit 1
fi

echo ""
echo "Done. Client: clickhouse-client | HTTP default :8123 | native default :9000"
if [[ "$LISTEN_ALL_INTERFACES" != "true" ]]; then
  echo "Remote browser/API to this host: re-run with --listen-all-interfaces or add listen_host in /etc/clickhouse-server/config.d/"
  echo "  (default is often localhost-only, so http://PUBLIC_IP:8123 will not work until you do)."
fi
