#!/bin/bash
# peer-capture.sh — generic .before-stop hook: snapshot admin peers for reinject.
# In-network vantage (rpc_chains curl): public gateway rejects admin; some hosts
# firewall host→bridge-IP. Client-aware peer list → /root/rpc/peer-state/<path>.json
#
# Usage: peer-capture.sh <compose-path>
#   compose-path: e.g. op/reth/lisk-mainnet-op-reth-pruned-trace
set -euo pipefail

COMPOSE_PATH="${1:-}"
if [ -z "$COMPOSE_PATH" ]; then
    echo "Usage: $0 <compose-path>" >&2
    exit 1
fi

BASEPATH="$(cd "$(dirname "$0")" && pwd)"
STATE_DIR="$BASEPATH/peer-state"
mkdir -p "$STATE_DIR"
SAFE="$(echo "$COMPOSE_PATH" | sed 's|[^a-zA-Z0-9._-]|_|g')"
OUT="$STATE_DIR/${SAFE}.json"
CURL_IMAGE="${PEER_CURL_IMAGE:-curlimages/curl:8.5.0}"
LOG="${PEER_HOOK_LOG:-/var/log/peer-hooks.log}"

log() { echo "$(date '+%F %T') peer-capture[$COMPOSE_PATH]: $*" | tee -a "$LOG" >&2; }

# Documented skips
case "$COMPOSE_PATH" in
    *nitro*|*zksync*|*zk-sync*)
        log "skip (nitro/zksync)"
        exit 0
        ;;
esac

BASE="$(basename "$COMPOSE_PATH" | sed 's/--.*//')"
# Find execution container (same heuristics as characterize-node exec_container)
EL=""
while read -r n; do
    [ -z "$n" ] && continue
    case "$n" in
        rpc-*) ;;
        *) continue ;;
    esac
    case "$n" in
        *-node-*|*-relay-*) continue ;;
    esac
    core="$(echo "$n" | sed -E 's/^rpc-(.+)-[0-9]+$/\1/')"
    if [ "$BASE" = "$core" ] || [[ "$BASE" == "$core"-* ]]; then
        EL="$n"
        break
    fi
done < <(docker ps --format '{{.Names}}')

if [ -z "$EL" ]; then
    log "no execution container for $BASE"
    exit 0
fi

IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$EL" 2>/dev/null || true)"
if [ -z "$IP" ]; then
    log "no IP for $EL"
    exit 0
fi

docker rm -f peer-hook-probe >/dev/null 2>&1 || true
docker image inspect "$CURL_IMAGE" >/dev/null 2>&1 || docker pull -q "$CURL_IMAGE" >/dev/null
docker run -d --name peer-hook-probe --network rpc_chains "$CURL_IMAGE" sleep 90 >/dev/null

rpc() {
    local method="$1"
    docker exec peer-hook-probe curl -s --max-time 8 -X POST \
        -H 'Content-Type: application/json' \
        --data "{\"jsonrpc\":\"2.0\",\"method\":\"$method\",\"params\":[],\"id\":1}" \
        "http://${IP}:8545" 2>/dev/null || true
}

INFO="$(rpc admin_nodeInfo)"
CLIENT_NAME="$(echo "$INFO" | jq -r '.result.name // empty' 2>/dev/null || true)"
PEERS_JSON="$(rpc admin_peers)"
PEER_COUNT="$(echo "$PEERS_JSON" | jq -r '.result | length' 2>/dev/null || echo 0)"

# Detect client type (connect-peers.sh logic)
NAME_LC="$(echo "$CLIENT_NAME" | tr '[:upper:]' '[:lower:]')"
CLIENT_TYPE="unknown"
case "$NAME_LC" in
    *geth*) CLIENT_TYPE="geth" ;;
    *reth*) CLIENT_TYPE="reth" ;;
    *erigon*) CLIENT_TYPE="erigon" ;;
    *nethermind*) CLIENT_TYPE="nethermind" ;;
    *besu*) CLIENT_TYPE="besu" ;;
esac

TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "$PEERS_JSON" | jq -c --arg path "$COMPOSE_PATH" --arg ts "$TS" \
    --arg el "$EL" --arg cn "$CLIENT_NAME" --arg ct "$CLIENT_TYPE" \
    --argjson pc "${PEER_COUNT:-0}" '
{
  node_path: $path,
  captured_at: $ts,
  container: $el,
  client_name: $cn,
  client_type: $ct,
  peer_count: $pc,
  peers: [(.result // [])[] | select(.enode != null) | {
    id: .enode,
    client: (.name // ""),
    layer: "execution"
  }]
}' > "$OUT.tmp" 2>/dev/null || {
    log "admin_peers failed; writing empty seed"
    cat > "$OUT.tmp" <<EOF
{"node_path":"$COMPOSE_PATH","captured_at":"$TS","container":"$EL","client_name":"$CLIENT_NAME","client_type":"$CLIENT_TYPE","peer_count":0,"peers":[]}
EOF
}
mv -f "$OUT.tmp" "$OUT"
# also keep a plain enode list for easy playback
jq -r '.peers[].id' "$OUT" > "${OUT%.json}.enodes" 2>/dev/null || true

docker rm -f peer-hook-probe >/dev/null 2>&1 || true
log "captured ${PEER_COUNT:-0} peer(s) → $OUT"
exit 0
