#!/bin/bash
# Start a dev instance of CTM + Wall-E on separate ports with a DB snapshot.
# Primary instance stays untouched. Ctrl+C to stop.
#
# Usage:
#   bash bin/dev.sh              # Pick ports and start with fresh empty DBs
#   bash bin/dev.sh --refresh    # Pick ports and copy production DBs before starting
#   bash bin/dev.sh --fresh      # Pick ports and reset to empty DBs before starting
#   bash bin/dev.sh --reuse      # Pick ports and reuse existing DBs in WALLE_DEV_DIR
#   bash bin/dev.sh --refresh --no-images  # Copy DBs only; skip image assets
#   DEV_CTM_PORT=4456 bash bin/dev.sh  # Use an explicit port pair

set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# Resolve the pinned Node binary (ignores ambient PATH) so a dev instance can
# never run under a different Node major and rebuild the shared better-sqlite3
# to a mismatched ABI — which would crash-loop the primary daemon.
NODE_BIN="$(bash "$ROOT/bin/node-bin.sh")" || { echo "[dev] could not resolve the pinned Node (see bin/node-bin.sh)" >&2; exit 1; }
MODE="fresh"
PRINT_CONFIG=0
COPY_IMAGES=1
KEEP_DATA_ON_EXIT="${CTM_DEV_KEEP_DATA:-0}"

usage() {
  cat <<'EOF'
Usage: bash bin/dev.sh [--fresh|--refresh|--reuse] [--no-images] [--keep-data] [--print-config]

Starts an isolated CTM/Wall-E dev instance on random non-primary ports.

Options:
  --fresh        Reset dev SQLite DBs before start (default)
  --refresh      Snapshot production CTM/Wall-E DBs before start
  --reuse        Reuse existing DBs in WALLE_DEV_DIR and keep data on exit
  --no-images    With --refresh, skip copying CTM image assets
  --keep-data    Do not remove the dev data dir when the launcher exits
  --print-config Print resolved ports/data dir and exit
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --fresh)
      MODE="fresh"
      ;;
    --refresh)
      MODE="refresh"
      ;;
    --reuse|--keep)
      MODE="reuse"
      KEEP_DATA_ON_EXIT=1
      ;;
    --no-images)
      COPY_IMAGES=0
      ;;
    --keep-data)
      KEEP_DATA_ON_EXIT=1
      ;;
    --print-config)
      PRINT_CONFIG=1
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      echo "[dev] Unknown option: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
  shift
done

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

assert_safe_port() {
  local name="$1"
  local port="$2"
  if ! is_integer "$port"; then
    echo "[dev] $name must be an integer port, got: $port" >&2
    exit 1
  fi
  if [[ "$port" == "3456" || "$port" == "3457" ]]; then
    echo "[dev] Refusing to use primary CTM/Wall-E port $port" >&2
    exit 1
  fi
}

port_is_free() {
  local port="$1"
  [[ -z "$(lsof -ti ":$port" -sTCP:LISTEN 2>/dev/null || true)" ]]
}

pick_ctm_port() {
  local attempt candidate
  for attempt in $(seq 1 80); do
    candidate=$((4100 + RANDOM % 898))
    if port_is_free "$candidate" && port_is_free "$((candidate + 1))" && port_is_free "$((candidate + 2))"; then
      echo "$candidate"
      return 0
    fi
  done
  echo "[dev] Could not find a free dev port pair in 4100-4999" >&2
  exit 1
}

if [[ -z "${DEV_CTM_PORT:-}" ]]; then
  DEV_CTM_PORT="$(pick_ctm_port)"
else
  assert_safe_port "DEV_CTM_PORT" "$DEV_CTM_PORT"
fi

DEV_WALLE_PORT="${DEV_WALLE_PORT:-$((DEV_CTM_PORT + 1))}"
DEV_OAUTH_PROXY_PORT="${DEV_OAUTH_PROXY_PORT:-$((DEV_CTM_PORT + 2))}"
assert_safe_port "DEV_WALLE_PORT" "$DEV_WALLE_PORT"
assert_safe_port "DEV_OAUTH_PROXY_PORT" "$DEV_OAUTH_PROXY_PORT"

DEV_DIR="${WALLE_DEV_DIR:-/tmp/walle-dev-$DEV_CTM_PORT}"

if [[ "$PRINT_CONFIG" == "1" ]]; then
  echo "DEV_CTM_PORT=$DEV_CTM_PORT"
  echo "DEV_WALLE_PORT=$DEV_WALLE_PORT"
  echo "DEV_OAUTH_PROXY_PORT=$DEV_OAUTH_PROXY_PORT"
  echo "WALLE_DEV_DIR=$DEV_DIR"
  exit 0
fi

# Source production .env to find DB paths. DEV_SOURCE_* lets worktrees without
# their own .env refresh from the primary repo's configured data directory.
PROD_CTM_DIR="${DEV_SOURCE_CTM_DATA_DIR:-$HOME/.walle/data}"
PROD_WALLE_DIR="${DEV_SOURCE_WALL_E_DATA_DIR:-$HOME/.walle/data}"
if [[ -f "$ROOT/.env" ]]; then
  eval "$(grep -E '^(CTM_DATA_DIR|WALL_E_DATA_DIR)=' "$ROOT/.env" 2>/dev/null)" || true
  [[ -z "$DEV_SOURCE_CTM_DATA_DIR" && -n "$CTM_DATA_DIR" ]] && PROD_CTM_DIR="$CTM_DATA_DIR"
  [[ -z "$DEV_SOURCE_WALL_E_DATA_DIR" && -n "$WALL_E_DATA_DIR" ]] && PROD_WALLE_DIR="$WALL_E_DATA_DIR"
fi

mkdir -p "$DEV_DIR"

cleanup_processes() {
  local stale_args=()
  if [[ "${1:-}" == "--stale" ]]; then
    stale_args=(--stale --max-age-min "${CTM_DEV_STALE_MAX_MIN:-1440}")
  fi
  "$NODE_BIN" "$ROOT/bin/ctm-dev-cleanup.js" \
    --kill \
    --port "$DEV_CTM_PORT" \
    --wall-e-port "$DEV_WALLE_PORT" \
    "${stale_args[@]}" >/dev/null 2>&1 || true
  pkill -f "ctm-dev-$DEV_CTM_PORT" 2>/dev/null || true
  pkill -f "Coding Task Manager dev-$DEV_CTM_PORT" 2>/dev/null || true
  pkill -f "walle-dev-$DEV_CTM_PORT" 2>/dev/null || true
  pkill -f "Wall-E dev-$DEV_CTM_PORT" 2>/dev/null || true
  lsof -ti ":$DEV_CTM_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
  lsof -ti ":$DEV_WALLE_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
  lsof -ti ":$DEV_OAUTH_PROXY_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
}

cleanup_dev() {
  "$NODE_BIN" "$ROOT/bin/ctm-dev-cleanup.js" \
    --kill \
    --port "$DEV_CTM_PORT" \
    --wall-e-port "$DEV_WALLE_PORT" >/dev/null 2>&1 || true
  cleanup_processes
  # Remove dev data directory to prevent /tmp bloat (each copy is ~2-6 GB)
  if [[ "$KEEP_DATA_ON_EXIT" != "1" && "$DEV_DIR" == /tmp/walle-* && -d "$DEV_DIR" ]]; then
    echo "[dev] Cleaning up $DEV_DIR ..."
    rm -rf "$DEV_DIR"
  elif [[ "$KEEP_DATA_ON_EXIT" == "1" && -d "$DEV_DIR" ]]; then
    echo "[dev] Keeping dev data $DEV_DIR"
  fi
}

cleanup_processes --stale

# Handle launch mode
if [[ "$MODE" == "fresh" ]]; then
  echo "[dev] Starting with fresh (empty) databases"
  rm -f "$DEV_DIR"/*.db "$DEV_DIR"/*.db-wal "$DEV_DIR"/*.db-shm
elif [[ "$MODE" == "refresh" ]]; then
  echo "[dev] Snapshotting production databases to $DEV_DIR ..."
  "$NODE_BIN" "$ROOT/bin/sqlite-snapshot.js" "$PROD_CTM_DIR/task-manager.db" "$DEV_DIR/task-manager.db" "CTM"
  "$NODE_BIN" "$ROOT/bin/sqlite-snapshot.js" "$PROD_WALLE_DIR/wall-e-brain.db" "$DEV_DIR/wall-e-brain.db" "Brain"
  if [[ "$COPY_IMAGES" == "1" && -d "$PROD_CTM_DIR/images" ]]; then
    echo "  Images: syncing $PROD_CTM_DIR/images -> $DEV_DIR/images"
    "$NODE_BIN" "$ROOT/bin/sync-images.js" "$PROD_CTM_DIR/images" "$DEV_DIR/images"
  elif [[ "$COPY_IMAGES" != "1" ]]; then
    echo "  Images: skipped (--no-images)"
  fi
  # Ensure the dev instance owns its WAL files from first open.
  rm -f "$DEV_DIR"/*.db-wal "$DEV_DIR"/*.db-shm
elif [[ "$MODE" == "reuse" ]]; then
  echo "[dev] Reusing existing databases in $DEV_DIR"
fi

echo ""
echo "  Dev CTM:    http://localhost:$DEV_CTM_PORT"
echo "  Dev Wall-E: port $DEV_WALLE_PORT"
echo "  Dev OAuth:  port $DEV_OAUTH_PROXY_PORT"
echo "  Dev data:   $DEV_DIR"
echo "  Source:     $ROOT"
echo ""
echo "  Ctrl+C to stop. Primary instance on :3456 is unaffected."
echo ""

# Load production .env vars (API keys, etc.) but override ports and data dirs
export CTM_PORT="$DEV_CTM_PORT"
export WALL_E_PORT="$DEV_WALLE_PORT"
export CTM_DATA_DIR="$DEV_DIR"
export WALL_E_DATA_DIR="$DEV_DIR"
export WALLE_SESSIONS_DIR="$DEV_DIR/sessions"
export WALL_E_SESSIONS_DIR="$DEV_DIR/sessions"
export CTM_HOST="127.0.0.1"
export CTM_INSTANCE_TAG="dev-$DEV_CTM_PORT"
export OAUTH_PROXY_PORT="$DEV_OAUTH_PROXY_PORT"
export CTM_TLS_CERT=""
export CTM_TLS_KEY=""
# Dev instances must not rewrite user-level Codex/Claude MCP config to their
# temporary Wall-E port. Set WALLE_MCP_AUTO_CONFIG=1 to opt in deliberately.
export WALLE_MCP_AUTO_CONFIG="${WALLE_MCP_AUTO_CONFIG:-0}"

# Source the rest of .env (API keys, owner name, etc.)
if [[ -f "$ROOT/.env" ]]; then
  set -a
  source <(grep -v '^#' "$ROOT/.env" | grep -vE '^(CTM_PORT|WALL_E_PORT|CTM_DATA_DIR|WALL_E_DATA_DIR|WALLE_SESSIONS_DIR|WALL_E_SESSIONS_DIR|CTM_HOST|CTM_INSTANCE_TAG|OAUTH_PROXY_PORT|CTM_TLS_CERT|CTM_TLS_KEY|CTM_API_BASE_URL|CTM_SESSION_MEMORY_API_BASE_URL|WALLE_MCP_AUTO_CONFIG|CTM_SKIP_DOTENV)=' | grep '=')
  set +a
fi

# Reassert the dev isolation boundary after loading production credentials.
# Some user .env files contain TLS/public-host settings for the primary CTM;
# the dev instance must stay loopback HTTP so Wall-E's internal CTM API probe,
# browser tests, and curl checks hit the isolated data dir instead.
export CTM_PORT="$DEV_CTM_PORT"
export WALL_E_PORT="$DEV_WALLE_PORT"
export CTM_DATA_DIR="$DEV_DIR"
export WALL_E_DATA_DIR="$DEV_DIR"
export WALLE_SESSIONS_DIR="$DEV_DIR/sessions"
export WALL_E_SESSIONS_DIR="$DEV_DIR/sessions"
export CTM_HOST="127.0.0.1"
export CTM_INSTANCE_TAG="dev-$DEV_CTM_PORT"
export OAUTH_PROXY_PORT="$DEV_OAUTH_PROXY_PORT"
export CTM_TLS_CERT=""
export CTM_TLS_KEY=""
export CTM_API_BASE_URL="http://127.0.0.1:$DEV_CTM_PORT"
export CTM_SESSION_MEMORY_API_BASE_URL="http://127.0.0.1:$DEV_CTM_PORT"
export WALLE_MCP_AUTO_CONFIG="${WALLE_MCP_AUTO_CONFIG:-0}"
export CTM_SKIP_DOTENV="1"

CTM_DEV_CHILD_PID=""
forward_signal() {
  local signal="$1"
  if [[ -n "$CTM_DEV_CHILD_PID" ]]; then
    kill -"$signal" "$CTM_DEV_CHILD_PID" 2>/dev/null || true
  fi
}

trap 'forward_signal INT' INT
trap 'forward_signal TERM' TERM
trap cleanup_dev EXIT

# Clean up stale dev/e2e data dirs older than 24h (from crashed sessions)
find /tmp -maxdepth 1 -name "walle-dev-*" -type d -mmin +1440 -exec rm -rf {} + 2>/dev/null || true
find /tmp -maxdepth 1 -name "walle-e2e-*" -type d -mmin +1440 -exec rm -rf {} + 2>/dev/null || true
find /tmp -maxdepth 1 -name "ctm-dev-*" -type d -mmin +1440 -exec rm -rf {} + 2>/dev/null || true

# Run in foreground so Ctrl+C stops it cleanly while keeping the shell trap alive.
cd "$ROOT"
"$NODE_BIN" claude-task-manager/server.js &
CTM_DEV_CHILD_PID="$!"
set +e
wait "$CTM_DEV_CHILD_PID"
status="$?"
set -e
CTM_DEV_CHILD_PID=""
exit "$status"
