#!/usr/bin/env bash
# ────────────────────────────────────────────────────────────────────────────
#  restart_v3.sh — RESTART COMPLETO de todos los procesos V3
#
#  DEFAULT: reinicia TODO desde cero (backend + emuladores + Evony + frida).
#  Preserva userdata persistente (cuentas Evony, instalaciones, Magisk grant).
#  Tarda ~2-3 min (boot emuladores + settle Evony).
#
#  Estados que sabe arreglar automáticamente:
#   - Magisk grant perdido → re-grant via launch de Magisk app
#   - frida-server binario faltante (perdido tras kill-9) → re-push
#   - frida-server proceso muerto → setsid relaunch (no nohup)
#   - Evony en estado degradado y attach falla 3x → force-stop + relaunch + settle 25s
#
#  Uso:
#    ./restart_v3.sh           → restart COMPLETO (emuladores + backend, ~2-3 min)
#    ./restart_v3.sh --fast    → restart solo backend (deps quedan vivas, ~10s)
#    ./restart_v3.sh --tail    → restart completo + sigue log
#    ./restart_v3.sh --status  → solo diagnostica sin tocar nada
#    ./restart_v3.sh --stop    → para V3 backend + emuladores limpio (preserva snapshots)
# ────────────────────────────────────────────────────────────────────────────
set -u

V3_DIR=/Users/danicosta/Desktop/Evony/evony-scout-v3-research
PY=/Applications/Xcode.app/Contents/Developer/usr/bin/python3
EMU_BIN=/opt/homebrew/share/android-commandlinetools/emulator/emulator
FS_BIN="$V3_DIR/frida-server-17.9.10-android-arm64"
LOG=/tmp/evony_v3.log
PORT=8772

# AVDs y puertos (cuentas server #1939)
AVD_W=evony1_1939   ; PORT_W=6000
AVD_E=evony2_1939   ; PORT_E=6002

# ───── helpers ─────
log()  { echo "[$(date +%H:%M:%S)] $*"; }
fail() { echo "[$(date +%H:%M:%S)] [!] $*" >&2; exit 1; }

emu_pid() { lsof -ti :$1 2>/dev/null; }

adb_dev() { adb devices 2>/dev/null | grep -E "^emulator-$1\s+device$"; }

emu_alive() {
  local p=$1
  [ -n "$(emu_pid $p)" ] && [ -n "$(adb_dev $p)" ]
}

# Levanta un emulador si no está vivo. Acepta avd_name + port.
# En modo FULL_RESTART=1, primero mata el emulador con adb emu kill (clean shutdown
# que preserva userdata) y vuelve a arrancarlo desde cero.
ensure_emulator_up() {
  local avd=$1 port=$2
  if [ "${FULL_RESTART:-0}" = "1" ] && emu_alive $port; then
    log "  $avd:$port vivo → kill clean para restart..."
    adb -s emulator-$port emu kill >/dev/null 2>&1
    # Esperar shutdown completo (max 30s)
    local i
    for i in $(seq 1 15); do
      if ! emu_alive $port; then break; fi
      sleep 2
    done
    # Force kill por puerto si sigue vivo
    if emu_alive $port; then
      log "  $avd:$port no se cerró limpiamente, force kill..."
      lsof -ti :$port 2>/dev/null | xargs -I{} kill -9 {} 2>/dev/null
      sleep 3
    fi
  fi
  if emu_alive $port; then
    log "  $avd:$port ya vivo"
    return 0
  fi
  log "  arrancando $avd en puerto $port..."
  nohup "$EMU_BIN" -avd $avd -port $port -no-snapshot-load -no-boot-anim \
        >/tmp/evony_$port.log 2>&1 &
  disown
  # esperar boot completo (max 120s)
  local i
  for i in $(seq 1 60); do
    local s=$(adb -s emulator-$port shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')
    if [ "$s" = "1" ]; then
      log "  $avd:$port booted en $((i*2))s"
      # buffer mínimo para que SystemUI/launcher terminen de cargar
      sleep 3
      return 0
    fi
    sleep 2
  done
  fail "$avd:$port no completó boot en 120s"
}

# Asegura que Magisk tenga grant para shell user. Si su -c whoami da Permission denied,
# lanza la app Magisk (que arma el daemon + policy autogrant para esta sesión).
ensure_magisk_root() {
  local port=$1
  local who=$(adb -s emulator-$port shell "su -c 'whoami'" 2>&1 | tr -d '\r' | head -1)
  if [ "$who" = "root" ]; then
    log "  $port Magisk root OK"
    return 0
  fi
  log "  $port Magisk grant denegado, lanzando app..."
  adb -s emulator-$port shell "monkey -p com.topjohnwu.magisk -c android.intent.category.LAUNCHER 1" \
       >/dev/null 2>&1
  sleep 8
  who=$(adb -s emulator-$port shell "su -c 'whoami'" 2>&1 | tr -d '\r' | head -1)
  if [ "$who" = "root" ]; then
    log "  $port Magisk root tras re-grant OK"
    # Intenta hacer policy persistente para no perder grant en este boot
    adb -s emulator-$port shell "su -c 'magisk --sqlite \"INSERT OR REPLACE INTO policies (uid, policy, until, logging, notification) VALUES (2000, 2, 0, 0, 0)\"'" >/dev/null 2>&1
    return 0
  fi
  fail "$port no consigue root via Magisk (su -c falla)"
}

# Asegura que /data/local/tmp/frida-server exista (lo pushea si falta).
ensure_frida_binary() {
  local port=$1
  local has=$(adb -s emulator-$port shell "ls /data/local/tmp/frida-server 2>/dev/null" | tr -d '\r')
  if [ -n "$has" ]; then
    log "  $port frida-server binary OK"
    return 0
  fi
  log "  $port pushing frida-server..."
  adb -s emulator-$port push "$FS_BIN" /data/local/tmp/frida-server >/dev/null 2>&1
  adb -s emulator-$port shell "su -c 'chmod 755 /data/local/tmp/frida-server'" >/dev/null 2>&1
  has=$(adb -s emulator-$port shell "ls /data/local/tmp/frida-server 2>/dev/null" | tr -d '\r')
  [ -n "$has" ] || fail "$port no consigue pushear frida-server"
  log "  $port frida-server pushed"
}

# Asegura que frida-server esté corriendo y escuchando en :27042.
ensure_frida_running() {
  local port=$1
  local listen=$(adb -s emulator-$port shell "su -c 'netstat -anl 2>/dev/null | grep \":27042 \" | grep LISTEN'" 2>&1 | head -1)
  if [ -n "$listen" ] && echo "$listen" | grep -q LISTEN; then
    log "  $port frida-server LISTEN OK"
    return 0
  fi
  log "  $port arrancando frida-server (setsid)..."
  # Mata el viejo (si lo hubiera) y espera a que libere el puerto antes de relanzar
  adb -s emulator-$port shell "su -c 'pkill -9 -f /data/local/tmp/frida-server 2>/dev/null'" >/dev/null 2>&1
  sleep 2   # dejar tiempo a que el puerto :27042 salga de TIME_WAIT
  # IMPORTANTE: setsid en vez de nohup — Android mata hijos del shell al cierre adb
  adb -s emulator-$port shell "su -c 'setsid /data/local/tmp/frida-server </dev/null >/dev/null 2>&1 &'" >/dev/null 2>&1
  # Retry loop con backoff (hasta 15s): frida-server tarda variable en bindear
  # según estado de Magisk daemon + SELinux. 4s no era suficiente en algunos casos.
  local i
  for i in $(seq 1 8); do
    sleep 2
    listen=$(adb -s emulator-$port shell "su -c 'netstat -anl 2>/dev/null | grep \":27042 \" | grep LISTEN'" 2>&1 | head -1)
    if [ -n "$listen" ] && echo "$listen" | grep -q LISTEN; then
      log "  $port frida-server LISTEN tras $((i*2))s OK"
      return 0
    fi
  done
  # Último intento: el proceso puede haber muerto. Lanzar otra vez y dar más tiempo.
  log "  $port retry final tras 16s sin LISTEN..."
  adb -s emulator-$port shell "su -c 'pkill -9 -f /data/local/tmp/frida-server 2>/dev/null'" >/dev/null 2>&1
  sleep 3
  adb -s emulator-$port shell "su -c 'setsid /data/local/tmp/frida-server </dev/null >/dev/null 2>&1 &'" >/dev/null 2>&1
  for i in $(seq 1 6); do
    sleep 2
    listen=$(adb -s emulator-$port shell "su -c 'netstat -anl 2>/dev/null | grep \":27042 \" | grep LISTEN'" 2>&1 | head -1)
    if [ -n "$listen" ] && echo "$listen" | grep -q LISTEN; then
      log "  $port frida-server LISTEN tras retry final ($((i*2))s) OK"
      return 0
    fi
  done
  fail "$port frida-server no LISTEN tras 2 intentos (28s) — revisar Magisk daemon"
}

# Garantiza que Evony esté corriendo Y "asentada" (Unity/IL2CPP cargado).
# En modo FULL_RESTART=1 (tras cold-boot del emulador), SIEMPRE force-stop + relaunch
# porque Android auto-arranca Evony al boot pero queda en splash/login screen
# sin IL2CPP cargado (frida-il2cpp-bridge no puede atachar a esa instancia "zombie").
# En modo fast (deps vivas), reusa la instancia existente.
ensure_evony_running() {
  local port=$1
  if [ "${FULL_RESTART:-0}" = "1" ]; then
    log "  $port [FULL] force-stop Evony para limpiar estado pre-IL2CPP..."
    adb -s emulator-$port shell "am force-stop com.topgamesinc.evony" >/dev/null 2>&1
    sleep 3
    log "  $port [FULL] am start Evony..."
    adb -s emulator-$port shell "am start -n com.topgamesinc.evony/com.topgamesinc.androidplugin.UnityActivity" >/dev/null 2>&1
    # Settle reducido: solo necesitamos que el PID exista + Unity/IL2CPP arranque.
    # El attach de frida (backend) ya reintenta 3x si IL2CPP aún no cargó, y autoWM
    # redirige al mapa en cuanto pueda. 18s basta para estos AVDs (antes 30s fijos).
    log "  $port settle 18s (Unity + IL2CPP load)..."
    sleep 18
    local ev=$(adb -s emulator-$port shell "pidof com.topgamesinc.evony" 2>&1 | tr -d '\r')
    [ -n "$ev" ] || fail "$port Evony no arrancó tras force-stop+restart"
    log "  $port Evony PID=$ev tras settle (full restart)"
    return 0
  fi
  # Modo fast: reusa si está corriendo
  local ev=$(adb -s emulator-$port shell "pidof com.topgamesinc.evony" 2>&1 | tr -d '\r')
  if [ -n "$ev" ]; then
    log "  $port Evony PID=$ev OK (reusing)"
    return 0
  fi
  log "  $port arrancando Evony..."
  adb -s emulator-$port shell "am start -n com.topgamesinc.evony/com.topgamesinc.androidplugin.UnityActivity" >/dev/null 2>&1
  log "  $port settle 25s (esperando Unity init)..."
  sleep 25
  ev=$(adb -s emulator-$port shell "pidof com.topgamesinc.evony" 2>&1 | tr -d '\r')
  [ -n "$ev" ] || fail "$port Evony no arrancó"
  log "  $port Evony PID=$ev tras settle"
}

# Fuerza re-launch de Evony con settle (usar cuando frida-attach falla repetidamente).
restart_evony_settled() {
  local port=$1
  log "  $port force-stop + relanzar Evony..."
  adb -s emulator-$port shell "am force-stop com.topgamesinc.evony" >/dev/null 2>&1
  sleep 3
  adb -s emulator-$port shell "am start -n com.topgamesinc.evony/com.topgamesinc.androidplugin.UnityActivity" >/dev/null 2>&1
  log "  $port settle 25s..."
  sleep 25
}

# Prepara un emulador COMPLETO: boot → Magisk root → frida binary → frida LISTEN → Evony.
# Diseñada para invocarse EN PARALELO (W y E a la vez, cada uno en su subshell), de modo
# que el boot y el settle de Evony de ambos solapen en vez de correr en serie (~mitad de tiempo).
setup_half() {
  local avd=$1 port=$2
  ensure_emulator_up   "$avd" "$port"
  ensure_magisk_root   "$port"
  ensure_frida_binary  "$port"
  ensure_frida_running "$port"
  ensure_evony_running "$port"
}

case "${1:-start}" in
  --status|status)
    log "=== V3 STATUS DIAGNOSTIC ==="
    echo "Backend :$PORT: $(lsof -ti :$PORT 2>/dev/null || echo MUERTO)"
    for cfg in "W:$PORT_W" "E:$PORT_E"; do
      half=${cfg%:*}; p=${cfg#*:}
      ev=$(adb -s emulator-$p shell "pidof com.topgamesinc.evony" 2>/dev/null | tr -d '\r')
      fs=$(adb -s emulator-$p shell "su -c 'netstat -anl 2>/dev/null | grep \":27042 \" | grep LISTEN'" 2>/dev/null | head -1)
      who=$(adb -s emulator-$p shell "su -c 'whoami'" 2>/dev/null | tr -d '\r' | head -1)
      echo "$half (port $p):"
      echo "  adb device: $(adb_dev $p && echo OK || echo NO)"
      echo "  Magisk root: ${who:-DENIED}"
      echo "  frida LISTEN: $([ -n "$fs" ] && echo OK || echo NO)"
      echo "  Evony PID: ${ev:-NOT RUNNING}"
    done
    exit 0
    ;;

  --stop|stop)
    log "=== STOPPING V3 + EMULATORS (clean) ==="
    lsof -ti :$PORT 2>/dev/null | xargs -I{} kill -9 {} 2>/dev/null
    pkill -9 -f "iscout_web.py" 2>/dev/null
    for p in $PORT_W $PORT_E; do
      adb -s emulator-$p emu kill >/dev/null 2>&1
    done
    sleep 4
    log "  todos detenidos"
    exit 0
    ;;

  --tail)      TAIL=1; FULL_RESTART=1 ;;
  --fast)      TAIL=0; FULL_RESTART=0 ;;
  --tail-fast) TAIL=1; FULL_RESTART=0 ;;
  start|"")    TAIL=0; FULL_RESTART=1 ;;
  --help|-h) sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
  *) echo "Usage: $0 {start|--fast|--tail|--tail-fast|--status|--stop|--help}"; exit 1 ;;
esac
export FULL_RESTART

# ────────────────────────────────────────────────────────────────────────────
# CICLO COMPLETO IDEMPOTENTE
# ────────────────────────────────────────────────────────────────────────────
if [ "${FULL_RESTART:-0}" = "1" ]; then
  log "=== V3 RESTART · TOTAL (emuladores + backend) ==="
else
  log "=== V3 RESTART · FAST (solo backend, deps quedan vivas) ==="
fi

# 0. Validar Python
log "[0/8] validar iscout_web.py..."
"$PY" -c "import py_compile; py_compile.compile('$V3_DIR/iscout_web.py', doraise=True)" 2>&1 \
  || fail "iscout_web.py sintaxis rota — abort"
log "  PY OK"

# 1. Matar backend V3 viejo
log "[1/8] matar backend viejo..."
lsof -ti :$PORT 2>/dev/null | xargs -I{} kill -9 {} 2>/dev/null
pkill -9 -f "iscout_web.py" 2>/dev/null
sleep 2

# 2. Preparar AMBOS emuladores EN PARALELO (boot → Magisk → frida → Evony).
# Antes los pasos 2-6 corrían en serie (todo W, luego todo E) → el boot y el settle
# de Evony se pagaban DOS veces. Ahora cada half corre en su propio subshell y se
# solapan → tiempo de pared ≈ la mitad. fail() dentro del subshell → exit!=0 → wait lo detecta.
log "[2/4] preparando emuladores W+E EN PARALELO (boot+Magisk+frida+Evony)..."
setup_half "$AVD_W" "$PORT_W" & PID_W=$!
setup_half "$AVD_E" "$PORT_E" & PID_E=$!
RC_W=0; RC_E=0
wait "$PID_W" || RC_W=$?
wait "$PID_E" || RC_E=$?
[ "$RC_W" = "0" ] || fail "setup W ($PORT_W) falló (rc=$RC_W) — ver mensajes arriba"
[ "$RC_E" = "0" ] || fail "setup E ($PORT_E) falló (rc=$RC_E) — ver mensajes arriba"
log "  ambos emuladores listos (W+E)"

# 3. Arrancar V3 backend
log "[3/4] arrancar V3 backend..."
> "$LOG"
# Bind a la IP de Tailscale (solo el tailnet llega; el VPS reverse-proxy entra por ahí).
# Si Tailscale no está arriba, cae a 127.0.0.1 (hermético, solo local).
ISCOUT_BIND="$(tailscale ip -4 2>/dev/null | head -1)"
[ -z "$ISCOUT_BIND" ] && ISCOUT_BIND="127.0.0.1"
export ISCOUT_BIND
log "  bind backend en $ISCOUT_BIND:8772"
cd "$V3_DIR" && nohup "$PY" iscout_web.py >"$LOG" 2>&1 &
disown
# El backend carga cache_persist.json (~50MB) al iniciar → puede tardar >8s en bindear
# :8772. Antes era un único check a los 8s que daba FALSO NEGATIVO. Ahora poll hasta 40s.
for i in $(seq 1 20); do
  [ -n "$(lsof -ti :$PORT 2>/dev/null)" ] && break
  sleep 2
done
[ -n "$(lsof -ti :$PORT 2>/dev/null)" ] || fail "V3 backend no abrió :$PORT tras 40s — ver $LOG"
log "  V3 backend up en http://127.0.0.1:$PORT"

# 8. Esperar attach confirmado (frida activo + self uid en ambos halves)
log "[4/4] esperar attach completo (max 90s)..."
DEADLINE=$(($(date +%s) + 90))
W_OK=0; E_OK=0
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
  [ "$W_OK" = "0" ] && grep -q "\[W self\] uid=" "$LOG" 2>/dev/null && { W_OK=1; log "  W self uid detected"; }
  [ "$E_OK" = "0" ] && grep -q "\[E self\] uid=" "$LOG" 2>/dev/null && { E_OK=1; log "  E self uid detected"; }
  [ "$W_OK" = "1" ] && [ "$E_OK" = "1" ] && break
  # Si vemos error de attach repetido en W o E, restart Evony de ese half
  if [ "$W_OK" = "0" ] && [ $(grep -c "\[W\] error: unexpectedly timed out" "$LOG") -ge 3 ]; then
    log "  [retry] W attach repeats fails -> restart Evony 6000 con settle"
    restart_evony_settled $PORT_W
    # truncate ese contador
    sed -i.bak '/\[W\] error: unexpectedly timed out/d' "$LOG" 2>/dev/null
  fi
  if [ "$E_OK" = "0" ] && [ $(grep -c "\[E\] error: unexpectedly timed out" "$LOG") -ge 3 ]; then
    log "  [retry] E attach repeats fails -> restart Evony 6002 con settle"
    restart_evony_settled $PORT_E
    sed -i.bak '/\[E\] error: unexpectedly timed out/d' "$LOG" 2>/dev/null
  fi
  sleep 3
done

if [ "$W_OK" = "1" ] && [ "$E_OK" = "1" ]; then
  log "=== ✅ V3 listo y operativo ==="
  log "  W uid: $(grep '\[W self\] uid=' $LOG | tail -1 | sed 's/.*uid=//')"
  log "  E uid: $(grep '\[E self\] uid=' $LOG | tail -1 | sed 's/.*uid=//')"
  log "  Server: $(grep 'server_id\]' $LOG | tail -1 | sed 's/.*\] //; s/ (.*//')"
  log "  UI: http://127.0.0.1:$PORT"
else
  log "[!] timeout esperando attach completo (W=$W_OK E=$E_OK). Revisa $LOG"
fi

[ "$TAIL" = "1" ] && { log "tailing $LOG..."; exec tail -f "$LOG"; }
