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

# ------------------------------------------------------------
# Budibase install script
#
# Usage:
#   curl -fsSL https://install.budibase.com | bash
#
# Overrides (env vars):
#   PORT=8080 \
#   DATA_DIR=/srv/budibase/data \
#   BUDIBASE_IMAGE=budibase/budibase:v3.0.0 \
#   CONTAINER_NAME=budibase \
#   READY_TIMEOUT_SECONDS=180 \
#   curl -fsSL https://install.budibase.com | bash
# ------------------------------------------------------------

# Defaults (override via env)
BUDIBASE_IMAGE="${BUDIBASE_IMAGE:-budibase/budibase:latest}"
CONTAINER_NAME="${CONTAINER_NAME:-budibase}"
DATA_DIR="${DATA_DIR:-${HOME}/budibase/data}"
PORT="${PORT:-10000}"
READY_TIMEOUT_SECONDS="${READY_TIMEOUT_SECONDS:-120}"

RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
NC="\033[0m"

log()  { echo -e "${GREEN}[Budibase]${NC} $1"; }
warn() { echo -e "${YELLOW}[Budibase]${NC} $1"; }
die()  { echo -e "${RED}[Budibase]${NC} $1"; exit 1; }

command_exists() { command -v "$1" >/dev/null 2>&1; }

is_integer() { [[ "${1:-}" =~ ^[0-9]+$ ]]; }

port_available() {
  local port="$1"

  if command_exists lsof; then
    ! lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
    return $?
  fi

  if command_exists ss; then
    ! ss -ltn 2>/dev/null | awk '{print $4}' | grep -E "[:.]${port}$" -q
    return $?
  fi

  if command_exists netstat; then
    ! netstat -an 2>/dev/null | grep -E "[\.:]${port}[[:space:]]" | grep -i LISTEN -q
    return $?
  fi

  return 0
}

normalize_data_dir() {
  local d="$1"
  if [[ "$d" == "~" || "$d" == "~/"* ]]; then
    d="${HOME}${d:1}"
  fi
  echo "$d"
}

# -----------------------------
# Single-line progress bar (CR-style)
# -----------------------------
BAR_WIDTH=30
_LAST_LINE_LEN=0

# Clear the previous rendered line completely (handles "long then short")
_clear_line() {
  local spaces=""
  if (( _LAST_LINE_LEN > 0 )); then
    spaces="$(printf '%*s' "${_LAST_LINE_LEN}" '')"
    echo -ne "\r${spaces}\r"
  else
    echo -ne "\r"
  fi
}

_render_bar() {
  local pct="$1"   # 0..100
  local label="$2"

  if (( pct < 0 )); then pct=0; fi
  if (( pct > 100 )); then pct=100; fi

  local filled=$(( (BAR_WIDTH * pct) / 100 ))
  local empty=$(( BAR_WIDTH - filled ))

  local bar
  bar="$(printf '%*s' "${filled}" '' | tr ' ' '#')"
  bar="${bar}$(printf '%*s' "${empty}" '' | tr ' ' ' ')"

  local line="[Budibase] [${bar}] (${pct}%) ${label}"

  _clear_line
  echo -ne "${GREEN}${line}${NC}\r"
  _LAST_LINE_LEN="${#line}"
}

# Run a command while animating an indeterminate bar up to 95%, then finish.
# Writes ONE final newline when the command completes.
run_with_progress() {
  local label="$1"
  shift
  local -a cmd=( "$@" )

  # Start command in background, silence its stdout/stderr to avoid fighting the bar.
  # If you want to see errors, we capture and print them on failure.
  local tmp_out tmp_err
  tmp_out="$(mktemp)"
  tmp_err="$(mktemp)"

  "${cmd[@]}" >"${tmp_out}" 2>"${tmp_err}" &
  local pid=$!

  local pct=0
  local step=1
  local direction=1

  # Animate while running: bounce between 5%..95% slowly.
  while kill -0 "${pid}" >/dev/null 2>&1; do
    # Simple indeterminate movement
    pct=$(( pct + (step * direction) ))
    if (( pct >= 95 )); then pct=95; direction=-1; fi
    if (( pct <= 5 )); then pct=5; direction=1; fi

    _render_bar "${pct}" "${label}"
    sleep 0.12
  done

  wait "${pid}"
  local rc=$?

  if (( rc == 0 )); then
    _render_bar 100 "${label} ✓"
    echo -ne "\n"
    rm -f "${tmp_out}" "${tmp_err}"
    return 0
  fi

  _render_bar 100 "${label} ✗"
  echo -ne "\n"

  # Print captured output to help debugging.
  if [[ -s "${tmp_err}" ]]; then
    warn "Command failed: ${cmd[*]}"
    warn "stderr:"
    sed 's/^/[Budibase]   /' "${tmp_err}" >&2 || true
  fi
  if [[ -s "${tmp_out}" ]]; then
    warn "stdout:"
    sed 's/^/[Budibase]   /' "${tmp_out}" >&2 || true
  fi

  rm -f "${tmp_out}" "${tmp_err}"
  return "${rc}"
}

http_ready() {
  local url="$1"

  if command_exists curl; then
    curl -fsS --max-time 2 "${url}" >/dev/null 2>&1
    return $?
  fi

  if command_exists wget; then
    wget -qO- --timeout=2 "${url}" >/dev/null 2>&1
    return $?
  fi

  return 2
}

wait_for_ready() {
  local url="$1"
  local timeout_s="${2:-120}"

  local start_ts now_ts
  start_ts="$(date +%s 2>/dev/null || echo 0)"

  while true; do
    if http_ready "${url}"; then
      return 0
    fi

    local rc=$?
    if [[ "${rc}" -eq 2 ]]; then
      # Can't probe; degrade gracefully.
      sleep 2
      return 0
    fi

    now_ts="$(date +%s 2>/dev/null || echo 0)"
    if [[ "${start_ts}" -ne 0 && "${now_ts}" -ne 0 ]]; then
      if (( now_ts - start_ts >= timeout_s )); then
        return 1
      fi
    fi

    sleep 1
  done
}

preflight() {
  log "Starting Budibase installation..."

  if ! command_exists docker; then
    die "Docker is not installed. Install Docker first: https://docs.docker.com/get-docker/"
  fi

  if ! docker info >/dev/null 2>&1; then
    die "Docker daemon is not running. Start Docker and retry."
  fi

  if ! is_integer "${PORT}"; then
    die "PORT must be an integer. Got: ${PORT}"
  fi

  if (( PORT < 1 || PORT > 65535 )); then
    die "PORT must be between 1 and 65535. Got: ${PORT}"
  fi

  if ! is_integer "${READY_TIMEOUT_SECONDS}"; then
    die "READY_TIMEOUT_SECONDS must be an integer. Got: ${READY_TIMEOUT_SECONDS}"
  fi

  if (( READY_TIMEOUT_SECONDS < 5 || READY_TIMEOUT_SECONDS > 3600 )); then
    die "READY_TIMEOUT_SECONDS must be between 5 and 3600. Got: ${READY_TIMEOUT_SECONDS}"
  fi

  DATA_DIR="$(normalize_data_dir "${DATA_DIR}")"

  log "Configuration:"
  log "  Image:                ${BUDIBASE_IMAGE}"
  log "  Container name:       ${CONTAINER_NAME}"
  log "  Host port:            ${PORT}"
  log "  Data dir:             ${DATA_DIR}"
  log "  Ready timeout (secs): ${READY_TIMEOUT_SECONDS}"
}

ensure_data_dir() {
  log "Ensuring data directory exists at ${DATA_DIR}"
  mkdir -p "${DATA_DIR}"

  if [[ ! -w "${DATA_DIR}" ]]; then
    warn "Data directory is not writable: ${DATA_DIR}"
    warn "You may need to adjust permissions or choose a different DATA_DIR."
  fi
}

handle_existing_container() {
  if docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then
    warn "Existing container '${CONTAINER_NAME}' detected"

    local running
    running="$(docker inspect -f '{{.State.Running}}' "${CONTAINER_NAME}" 2>/dev/null || echo "false")"

    if [[ "${running}" == "true" ]]; then
      log "Budibase is already running"
      log "Access it at: http://localhost:${PORT}"
      exit 0
    fi

    warn "Removing stopped container '${CONTAINER_NAME}'"
    docker rm "${CONTAINER_NAME}" >/dev/null
  fi
}

check_port() {
  if ! port_available "${PORT}"; then
    die "Port ${PORT} is already in use. Choose another PORT, e.g.: PORT=8080 curl -fsSL https://install.budibase.com | bash"
  fi
}

pull_image() {
  run_with_progress "Pulling ${BUDIBASE_IMAGE}" docker pull "${BUDIBASE_IMAGE}"
}

run_container() {
  run_with_progress "Starting container '${CONTAINER_NAME}'" docker run -d \
    --name="${CONTAINER_NAME}" \
    -p "${PORT}:80" \
    -v "${DATA_DIR}:/data" \
    --restart unless-stopped \
    "${BUDIBASE_IMAGE}"
}

wait_until_up() {
  # Note: wait_for_ready is quiet; we animate a bar while it polls.
  run_with_progress "Waiting for http://localhost:${PORT}" wait_for_ready "http://localhost:${PORT}/" "${READY_TIMEOUT_SECONDS}" || {
    warn "Budibase container started, but the UI didn't become reachable within ${READY_TIMEOUT_SECONDS}s."
    warn "Check logs: docker logs -f ${CONTAINER_NAME}"
    return 0
  }
}

post_install() {
  log "Budibase successfully installed"
  log "---------------------------------------"
  log "UI:              http://localhost:${PORT}"
  log "Data directory:  ${DATA_DIR}"
  log "Container:       ${CONTAINER_NAME}"
  log "---------------------------------------"
  log "Useful commands:"
  log "  View logs:   docker logs -f ${CONTAINER_NAME}"
  log "  Stop:        docker stop ${CONTAINER_NAME}"
  log "  Start:       docker start ${CONTAINER_NAME}"
  log "  Remove:      docker rm -f ${CONTAINER_NAME}"
  log "Done."
}

main() {
  preflight
  ensure_data_dir
  check_port
  handle_existing_container
  pull_image
  run_container
  wait_until_up
  post_install
}

main "$@"
