#!/bin/bash
set -e

# ── Output helpers (color-coded UX) ───────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
ok()   { echo -e "${GREEN}✓${NC} $1"; }
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
fail() { echo -e "${RED}✗${NC} $1"; }
step() { CURRENT_PHASE="$1"; echo -e "\n${CYAN}▶ $1${NC}"; }

# On an unexpected failure, say which phase died. This only fires for genuine
# command failures — not for the explicit `exit 1` blocks below (which print
# their own detailed guidance) and not for commands inside `if` conditions.
CURRENT_PHASE="startup"
trap 'fail "Setup failed during: ${CURRENT_PHASE}"; echo "  Fix the error shown above and re-run: bash setup.sh"' ERR

# Relabel .env for SELinux (enforcing systems only) so the systemd service can
# read it. restorecon is per-file, so this is called again after any .env rewrite.
fix_selinux_env() {
  if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Enforcing" ]; then
    command -v semanage >/dev/null 2>&1 || sudo dnf install -y policycoreutils-python-utils >/dev/null 2>&1 || true
    sudo semanage fcontext -a -t etc_t "$(pwd)/.env" 2>/dev/null || sudo semanage fcontext -m -t etc_t "$(pwd)/.env" 2>/dev/null || true
    sudo restorecon -v "$(pwd)/.env" >/dev/null 2>&1 || true
    warn "SELinux enforcing — relabeled .env as etc_t so the service can read it"
  fi
}

echo -e "${CYAN}╔═══════════════════════════════════════╗${NC}"
echo -e "${CYAN}║   TuneVault Self-Hosted Setup         ║${NC}"
echo -e "${CYAN}║   Oracle DB + EBS Health Intelligence ║${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════╝${NC}"

# ── Licence acceptance (first run only) ───────────────────────────────────────
# Runs BEFORE anything is written — declining must leave the machine untouched,
# so this sits ahead of the .env copy below. The marker file makes it first-run
# only; re-running setup.sh after a config change does not re-prompt.
LICENSE_FILE="$(pwd)/LICENSE.txt"
LICENSE_MARKER="$(pwd)/.license-accepted"

if [ ! -f "$LICENSE_MARKER" ]; then
  step "Licence agreement"

  if [ ! -f "$LICENSE_FILE" ]; then
    fail "LICENSE.txt not found at ${LICENSE_FILE}"
    echo "  This release looks incomplete. Re-download from https://tunevault.app/download"
    exit 1
  fi

  echo ""
  echo "  TuneVault is proprietary software. It is licensed, not sold, under the"
  echo "  End-User Licence Agreement at:"
  echo ""
  echo "      ${LICENSE_FILE}"
  echo ""
  echo "  In short: internal use on servers you own or operate; no redistribution,"
  echo "  resale, sublicensing or reverse engineering; provided \"as is\" with no"
  echo "  warranty; liability capped at fees paid in the last 12 months, and zero"
  echo "  for unpaid installs. All control actions run at your direction and under"
  echo "  your supervision — you remain responsible for actions taken on your"
  echo "  databases."
  echo ""
  echo "  That summary is not the agreement. Read it in full:  less LICENSE.txt"
  echo ""

  if [ "${TUNEVAULT_ACCEPT_LICENSE:-}" = "yes" ]; then
    ACCEPT="yes"
    warn "Licence accepted non-interactively via TUNEVAULT_ACCEPT_LICENSE=yes"
  elif [ -t 0 ]; then
    printf "  Type 'yes' to accept the licence and continue: "
    # `|| true` so Ctrl-D (read returns non-zero on EOF) falls through to the
    # decline path below instead of tripping `set -e` and the ERR trap.
    read -r ACCEPT || true
  else
    fail "Cannot prompt — setup.sh is not attached to a terminal."
    echo "  Re-run it interactively, or accept explicitly:"
    echo "    TUNEVAULT_ACCEPT_LICENSE=yes bash setup.sh"
    exit 1
  fi

  if [ "${ACCEPT:-}" != "yes" ]; then
    fail "Licence not accepted — install aborted. Nothing on this machine was changed."
    exit 1
  fi

  date -u +"TuneVault licence accepted %Y-%m-%dT%H:%M:%SZ" > "$LICENSE_MARKER"
  ok "Licence accepted — recorded in .license-accepted"
fi

# ── .env ──────────────────────────────────────────────────────────────────────
if [ ! -f .env ]; then
  cp .env.example .env
  ok "Created .env from template"
  echo ""
  warn "Required: TUNEVAULT_LICENSE_KEY and ADMIN_EMAILS"
  echo "  Optional: OPENAI_API_KEY and ANTHROPIC_API_KEY — without them, AI health"
  echo "  summaries and TuneBot are disabled, but all monitoring/ops features work normally."
  echo ""
  echo "Edit .env, then run: bash setup.sh"
  exit 0
fi

# Auto-generate SESSION_SECRET if not set
if grep -q "SESSION_SECRET=change_this" .env 2>/dev/null; then
  SECRET=$(openssl rand -hex 32)
  sed -i "s|SESSION_SECRET=change_this_to_a_random_64_char_string|SESSION_SECRET=${SECRET}|" .env
  ok "Generated SESSION_SECRET"
fi

# Auto-generate ENCRYPTION_KEY if blank/absent (same pattern as SESSION_SECRET above).
# Without it the app runs until the first credential is encrypted, then exits(1)
# (crypto-utils.js requires it whenever NODE_ENV=production, which docker-compose.yml
# sets) — i.e. install looks fine, then dies when the user saves their first Oracle
# connection. Must be exactly 64 hex chars = 32 bytes for AES-256-GCM.
# Only ever generated when EMPTY: regenerating over an existing key would make every
# already-stored credential undecryptable.
if grep -qE "^ENCRYPTION_KEY=$" .env 2>/dev/null; then
  ENCKEY=$(openssl rand -hex 32)
  sed -i "s|^ENCRYPTION_KEY=$|ENCRYPTION_KEY=${ENCKEY}|" .env
  ok "Generated ENCRYPTION_KEY"
elif ! grep -q "^ENCRYPTION_KEY=" .env 2>/dev/null; then
  ENCKEY=$(openssl rand -hex 32)
  echo "ENCRYPTION_KEY=${ENCKEY}" >> .env
  ok "Generated ENCRYPTION_KEY"
fi

# Self-hosted installs must always run in self-hosted mode — set it if a pre-existing
# .env is missing the flag (new .env files already get it from .env.example).
if ! grep -q "^TUNEVAULT_SELF_HOSTED=" .env 2>/dev/null; then
  echo "TUNEVAULT_SELF_HOSTED=true" >> .env
  ok "Set TUNEVAULT_SELF_HOSTED=true"
fi

# .env is finalized here — relabel it for SELinux before anything reads it.
fix_selinux_env

# Load all env vars from .env into the current shell so DATABASE_URL, DB_PASSWORD,
# etc. are available to node migrate.js / node server.js without manual exports.
set -a
source .env
set +a

# ── Docker path ────────────────────────────────────────────────────────────────
if command -v docker >/dev/null 2>&1 && command -v docker-compose >/dev/null 2>&1; then
  step "Starting TuneVault (Docker Compose)"
  ok "Docker found — using Docker Compose"
  docker-compose up -d --build
  ok "TuneVault is running at ${APP_URL:-http://localhost:3000}"
  exit 0
fi

step "Checking system requirements"
warn "Docker not found — using direct Node.js install"

# Detect the distro + major version so we can pick the right package commands.
OS_ID=""; OS_MAJOR=""; OS_PRETTY="unknown OS"
if [ -f /etc/os-release ]; then
  . /etc/os-release
  OS_ID="${ID:-}"
  OS_MAJOR="${VERSION_ID%%.*}"
  OS_PRETTY="${PRETTY_NAME:-${ID:-} ${VERSION_ID:-}}"
fi
ok "Detected OS: ${OS_PRETTY}"

case "$OS_ID" in
  ol|rhel|centos|rocky|almalinux|fedora)
    # RHEL family. Direct install supports 8 and 9 (7 can't run Node.js 18+).
    if [ "${OS_MAJOR:-0}" -lt 7 ] 2>/dev/null; then
      fail "Unsupported ${OS_PRETTY}. TuneVault self-hosted requires Oracle Linux / RHEL 8 or 9."
      echo "  Install Docker and re-run to use the Docker Compose path, or upgrade the OS."
      exit 1
    fi
    if [ "${OS_MAJOR}" = "7" ]; then
      fail "Oracle Linux 7 cannot run Node.js 18+ (requires glibc 2.25+, OL7 has 2.17)"
      echo ""
      echo "Options:"
      echo "  1. Use Docker Compose on this server:"
      echo "     yum install -y docker && systemctl start docker"
      echo "     Then run: bash setup.sh"
      echo "  2. Install TuneVault on an OL8/OL9 server instead"
      echo "     (recommended — TuneVault control plane runs separately from Oracle DB servers)"
      exit 1
    fi
    if [ "${OS_MAJOR:-0}" -gt 9 ] 2>/dev/null; then
      warn "${OS_PRETTY} is newer than tested (OL/RHEL 8–9). Continuing — report issues if package steps fail."
    fi
    ;;
  debian|ubuntu)
    : ;; # handled by the apt branches below
  "" )
    warn "Could not read /etc/os-release — assuming a generic Linux. Package install may need manual steps." ;;
  *)
    warn "${OS_PRETTY} is not an officially supported distro (Oracle Linux / RHEL / Debian / Ubuntu)."
    echo "  Continuing best-effort; if yum/apt steps fail, install Node.js 18+ and PostgreSQL manually." ;;
esac

# Check Node.js
if ! command -v node >/dev/null 2>&1; then
  warn "Node.js not found — installing Node.js 20"
  if command -v yum >/dev/null 2>&1; then
    curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - 2>/dev/null
    sudo yum install -y nodejs
  elif command -v apt-get >/dev/null 2>&1; then
    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - 2>/dev/null
    sudo apt-get install -y nodejs
  else
    fail "Please install Node.js 20+ manually from https://nodejs.org"
    exit 1
  fi
fi

NODE_VER=$(node --version | cut -d. -f1 | tr -d 'v')
if [ "$NODE_VER" -lt 18 ]; then
  fail "Node.js 18+ required. Current: $(node --version)"
  exit 1
fi
ok "Node.js $(node --version)"

# ── PostgreSQL ────────────────────────────────────────────────────────────────
step "Setting up PostgreSQL"
# Binary + service names — overridden below if we install PostgreSQL 15 from PGDG.
PSQL="psql"
PG_SERVICE="postgresql"
PGDG_VER=15

if ! command -v psql >/dev/null 2>&1; then
  warn "PostgreSQL not found — installing"
  if command -v yum >/dev/null 2>&1; then
    # OL/RHEL 8/9: install PostgreSQL ${PGDG_VER} from the official PGDG repo
    # (the distro default is too old for TuneVault). PGDG builds for EL8/9.
    ARCH=$(uname -m)
    echo "  Adding PGDG repo for EL${OS_MAJOR} (${ARCH}) — PostgreSQL ${PGDG_VER}..."
    sudo yum install -y "https://download.postgresql.org/pub/repos/yum/reporpms/EL-${OS_MAJOR}-${ARCH}/pgdg-redhat-repo-latest.noarch.rpm"
    # EL8+ ships a conflicting built-in postgresql module — disable it so PGDG packages win.
    if command -v dnf >/dev/null 2>&1; then
      sudo dnf -qy module disable postgresql 2>/dev/null || true
    fi
    sudo yum install -y "postgresql${PGDG_VER}-server" "postgresql${PGDG_VER}"
    sudo "/usr/pgsql-${PGDG_VER}/bin/postgresql-${PGDG_VER}-setup" initdb 2>/dev/null || true
    sudo systemctl enable "postgresql-${PGDG_VER}"
    sudo systemctl start "postgresql-${PGDG_VER}"
    # PGDG installs under /usr/pgsql-${PGDG_VER}/bin (not on PATH) and runs as postgresql-${PGDG_VER}.
    PSQL="/usr/pgsql-${PGDG_VER}/bin/psql"
    PG_SERVICE="postgresql-${PGDG_VER}"
    export PATH="/usr/pgsql-${PGDG_VER}/bin:$PATH"
  elif command -v apt-get >/dev/null 2>&1; then
    sudo apt-get install -y postgresql
    sudo systemctl enable postgresql
    sudo systemctl start postgresql
  fi
fi
ok "PostgreSQL ready (${PG_SERVICE})"

# ── pg_hba.conf auth fixes (the manual fixes we've had to apply on fresh OLAM boxes) ──
# Locate pg_hba.conf via the filesystem — NOT via `psql SHOW hba_file`, which would
# itself hang/fail if local auth isn't peer/trust yet.
PG_HBA=$(sudo find /var/lib/pgsql -maxdepth 3 -name pg_hba.conf -path '*/data/*' 2>/dev/null | head -1)
[ -z "$PG_HBA" ] && PG_HBA=$(sudo find /etc/postgresql -maxdepth 4 -name pg_hba.conf 2>/dev/null | head -1)
if [ -n "$PG_HBA" ] && sudo test -f "$PG_HBA"; then
  # 1. Local socket auth → peer, so the installer can run psql as the postgres OS
  #    user without a password. Must happen BEFORE any `sudo su - postgres -c psql`.
  if sudo grep -qE '^[[:space:]]*local[[:space:]]+all[[:space:]]+all[[:space:]]+(md5|password|scram-sha-256)' "$PG_HBA"; then
    sudo sed -ri 's#^([[:space:]]*local[[:space:]]+all[[:space:]]+all[[:space:]]+)(md5|password|scram-sha-256)#\1peer#' "$PG_HBA"
    sudo systemctl restart "$PG_SERVICE" 2>/dev/null || sudo systemctl restart postgresql 2>/dev/null || true
    warn "Fixed local auth: md5 → peer in pg_hba.conf (installer can now manage the DB without a password)"
  else
    ok "Local auth already OK (peer/trust)"
  fi
  # 2. Localhost TCP auth → md5, so the app can connect over 127.0.0.1 with a password.
  #    (md5 in pg_hba also accepts SCRAM-stored passwords on PG10+.)
  sudo sed -ri 's|^(host[[:space:]]+all[[:space:]]+all[[:space:]]+127\.0\.0\.1/32[[:space:]]+)[a-z0-9-]+|\1md5|' "$PG_HBA"
  sudo sed -ri 's|^(host[[:space:]]+all[[:space:]]+all[[:space:]]+::1/128[[:space:]]+)[a-z0-9-]+|\1md5|' "$PG_HBA"
  sudo systemctl reload "$PG_SERVICE" 2>/dev/null || sudo systemctl reload postgresql 2>/dev/null || true
  ok "pg_hba.conf configured (local=peer, localhost TCP=md5): ${PG_HBA}"
else
  warn "Could not locate pg_hba.conf — skipping auth config; the connection smoke test below will catch any problem"
fi

# ── Create tunevault role + database (peer auth via the postgres OS user) ─────
if ! sudo su - postgres -s /bin/bash -c "${PSQL} -tAc \"SELECT 1 FROM pg_roles WHERE rolname='tunevault'\"" 2>/dev/null | grep -q 1; then
  sudo su - postgres -s /bin/bash -c "${PSQL} -c \"CREATE USER tunevault WITH PASSWORD '${DB_PASSWORD}';\""
  ok "Created tunevault database user"
fi

if ! sudo su - postgres -s /bin/bash -c "${PSQL} -tAc \"SELECT 1 FROM pg_database WHERE datname='tunevault'\"" 2>/dev/null | grep -q 1; then
  sudo su - postgres -s /bin/bash -c "${PSQL} -c \"CREATE DATABASE tunevault OWNER tunevault;\""
  ok "Created tunevault database"
fi

# ── DATABASE_URL → .env (write or update), then reload env ────────────────────
if grep -q "^DATABASE_URL=" .env; then
  sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://tunevault:${DB_PASSWORD}@localhost:5432/tunevault|" .env
else
  echo "DATABASE_URL=postgresql://tunevault:${DB_PASSWORD}@localhost:5432/tunevault" >> .env
fi
# .env was just rewritten — relabel it again (restorecon is per-file).
fix_selinux_env
set -a
source .env
set +a
ok "DATABASE_URL written to .env"

# ── Connection smoke test — fail here (clear message) instead of at migrate ───
echo "  Verifying database connection..."
if ! PGPASSWORD="${DB_PASSWORD}" "$PSQL" -h localhost -U tunevault -d tunevault -w -tAc 'SELECT 1' >/dev/null 2>&1; then
  fail "Could not connect to PostgreSQL as 'tunevault' over localhost TCP (the same path the app uses)."
  echo "  Tried: ${PSQL} -h localhost -U tunevault -d tunevault -c 'SELECT 1'"
  echo "  Common causes:"
  echo "    - pg_hba.conf still rejects password auth on 127.0.0.1 (needs md5/scram)"
  echo "    - DB_PASSWORD in .env doesn't match the 'tunevault' role's password"
  echo "    - PostgreSQL isn't listening on localhost:5432"
  echo "  Fix the above and re-run: bash setup.sh"
  exit 1
fi
ok "Database connection OK"

# ── Dependencies ───────────────────────────────────────────────────────────────
step "Installing Node.js dependencies"
npm install --production 2>/dev/null
ok "Dependencies installed"

# ── Migrations ─────────────────────────────────────────────────────────────────
# migrate.js applies the baseline schema (db/baseline-schema.sql) on a fresh DB,
# then the delta migrations — no server bootstrap needed.
step "Running database migrations"
node migrate.js
ok "Database ready"

# ── systemd service (loads .env via EnvironmentFile so node has all env vars) ─
step "Starting TuneVault service"
APP_DIR=$(pwd)
NODE_BIN=$(which node)
sudo tee /etc/systemd/system/tunevault.service > /dev/null <<EOF
[Unit]
Description=TuneVault Oracle Health Intelligence
After=network.target postgresql.service postgresql-15.service

[Service]
Type=simple
User=root
WorkingDirectory=${APP_DIR}
EnvironmentFile=${APP_DIR}/.env
ExecStart=${NODE_BIN} ${APP_DIR}/server.js
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable tunevault
sudo systemctl start tunevault

# ── Final status ───────────────────────────────────────────────────────────────
sleep 5
if systemctl is-active --quiet tunevault; then
  URL="${APP_URL:-http://localhost:3000}"
  IP=$(hostname -I 2>/dev/null | awk '{print $1}')
  echo ""
  echo -e "${GREEN}╔═══════════════════════════════════════╗${NC}"
  echo -e "${GREEN}║   ✓ TuneVault is running!             ║${NC}"
  echo -e "${GREEN}╚═══════════════════════════════════════╝${NC}"
  echo ""
  ok "Service is active (systemd unit: tunevault.service)"
  ok "Open in your browser: ${URL}"
  if [ -n "$IP" ] && echo "$URL" | grep -q localhost; then
    echo "    (from another machine on the network: http://${IP}:3000)"
  fi
  echo ""
  echo -e "${CYAN}Next steps:${NC}"
  echo "  1. Open ${URL} in your browser"
  # Only promise an email if a provider is actually configured — otherwise the sign-in
  # link is printed to the service log (server.js logSignInLinkToServerLog) and telling
  # them to check their inbox is a dead end.
  if grep -qE "^RESEND_API_KEY=.+" .env 2>/dev/null; then
    echo "  2. Enter your email — the sign-in link is emailed to you"
  else
    echo "  2. Enter your email, then grab the sign-in link from the log:"
    echo "       journalctl -u tunevault -f      (or: docker compose logs -f app)"
    echo "     No email provider is configured, so the link is printed there."
    echo "     Set RESEND_API_KEY in .env and restart to receive it by email instead."
  fi
  echo "  3. Go to Add Server to connect your Oracle DB"
  echo ""
  echo "  Manage: systemctl status tunevault    Logs: journalctl -u tunevault -f"
else
  fail "TuneVault service failed to start (install completed, but the service isn't active)"
  echo "  See what went wrong:  journalctl -u tunevault -n 50"
  sudo systemctl status tunevault --no-pager || true
  exit 1
fi
