#!/usr/bin/env bash
#
# Forge Server Control Script (appctl.sh)
# One-stop management for Forge on Linux servers.
#
# Usage: ./appctl.sh <command> [options]
#
# Commands:
#   start       Start Forge server
#   stop        Stop Forge server
#   restart     Restart Forge server
#   status      Check if Forge is running
#   logs        Tail Forge logs
#   build       Build server package from source (requires Node.js + pnpm)
#   init        First-time setup (create dirs, install systemd service)
#   env         Show current environment configuration
#   backup      Backup database
#   help        Show this help message
#

set -euo pipefail

# ── Configuration ──────────────────────────────────────────────

# Installation directory (where server.js lives)
INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"

# App name for process identification
APP_NAME="forge"

# ── Load environment overrides ─────────────────────────────────

# 1. .env in install dir
if [[ -f "${INSTALL_DIR}/.env" ]]; then
  set -a
  source "${INSTALL_DIR}/.env"
  set +a
fi

# 2. .env.local in install dir (preferred for local overrides)
if [[ -f "${INSTALL_DIR}/.env.local" ]]; then
  set -a
  source "${INSTALL_DIR}/.env.local"
  set +a
fi

# 3. System-level defaults (e.g. /etc/default/forge)
if [[ -f /etc/default/forge ]]; then
  set -a
  source /etc/default/forge
  set +a
fi

# Default environment. Applied after env files so empty values fall back safely.
: "${APP_ENTRY:=server.js}"
: "${PORT:=3000}"
: "${HOSTNAME:=0.0.0.0}"
: "${NODE_ENV:=production}"
: "${FORGE_DATA_DIR:=${INSTALL_DIR}/../forge-data}"
: "${FORGE_WORKSPACES_ROOT:=${INSTALL_DIR}/workspaces}"

# PID file
PID_FILE="${INSTALL_DIR}/${APP_NAME}.pid"
LOG_FILE="${INSTALL_DIR}/${APP_NAME}.log"

# ── Helper functions ────────────────────────────────────────────

color_red='\033[0;31m'
color_green='\033[0;32m'
color_yellow='\033[0;33m'
color_cyan='\033[0;36m'
color_reset='\033[0m'

log_info()  { echo -e "${color_cyan}[INFO]${color_reset}  $*"; }
log_ok()    { echo -e "${color_green}[OK]${color_reset}    $*"; }
log_warn()  { echo -e "${color_yellow}[WARN]${color_reset}  $*"; }
log_error() { echo -e "${color_red}[ERROR]${color_reset} $*"; }

get_pid() {
  if [[ -f "$PID_FILE" ]]; then
    local pid
    pid=$(cat "$PID_FILE" 2>/dev/null)
    if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
      echo "$pid"
      return 0
    fi
    rm -f "$PID_FILE"
  fi
  # Fallback: find by port
  local pid
  pid=$(lsof -ti :"$PORT" 2>/dev/null | head -1)
  if [[ -n "$pid" ]]; then
    echo "$pid"
    return 0
  fi
  return 1
}

is_running() {
  local pid
  pid=$(get_pid) && return 0 || return 1
}

wait_for_start() {
  local max_wait=30
  local elapsed=0
  while (( elapsed < max_wait )); do
    if curl -sf "http://localhost:${PORT}" > /dev/null 2>&1; then
      return 0
    fi
    sleep 1
    ((elapsed += 1))
  done
  return 1
}

ensure_dirs() {
  mkdir -p "$FORGE_DATA_DIR" "$FORGE_WORKSPACES_ROOT"
}

resolve_app_entry() {
  local required="${1:-true}"

  if [[ -f "${INSTALL_DIR}/${APP_ENTRY}" ]]; then
    return
  fi

  if [[ -f "${INSTALL_DIR}/dist-server/server.js" ]]; then
    APP_ENTRY="dist-server/server.js"
    return
  fi

  if [[ "$required" != "true" ]]; then
    return 1
  fi

  log_error "Server entry not found. Run './appctl.sh build' first, or start from dist-server/."
  exit 1
}

find_node() {
  # Prefer explicit NODE_BIN, then common locations
  if [[ -n "${NODE_BIN:-}" ]] && [[ -x "$NODE_BIN" ]]; then
    echo "$NODE_BIN"
    return
  fi
  for candidate in /usr/bin/node /usr/local/bin/node /opt/homebrew/bin/node; do
    if [[ -x "$candidate" ]]; then
      echo "$candidate"
      return
    fi
  done
  which node 2>/dev/null && return
  log_error "Node.js not found. Install Node.js 22+ or set NODE_BIN."
  exit 1
}

# ── Commands ────────────────────────────────────────────────────

cmd_start() {
  if is_running; then
    local pid
    pid=$(get_pid)
    log_warn "Already running (PID: ${pid}, port: ${PORT})"
    return 0
  fi

  ensure_dirs

  local node_bin
  node_bin=$(find_node)
  resolve_app_entry true

  log_info "Starting Forge server..."
  log_info "  Node:     ${node_bin}"
  log_info "  Entry:    ${APP_ENTRY}"
  log_info "  Port:     ${PORT}"
  log_info "  Host:     ${HOSTNAME}"
  log_info "  Data:     ${FORGE_DATA_DIR}"
  log_info "  Workspaces: ${FORGE_WORKSPACES_ROOT}"

  cd "$INSTALL_DIR"

  nohup "$node_bin" "$APP_ENTRY" >> "$LOG_FILE" 2>&1 &
  local pid=$!
  echo "$pid" > "$PID_FILE"

  if wait_for_start; then
    log_ok "Forge started (PID: ${pid})"
    log_ok "Access: http://localhost:${PORT}"
  else
    log_error "Failed to start. Check ${LOG_FILE}"
    cmd_logs 20
    rm -f "$PID_FILE"
    exit 1
  fi
}

cmd_stop() {
  if ! is_running; then
    log_warn "Not running"
    rm -f "$PID_FILE"
    return 0
  fi

  local pid
  pid=$(get_pid)
  log_info "Stopping Forge (PID: ${pid})..."
  kill "$pid" 2>/dev/null || true

  # Wait for graceful shutdown
  local max_wait=10
  local elapsed=0
  while (( elapsed < max_wait )); do
    if ! kill -0 "$pid" 2>/dev/null; then
      log_ok "Stopped"
      rm -f "$PID_FILE"
      return 0
    fi
    sleep 1
    ((elapsed += 1))
  done

  # Force kill if still running
  log_warn "Graceful shutdown timed out, force killing..."
  kill -9 "$pid" 2>/dev/null || true
  rm -f "$PID_FILE"
  log_ok "Force stopped"
}

cmd_restart() {
  log_info "Restarting Forge..."
  cmd_stop
  sleep 1
  cmd_start
}

cmd_status() {
  echo -e "${color_cyan}━━ Forge Server Status ━━${color_reset}"
  echo ""

  if is_running; then
    local pid
    pid=$(get_pid)
    echo -e "  Status:    ${color_green}RUNNING${color_reset}"
    echo "  PID:       ${pid}"
    echo "  Port:      ${PORT}"
    echo "  Uptime:    $(ps -o etime= -p "$pid" 2>/dev/null || echo 'unknown')"
    echo "  Memory:    $(ps -o rss= -p "$pid" 2>/dev/null | awk '{printf "%.1f MB", $1/1024}' || echo 'unknown')"
  else
    echo -e "  Status:    ${color_red}STOPPED${color_reset}"
  fi

  echo ""
  echo "  Install:   ${INSTALL_DIR}"
  echo "  Node:      $(find_node)"
  echo "  Entry:     ${APP_ENTRY}"
  echo "  Data:      ${FORGE_DATA_DIR}"
  echo "  Workspaces:${FORGE_WORKSPACES_ROOT}"
  echo "  Log:       ${LOG_FILE}"

  # Database info
  if [[ -f "${FORGE_DATA_DIR}/forge.db" ]]; then
    local db_size
    db_size=$(du -h "${FORGE_DATA_DIR}/forge.db" 2>/dev/null | cut -f1)
    echo "  Database:  ${db_size}"
  else
    echo "  Database:  (not found)"
  fi

  # Disk usage
  echo ""
  echo -e "${color_cyan}━━ Disk Usage ━━${color_reset}"
  df -h "$INSTALL_DIR" 2>/dev/null | tail -1 | awk '{printf "  Install disk: %s used / %s total (%s avail)\n", $3, $2, $4}' || true
  df -h "$FORGE_DATA_DIR" 2>/dev/null | tail -1 | awk '{printf "  Data disk:    %s used / %s total (%s avail)\n", $3, $2, $4}' || true
}

cmd_logs() {
  local lines="${1:-100}"
  if [[ ! -f "$LOG_FILE" ]]; then
    log_warn "No log file at ${LOG_FILE}"
    return
  fi
  if (( lines > 0 )); then
    tail -n "$lines" "$LOG_FILE"
  else
    tail -f "$LOG_FILE"
  fi
}

cmd_build() {
  log_info "Building Forge server package..."
  cd "${INSTALL_DIR}"

  if ! command -v pnpm &>/dev/null; then
    log_error "pnpm not found. Install: npm install -g pnpm"
    exit 1
  fi

  pnpm install --frozen-lockfile 2>/dev/null || pnpm install
  pnpm build:server

  log_ok "Build complete! Output: dist-server/"
  log_ok "Deploy: scp -r dist-server/ user@server:${INSTALL_DIR}"
}

cmd_init() {
  log_info "Initializing Forge server environment..."

  # Create system user if needed
  if ! id "$APP_NAME" &>/dev/null; then
    log_info "Creating system user '${APP_NAME}'..."
    sudo useradd -r -m -s /bin/bash "$APP_NAME" 2>/dev/null || true
  fi

  # Create directories
  ensure_dirs

  # Set ownership
  sudo chown -R "$APP_NAME:$APP_NAME" "$FORGE_DATA_DIR" "$FORGE_WORKSPACES_ROOT" 2>/dev/null || true

  # Install systemd service
  if [[ -f "${INSTALL_DIR}/deploy/forge.service" ]]; then
    log_info "Installing systemd service..."
    sudo cp "${INSTALL_DIR}/deploy/forge.service" /etc/systemd/system/
    sudo systemctl daemon-reload
    log_ok "Systemd service installed. Use: systemctl start forge"
  else
    log_warn "No forge.service found in deploy/. Using appctl.sh for process management."
  fi

  # Create system env file
  if [[ ! -f /etc/default/forge ]]; then
    log_info "Creating /etc/default/forge..."
    sudo tee /etc/default/forge > /dev/null <<EOF
# Forge environment variables
PORT=${PORT}
HOSTNAME=0.0.0.0
NODE_ENV=production
FORGE_DATA_DIR=${FORGE_DATA_DIR}
FORGE_WORKSPACES_ROOT=${FORGE_WORKSPACES_ROOT}
EOF
    log_ok "Created /etc/default/forge"
  fi

  log_ok "Initialization complete!"
  echo ""
  log_info "Next steps:"
  log_info "  1. Edit /etc/default/forge to customize environment"
  log_info "  2. Start: ./appctl.sh start  (or: systemctl start forge)"
  log_info "  3. Open: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):${PORT}"
}

cmd_env() {
  echo -e "${color_cyan}━━ Forge Environment ━━${color_reset}"
  echo ""
  echo "  INSTALL_DIR=${INSTALL_DIR}"
  echo "  NODE_BIN=$(find_node)"
  if resolve_app_entry false; then
    echo "  APP_ENTRY=${APP_ENTRY}"
  else
    echo "  APP_ENTRY=${APP_ENTRY} (not found; run './appctl.sh build' first)"
  fi
  echo "  PORT=${PORT}"
  echo "  HOSTNAME=${HOSTNAME}"
  echo "  NODE_ENV=${NODE_ENV}"
  echo "  FORGE_DATA_DIR=${FORGE_DATA_DIR}"
  echo "  FORGE_WORKSPACES_ROOT=${FORGE_WORKSPACES_ROOT}"
  echo "  PID_FILE=${PID_FILE}"
  echo "  LOG_FILE=${LOG_FILE}"
  echo ""
  if [[ -f "${INSTALL_DIR}/.env" ]]; then
    echo -e "  ${color_green}Loaded from:${color_reset} ${INSTALL_DIR}/.env"
  fi
  if [[ -f "${INSTALL_DIR}/.env.local" ]]; then
    echo -e "  ${color_green}Loaded from:${color_reset} ${INSTALL_DIR}/.env.local"
  fi
  if [[ -f /etc/default/forge ]]; then
    echo -e "  ${color_green}Loaded from:${color_reset} /etc/default/forge"
  fi
}

cmd_backup() {
  local db_path="${FORGE_DATA_DIR}/forge.db"
  if [[ ! -f "$db_path" ]]; then
    log_error "Database not found at ${db_path}"
    return 1
  fi

  local timestamp
  timestamp=$(date +%Y%m%d_%H%M%S)
  local backup_dir="${INSTALL_DIR}/backups"
  mkdir -p "$backup_dir"

  # Stop briefly for consistent backup (SQLite)
  local was_running=false
  if is_running; then
    was_running=true
    log_info "Pausing server for backup consistency..."
    cmd_stop
  fi

  cp "${db_path}" "${backup_dir}/forge_${timestamp}.db"
  # Also backup WAL/SHM if present
  [[ -f "${db_path}-shm" ]] && cp "${db_path}-shm" "${backup_dir}/forge_${timestamp}.db-shm"
  [[ -f "${db_path}-wal" ]] && cp "${db_path}-wal" "${backup_dir}/forge_${timestamp}.db-wal"

  if $was_running; then
    cmd_start
  fi

  local db_size
  db_size=$(du -h "${backup_dir}/forge_${timestamp}.db" | cut -f1)
  log_ok "Backup saved: ${backup_dir}/forge_${timestamp}.db (${db_size})"

  # Keep only last 7 backups
  local count
  count=$(ls -1 "${backup_dir}"/forge_*.db 2>/dev/null | wc -l)
  if (( count > 7 )); then
    local to_remove=$(( count - 7 ))
    ls -1t "${backup_dir}"/forge_*.db | tail -n "$to_remove" | xargs rm -f
    log_info "Cleaned ${to_remove} old backup(s), kept last 7"
  fi
}

cmd_help() {
  cat <<'HELP'
Forge Server Control Script (appctl.sh)

Usage: ./appctl.sh <command> [options]

Commands:
  start          Start Forge server
  stop           Stop Forge server
  restart        Restart Forge server
  status         Show server status, PID, memory, disk usage
  logs [N]       Show last N lines of log (default: 100, 0 = follow)
  build          Build server package from source (requires pnpm)
  init           First-time setup (create dirs, user, systemd service)
  env            Show current environment configuration
  backup         Backup SQLite database (pauses server briefly)
  help           Show this help message

Environment:
  Override via .env, .env.local in install dir, or /etc/default/forge

  APP_ENTRY              Server entry (default: server.js, fallback: dist-server/server.js)
  PORT                   HTTP port (default: 3000)
  HOSTNAME               Bind address (default: 0.0.0.0)
  NODE_ENV               Node environment (default: production)
  FORGE_DATA_DIR         Database + uploads directory
  FORGE_WORKSPACES_ROOT  Default workspace root directory
  NODE_BIN               Path to Node.js binary (auto-detected if unset)

Examples:
  ./appctl.sh start                          # Start on port 3000
  PORT=8080 ./appctl.sh start                # Start on custom port
  ./appctl.sh logs 0                         # Follow logs in real-time
  ./appctl.sh backup                         # Backup database
  ./appctl.sh init                           # First-time server setup
  ./appctl.sh status                         # Check health

Directory layout:
  /opt/forge/                    ← INSTALL_DIR (this script lives here)
  /opt/forge-data/              ← FORGE_DATA_DIR (database, uploads)
  /opt/forge/workspaces/        ← FORGE_WORKSPACES_ROOT (project dirs)
  /opt/forge/backups/           ← Database backups
HELP
}

# ── Main ────────────────────────────────────────────────────────

cmd="${1:-help}"
shift || true

case "$cmd" in
  start)   cmd_start ;;
  stop)    cmd_stop ;;
  restart) cmd_restart ;;
  status)  cmd_status ;;
  logs)    cmd_logs "${1:-100}" ;;
  build)   cmd_build ;;
  init)    cmd_init ;;
  env)     cmd_env ;;
  backup)  cmd_backup ;;
  help|--help|-h)
    cmd_help
    ;;
  *)
    log_error "Unknown command: ${cmd}"
    echo ""
    cmd_help
    exit 1
    ;;
esac
