#!/usr/bin/env bash
#
# VPS Pilot — one-command installer for fresh Ubuntu VPS (22.04 / 24.04, x86_64, root).
#
# Usage:
#   bash <(curl -fsSL https://install.puretik.com/install.sh)
#   VPS_PILOT_SSH_PASSWORD='secret' bash <(curl -fsSL https://install.puretik.com/install.sh)
#
# Downloads the latest public release tarball from latest.json — no git clone required.
#
set -euo pipefail

# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
VPS_PILOT_RELEASE_MANIFEST_URL="${VPS_PILOT_RELEASE_MANIFEST_URL:-https://install.puretik.com/releases/latest.json}"
# Optional override for testing; when unset, release URL is resolved from the manifest.
VPS_PILOT_RELEASE_URL="${VPS_PILOT_RELEASE_URL:-}"
VPS_PILOT_INSTALL_DIR="${VPS_PILOT_INSTALL_DIR:-/root/vps-pilot}"
VPS_PILOT_APP_PORT="${VPS_PILOT_APP_PORT:-3001}"
VPS_PILOT_DB_NAME="${VPS_PILOT_DB_NAME:-vps_pilot}"
VPS_PILOT_PM2_NAME="vps-pilot"
VPS_PILOT_NGINX_SITE="vps-pilot"
VPS_PILOT_SSH_PASSWORD="${VPS_PILOT_SSH_PASSWORD:-}"

# Future: VPS_PILOT_DOMAIN="", VPS_PILOT_ENABLE_SSL=false, VPS_PILOT_AUTO_UPDATE=false

# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
log()  { printf '\n\033[1;34m==>\033[0m %s\n' "$*"; }
ok()   { printf '\033[1;32m✓\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!\033[0m %s\n' "$*"; }
die()  { printf '\033[1;31mError:\033[0m %s\n' "$*" >&2; exit 1; }

generate_password() {
  openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32
}

detect_server_ip() {
  local ip=""
  ip="$(curl -fsSL -4 --max-time 5 ifconfig.me 2>/dev/null || true)"
  if [[ -z "$ip" ]]; then
    ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  fi
  [[ -n "$ip" ]] || die "Could not detect server IP address."
  printf '%s' "$ip"
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

resolve_release_url() {
  if [[ -n "${VPS_PILOT_RELEASE_URL}" ]]; then
    ok "Using VPS_PILOT_RELEASE_URL override"
    return 0
  fi

  log "Resolving latest release from manifest"

  local manifest_json
  manifest_json="$(curl -fsSL "${VPS_PILOT_RELEASE_MANIFEST_URL}")" \
    || die "Failed to fetch release manifest: ${VPS_PILOT_RELEASE_MANIFEST_URL}"

  eval "$(
    MANIFEST_JSON="$manifest_json" node -e "
      let data;
      try {
        data = JSON.parse(process.env.MANIFEST_JSON);
      } catch {
        process.stderr.write('Release manifest is not valid JSON\n');
        process.exit(1);
      }
      const { version, releaseUrl } = data;
      if (!version || !releaseUrl) {
        process.stderr.write('Release manifest is missing version or releaseUrl\n');
        process.exit(1);
      }
      if (typeof version !== 'string' || typeof releaseUrl !== 'string') {
        process.stderr.write('Release manifest version and releaseUrl must be strings\n');
        process.exit(1);
      }
      const normalizedUrl = releaseUrl.replace(
        /^http:\\/\\/install\\.puretik\\.com\\//,
        'https://install.puretik.com/',
      );
      console.log('VPS_PILOT_RELEASE_VERSION=' + JSON.stringify(version));
      console.log('VPS_PILOT_RELEASE_URL=' + JSON.stringify(normalizedUrl));
    "
  )" || die "Invalid release manifest: ${VPS_PILOT_RELEASE_MANIFEST_URL}"

  printf '\nResolved release:\n'
  printf 'v%s\n' "${VPS_PILOT_RELEASE_VERSION}"
  printf '%s\n' "${VPS_PILOT_RELEASE_URL}"
}

# -----------------------------------------------------------------------------
# Safety checks
# -----------------------------------------------------------------------------
preflight_checks() {
  log "Running preflight checks"

  [[ "${EUID:-$(id -u)}" -eq 0 ]] || die "This installer must be run as root."

  [[ -f /etc/os-release ]] || die "Unsupported system: missing /etc/os-release."
  # shellcheck source=/dev/null
  source /etc/os-release

  [[ "${ID:-}" == "ubuntu" ]] || die "Only Ubuntu is supported (found: ${ID:-unknown})."

  case "${VERSION_ID:-}" in
    22.04|24.04) ok "Ubuntu ${VERSION_ID}" ;;
    *) die "Only Ubuntu 22.04 and 24.04 are supported (found: ${VERSION_ID:-unknown})." ;;
  esac

  [[ "$(uname -m)" == "x86_64" ]] || die "Only x86_64 architecture is supported (found: $(uname -m))."
  ok "Architecture x86_64"
}

# -----------------------------------------------------------------------------
# System packages
# -----------------------------------------------------------------------------
install_system_packages() {
  log "Installing system packages"
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq \
    curl \
    nginx \
    postgresql \
    postgresql-contrib \
    build-essential \
    ca-certificates \
    gnupg \
  >/dev/null
  ok "System packages installed"
}

# -----------------------------------------------------------------------------
# Node.js 22 (NodeSource)
# -----------------------------------------------------------------------------
install_nodejs() {
  log "Installing Node.js 22"
  curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null
  apt-get install -y -qq nodejs >/dev/null
  require_command node
  require_command npm
  ok "Node.js $(node -v), npm $(npm -v)"
}

# -----------------------------------------------------------------------------
# pnpm + PM2
# -----------------------------------------------------------------------------
install_node_tools() {
  log "Installing pnpm and PM2"
  npm install -g pnpm pm2 >/dev/null
  require_command pnpm
  require_command pm2
  ok "pnpm $(pnpm -v), PM2 $(pm2 -v)"
}

# -----------------------------------------------------------------------------
# PostgreSQL database
# -----------------------------------------------------------------------------
setup_postgresql() {
  log "Configuring PostgreSQL"
  systemctl enable postgresql >/dev/null
  systemctl start postgresql

  local db_exists
  db_exists="$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${VPS_PILOT_DB_NAME}'" 2>/dev/null || true)"

  if [[ "$db_exists" != "1" ]]; then
    sudo -u postgres createdb "${VPS_PILOT_DB_NAME}"
    ok "Database '${VPS_PILOT_DB_NAME}' created"
  else
    ok "Database '${VPS_PILOT_DB_NAME}' already exists"
  fi

  systemctl is-active --quiet postgresql || die "PostgreSQL is not running."
  ok "PostgreSQL is active"
}

set_postgres_password() {
  local password="$1"
  sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER USER postgres WITH PASSWORD '${password}';" >/dev/null
}

# -----------------------------------------------------------------------------
# Atomic release layout:
#   /root/vps-pilot/current -> releases/vX.Y.Z
#   /root/vps-pilot/shared/  (.env, uploads)
#   /root/vps-pilot/data/    (runtime state)
# -----------------------------------------------------------------------------
vps_pilot_app_dir() {
  printf '%s' "${VPS_PILOT_INSTALL_DIR}/current"
}

link_shared_resources() {
  local release_dir="$1"
  local shared_dir="${VPS_PILOT_INSTALL_DIR}/shared"
  local data_dir="${VPS_PILOT_INSTALL_DIR}/data"

  mkdir -p "${shared_dir}/apps/api" "${shared_dir}/uploads" "${data_dir}"
  mkdir -p "${release_dir}/apps/api"

  ln -sfn "${shared_dir}/apps/api/.env" "${release_dir}/apps/api/.env"
  ln -sfn "${shared_dir}/uploads" "${release_dir}/uploads"
  ln -sfn "${data_dir}" "${release_dir}/data"
}

migrate_flat_install_if_needed() {
  [[ -d "${VPS_PILOT_INSTALL_DIR}" ]] || return 0
  [[ -L "${VPS_PILOT_INSTALL_DIR}/current" || -e "${VPS_PILOT_INSTALL_DIR}/current" ]] && return 0

  local shared_dir="${VPS_PILOT_INSTALL_DIR}/shared"
  mkdir -p "${shared_dir}/apps/api" "${shared_dir}/uploads" "${VPS_PILOT_INSTALL_DIR}/data"

  if [[ -f "${VPS_PILOT_INSTALL_DIR}/apps/api/.env" && ! -f "${shared_dir}/apps/api/.env" ]]; then
    mv "${VPS_PILOT_INSTALL_DIR}/apps/api/.env" "${shared_dir}/apps/api/.env"
  fi

  if [[ -d "${VPS_PILOT_INSTALL_DIR}/uploads" && ! -d "${shared_dir}/uploads" ]]; then
    rmdir "${shared_dir}/uploads" 2>/dev/null || true
    mv "${VPS_PILOT_INSTALL_DIR}/uploads" "${shared_dir}/uploads"
  fi
}

download_and_extract_release() {
  resolve_release_url

  log "Downloading VPS Pilot release"
  local archive staging extracted_root release_version release_dir

  archive="$(mktemp /tmp/vps-pilot.XXXXXX.tar.gz)"
  staging="$(mktemp -d /tmp/vps-pilot-release.XXXXXX)"

  migrate_flat_install_if_needed

  if command -v curl >/dev/null 2>&1; then
    curl -fsSL "${VPS_PILOT_RELEASE_URL}" -o "${archive}"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO "${archive}" "${VPS_PILOT_RELEASE_URL}"
  else
    die "curl or wget is required to download the release."
  fi

  # GNU tar on Ubuntu warns on macOS xattr headers baked into release archives.
  tar --warning=no-unknown-keyword -xzf "${archive}" -C "${staging}"
  extracted_root="$(find "${staging}" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
  [[ -n "${extracted_root}" ]] || die "Release archive is empty or invalid."

  release_version="$(node -p "require('${extracted_root}/package.json').version")"
  [[ -n "${release_version}" ]] || die "Could not read release version from package.json."

  validate_release_contents "${extracted_root}"

  mkdir -p "${VPS_PILOT_INSTALL_DIR}/releases"
  release_dir="${VPS_PILOT_INSTALL_DIR}/releases/v${release_version}"
  rm -rf "${release_dir}"
  cp -a "${extracted_root}/." "${release_dir}/"

  link_shared_resources "${release_dir}"
  ln -sfn "${release_dir}" "${VPS_PILOT_INSTALL_DIR}/current"

  rm -f "${archive}"
  rm -rf "${staging}"
  ok "Release v${release_version} installed at ${release_dir}"
  ok "Active release: ${VPS_PILOT_INSTALL_DIR}/current"
}

validate_release_contents() {
  local root="$1"
  local required=(
    scripts/install-dependencies.mjs
    scripts/lib/installDependencies.mjs
    apps/api/src/scripts/run-update.js
    ecosystem.config.js
    package.json
    pnpm-lock.yaml
  )
  local missing=()

  for path in "${required[@]}"; do
    [[ -e "${root}/${path}" ]] || missing+=("$path")
  done

  if ((${#missing[@]} > 0)); then
    die "Release archive is missing required files: ${missing[*]}"
  fi

  ok "Release archive contains required runtime scripts"
}

# -----------------------------------------------------------------------------
# Environment
# -----------------------------------------------------------------------------
ensure_release_check_url() {
  local env_file="${VPS_PILOT_INSTALL_DIR}/shared/apps/api/.env"
  local default_url="https://install.puretik.com/releases/latest.json"
  [[ -f "$env_file" ]] || return 0

  if grep -qE '^[[:space:]]*RELEASE_CHECK_URL=http://install\.puretik\.com/' "$env_file"; then
    sed -i 's|^\([[:space:]]*RELEASE_CHECK_URL=\)http://install\.puretik\.com/|\1https://install.puretik.com/|' \
      "$env_file"
    ok "Upgraded RELEASE_CHECK_URL to HTTPS in ${env_file}"
    return 0
  fi

  if grep -qE '^[[:space:]]*RELEASE_CHECK_URL=' "$env_file"; then
    return 0
  fi

  printf '\nRELEASE_CHECK_URL=%s\n' "$default_url" >>"$env_file"
  ok "Set RELEASE_CHECK_URL in ${env_file}"
}

setup_environment() {
  log "Configuring environment"
  local env_file="${VPS_PILOT_INSTALL_DIR}/shared/apps/api/.env"
  SERVER_IP="$(detect_server_ip)"

  if [[ -f "$env_file" ]]; then
    warn "Keeping existing ${env_file}"
    ensure_release_check_url
    return
  fi

  POSTGRES_PASSWORD="$(generate_password)"

  if [[ -z "${VPS_PILOT_SSH_PASSWORD}" ]]; then
    printf 'Server root SSH password (for deployments on this VPS): '
    read -rs VPS_PILOT_SSH_PASSWORD || true
    printf '\n'
  fi
  [[ -n "${VPS_PILOT_SSH_PASSWORD}" ]] || die "Server SSH password is required."

  printf 'GEMINI_API_KEY (optional, press Enter to skip): '
  read -r GEMINI_API_KEY || true
  GEMINI_API_KEY="${GEMINI_API_KEY:-}"

  set_postgres_password "${POSTGRES_PASSWORD}"

  {
    cat <<EOF
# Generated by scripts/install.sh on $(date -u +"%Y-%m-%dT%H:%M:%SZ")
DATABASE_URL="postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432/${VPS_PILOT_DB_NAME}?schema=public"
PORT=${VPS_PILOT_APP_PORT}
CORS_ORIGIN=http://${SERVER_IP}
NODE_ENV=production

SSH_USERNAME=root
RELEASE_CHECK_URL=https://install.puretik.com/releases/latest.json
EOF
    printf 'SSH_PASSWORD=%s\n' "${VPS_PILOT_SSH_PASSWORD}"
    printf 'GEMINI_API_KEY=%s\n' "${GEMINI_API_KEY}"
  } >"$env_file"

  chmod 600 "$env_file"
  ok "Created ${env_file}"
  ensure_release_check_url
}

# -----------------------------------------------------------------------------
# Application build
# -----------------------------------------------------------------------------
# Matches scripts/lib/installDependencies.mjs — devDependencies required for build.
run_in_build_pipeline() {
  (
    export NODE_ENV=development
    cd "$(vps_pilot_app_dir)"
    "$@"
  )
}

install_app_dependencies() {
  log "Installing application dependencies"
  run_in_build_pipeline node scripts/install-dependencies.mjs
  ok "Dependencies installed"
}

run_migrations() {
  log "Running database migrations"
  run_in_build_pipeline pnpm db:migrate:deploy
  ok "Migrations applied"
}

build_app() {
  log "Building application"
  run_in_build_pipeline pnpm build
  ok "Build complete"
}

# -----------------------------------------------------------------------------
# PM2
# -----------------------------------------------------------------------------
start_pm2() {
  log "Starting PM2 process"
  cd "$(vps_pilot_app_dir)"

  export VPS_PILOT_INSTALL_DIR="${VPS_PILOT_INSTALL_DIR}"
  pm2 delete "${VPS_PILOT_PM2_NAME}" >/dev/null 2>&1 || true
  pm2 start ecosystem.config.js
  pm2 save

  local startup_cmd=""
  startup_cmd="$(pm2 startup systemd -u root --hp /root 2>&1 | grep -E '^sudo' | tail -n 1 || true)"
  if [[ -n "$startup_cmd" ]]; then
    eval "$startup_cmd"
    pm2 save
    ok "PM2 startup hook installed"
  else
    warn "Could not auto-configure PM2 startup — run 'pm2 startup' manually after install"
  fi

  pm2 list | grep -q "${VPS_PILOT_PM2_NAME}.*online" || die "PM2 process '${VPS_PILOT_PM2_NAME}' is not online."
  ok "PM2 process online"
}

# -----------------------------------------------------------------------------
# nginx reverse proxy
# Future: SSL (certbot), custom server_name, HTTP→HTTPS redirect
# -----------------------------------------------------------------------------
configure_nginx() {
  log "Configuring nginx reverse proxy"
  local site_available="/etc/nginx/sites-available/${VPS_PILOT_NGINX_SITE}"
  local site_enabled="/etc/nginx/sites-enabled/${VPS_PILOT_NGINX_SITE}"

  cat >"$site_available" <<EOF
# VPS Pilot — generated by scripts/install.sh
# Future: server_name, SSL certificates, HTTPS redirect
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://localhost:${VPS_PILOT_APP_PORT};
        proxy_http_version 1.1;

        proxy_set_header Host \$host;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF

  ln -sf "$site_available" "$site_enabled"
  rm -f /etc/nginx/sites-enabled/default

  nginx -t
  systemctl enable nginx >/dev/null
  systemctl reload nginx

  systemctl is-active --quiet nginx || die "nginx is not running."
  ok "nginx reverse proxy active on port 80"
}

# -----------------------------------------------------------------------------
# Health validation
# -----------------------------------------------------------------------------
validate_installation() {
  log "Validating installation"

  pm2 list | grep -q "${VPS_PILOT_PM2_NAME}.*online" || die "Health check failed: PM2 process offline."
  ok "PM2 process online"

  systemctl is-active --quiet nginx || die "Health check failed: nginx inactive."
  ok "nginx active"

  systemctl is-active --quiet postgresql || die "Health check failed: PostgreSQL inactive."
  ok "PostgreSQL active"

  local health_url="http://127.0.0.1:${VPS_PILOT_APP_PORT}/health"
  local attempt=0
  local max_attempts=30
  local health_body=""

  while (( attempt < max_attempts )); do
    if health_body="$(curl -fsS "$health_url" 2>/dev/null)"; then
      if echo "$health_body" | grep -q '"status":"ok"'; then
        ok "/health responds with status ok"
        validate_system_version
        return 0
      fi
      if echo "$health_body" | grep -q '"database":"connected"'; then
        ok "/health database connected"
        validate_system_version
        return 0
      fi
    fi
    attempt=$((attempt + 1))
    sleep 2
  done

  die "Health check failed: ${health_url} did not return a healthy response."
}

validate_system_version() {
  local version_url="http://127.0.0.1:${VPS_PILOT_APP_PORT}/api/system/version"
  local attempt=0
  local max_attempts=30
  local version_body=""

  while (( attempt < max_attempts )); do
    if version_body="$(curl -fsS "$version_url" 2>/dev/null)"; then
      local version fetch_error
      version="$(node -e 'const d=JSON.parse(process.argv[1]); process.stdout.write(d.version||"");' "$version_body")"
      fetch_error="$(node -e 'const d=JSON.parse(process.argv[1]); process.stdout.write(d.fetchError==null?"":String(d.fetchError));' "$version_body")"

      if [[ "$version" == "0.0.0" ]]; then
        die "Version check failed: /api/system/version reported version 0.0.0"
      fi
      if [[ -n "$fetch_error" ]]; then
        die "Version check failed: fetchError=${fetch_error}"
      fi

      ok "/api/system/version reports v${version} (release metadata reachable)"
      return 0
    fi
    attempt=$((attempt + 1))
    sleep 2
  done

  die "Version check failed: ${version_url} did not return a valid response."
}

# -----------------------------------------------------------------------------
# Final output
# -----------------------------------------------------------------------------
print_success() {
  SERVER_IP="${SERVER_IP:-$(detect_server_ip)}"
  cat <<EOF

==================================================
VPS Pilot installed successfully
==================================================

Open:
http://${SERVER_IP}

PM2 Logs:
pm2 logs ${VPS_PILOT_PM2_NAME}

Install directory:
${VPS_PILOT_INSTALL_DIR}

Active release:
${VPS_PILOT_INSTALL_DIR}/current

EOF
}

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
main() {
  preflight_checks
  install_system_packages
  install_nodejs
  install_node_tools
  setup_postgresql
  download_and_extract_release
  setup_environment
  install_app_dependencies
  run_migrations
  build_app
  start_pm2
  configure_nginx
  validate_installation
  print_success
}

main "$@"
