#!/usr/bin/env bash
# Chain 138 PMM / oracle mesh tick — default every 6 seconds.
# Off-chain automation (systemd recommended). Keeps oracle + PriceFeedKeeper aligned with
# DODOPMMIntegration / ReserveSystem reads; optional WETH/ETH polling for mesh depth signals.
#
# Env (smom-dbis-138/.env or parent load-project-env):
#   PMM_MESH_INTERVAL_SEC=6       Sleep between ticks
#   RPC_URL_138 / RPC_URL
#   PRIVATE_KEY or DEPLOYER_PRIVATE_KEY — ETH/USD oracle push (update-oracle-price.sh)
#   KEEPER_PRIVATE_KEY + PRICE_FEED_KEEPER_ADDRESS — on-chain keeper performUpkeep when needed
#   DODO_PMM_INTEGRATION_ADDRESS — default 0x86AD… (current canonical integration)
#   PMM_MESH_POLL_POOLS — space-separated pool addresses (default: cUSDT/cUSDC PMM pool)
#   ENABLE_MESH_ORACLE_TICK=1     Run scripts/update-oracle-price.sh each tick (skips on-chain if <1% move)
#   ENABLE_MESH_KEEPER_TICK=1     Run keeper when checkUpkeep is true
#   ENABLE_MESH_WETH_MOCK_SYNC=0  Sync CHAIN138_WETH_MOCK_PRICE_FEED before keeper upkeep
#   MESH_WETH_MOCK_SYNC_EVERY_N=5 Only sync mock feed every N ticks (5 * 6s = 30s)
#   ENABLE_MESH_PMM_READS=1       eth_call getPoolPriceOrOracle per pool (warm path / observability)
#   ENABLE_MESH_WETH_READS=1      eth_call WETH9/WETH10 totalSupply (ETH mesh signal)
#   MESH_WETH_WRAP_WEI=0          If >0 and KEEPER_PRIVATE_KEY set: WETH9.deposit{value} (costs gas; rare)
#   MESH_WETH_WRAP_EVERY_N=60     Only wrap every N ticks when MESH_WETH_WRAP_WEI>0
#   MESH_TX_BACKOFF_SEC=30        Cooldown after "known tx" / "replacement underpriced" send errors
#   KEEPER_SECRET_FILE            Dedicated keeper env file (default: /root/.secure-secrets/chain138-keeper.env)
#   ALLOW_ORACLE_KEY_FOR_KEEPER=0 Set to 1 to let keeper reuse PRIVATE_KEY when no dedicated keeper key exists
#   MAX_TICKS=                    Exit after N ticks (empty = run forever)
#   DRY_RUN=1                     Log only, no txs
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$SMOM_ROOT"

ORACLE_PUBLISHER_ENV="${ORACLE_PUBLISHER_ENV:-/opt/oracle-publisher/.env}"
KEEPER_SECRET_FILE="${KEEPER_SECRET_FILE:-/root/.secure-secrets/chain138-keeper.env}"
if [ -f "$ORACLE_PUBLISHER_ENV" ]; then
  set -a
  # shellcheck source=/dev/null
  source "$ORACLE_PUBLISHER_ENV"
  set +a
fi

if [ -f "$KEEPER_SECRET_FILE" ]; then
  set -a
  # shellcheck source=/dev/null
  source "$KEEPER_SECRET_FILE"
  set +a
fi

if [ -f "$SMOM_ROOT/.env" ]; then
  set -a
  # shellcheck source=/dev/null
  source "$SMOM_ROOT/.env"
  set +a
fi

# Proxmox repo root .env (parent of smom-dbis-138) if key not in submodule .env
REPO_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
if [ -z "${PRIVATE_KEY:-}" ] && [ -f "$REPO_ROOT/.env" ]; then
  set -a
  # shellcheck source=/dev/null
  source "$REPO_ROOT/.env" 2>/dev/null || true
  set +a
fi

RPC="${RPC_URL_138:-${RPC_URL:-http://192.168.11.211:8545}}"
INTERVAL="${PMM_MESH_INTERVAL_SEC:-6}"
DODO="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895}}"
# Canonical cUSDT/cUSDC PMM pool on Chain 138 (current integration)
DEFAULT_POOLS="0x9e89bAe009adf128782E19e8341996c596ac40dC"
POOLS="${PMM_MESH_POLL_POOLS:-$DEFAULT_POOLS}"
WETH9="${WETH9_ADDRESS:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}"
WETH10="${WETH10_ADDRESS:-0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f}"

ENABLE_ORACLE="${ENABLE_MESH_ORACLE_TICK:-1}"
ENABLE_KEEPER="${ENABLE_MESH_KEEPER_TICK:-1}"
ENABLE_WETH_MOCK_SYNC="${ENABLE_MESH_WETH_MOCK_SYNC:-0}"
WETH_MOCK_SYNC_EVERY_N="${MESH_WETH_MOCK_SYNC_EVERY_N:-5}"
ENABLE_PMM_READS="${ENABLE_MESH_PMM_READS:-1}"
ENABLE_WETH_READS="${ENABLE_MESH_WETH_READS:-1}"
WRAP_WEI="${MESH_WETH_WRAP_WEI:-0}"
WRAP_EVERY_N="${MESH_WETH_WRAP_EVERY_N:-60}"
MESH_TX_BACKOFF_SEC="${MESH_TX_BACKOFF_SEC:-30}"
ALLOW_ORACLE_KEY_FOR_KEEPER="${ALLOW_ORACLE_KEY_FOR_KEEPER:-0}"
# Besu often needs an explicit gas price for replacement / mempool policy.
MESH_CAST_GAS_PRICE="${MESH_CAST_GAS_PRICE:-2gwei}"

ORACLE_PK="${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}"
KEEPER_PK="${KEEPER_PRIVATE_KEY:-}"
if [ -z "$KEEPER_PK" ] && [ "$ALLOW_ORACLE_KEY_FOR_KEEPER" = "1" ]; then
  KEEPER_PK="${PRIVATE_KEY:-}"
fi
ORACLE_ADDR=""
KEEPER_ADDR=""
if [ -n "$ORACLE_PK" ]; then
  ORACLE_ADDR="$(cast wallet address --private-key "$ORACLE_PK" 2>/dev/null || true)"
fi
if [ -n "$KEEPER_PK" ]; then
  KEEPER_ADDR="$(cast wallet address --private-key "$KEEPER_PK" 2>/dev/null || true)"
fi

NEXT_KEEPER_TX_AT=0
NEXT_ORACLE_TX_AT=0

log() { echo "[$(date -Iseconds)] $*"; }

is_retryable_tx_error() {
  local text="${1:-}"
  grep -qiE 'Known transaction|Replacement transaction underpriced' <<<"$text"
}

account_has_pending_nonce_gap() {
  local addr="${1:-}"
  [ -n "$addr" ] || return 1
  local latest pending
  latest="$(cast nonce "$addr" --rpc-url "$RPC" --block latest 2>/dev/null || true)"
  pending="$(cast nonce "$addr" --rpc-url "$RPC" --block pending 2>/dev/null || true)"
  [[ "$latest" =~ ^[0-9]+$ && "$pending" =~ ^[0-9]+$ ]] || return 1
  (( pending > latest ))
}

eth_call_price() {
  local to="$1" data="$2"
  cast rpc eth_call "{\"to\":\"$to\",\"data\":\"$data\"}" latest --rpc-url "$RPC" 2>/dev/null | tr -d '\n\"' || true
}

tick_pmm_reads() {
  [ "$ENABLE_PMM_READS" = "1" ] || return 0
  [ -n "$DODO" ] || return 0
  for pool in $POOLS; do
    [ "$pool" = "0x0000000000000000000000000000000000000000" ] && continue
    data=$(cast calldata "getPoolPriceOrOracle(address)" "$pool" 2>/dev/null) || continue
    out=$(eth_call_price "$DODO" "$data")
    if [ -n "$out" ]; then
      log "PMM getPoolPriceOrOracle($pool) -> $out"
    fi
  done
}

tick_weth_reads() {
  [ "$ENABLE_WETH_READS" = "1" ] || return 0
  for w in "$WETH9" "$WETH10"; do
    data=$(cast calldata "totalSupply()" 2>/dev/null) || continue
    out=$(eth_call_price "$w" "$data")
    if [ -n "$out" ]; then
      log "WETH totalSupply($w) raw -> $out"
    fi
  done
}

tick_keeper() {
  [ "$ENABLE_KEEPER" = "1" ] || return 0
  local now
  now="$(date +%s)"
  if (( now < NEXT_KEEPER_TX_AT )); then
    log "keeper tick cooling down until $(date -d "@$NEXT_KEEPER_TX_AT" -Iseconds)"
    return 0
  fi
  local k="${PRICE_FEED_KEEPER_ADDRESS:-}"
  [ -n "$k" ] && [ -n "$KEEPER_PK" ] || { log "keeper tick skipped (set PRICE_FEED_KEEPER_ADDRESS + KEEPER_PRIVATE_KEY)"; return 0; }
  if account_has_pending_nonce_gap "$KEEPER_ADDR"; then
    NEXT_KEEPER_TX_AT=$(( now + MESH_TX_BACKOFF_SEC ))
    log "keeper tx already pending for $KEEPER_ADDR; backoff ${MESH_TX_BACKOFF_SEC}s"
    return 0
  fi
  local raw dec first
  raw="$(cast rpc eth_call "{\"to\":\"$k\",\"data\":\"$(cast calldata "checkUpkeep()")\"}" latest --rpc-url "$RPC" 2>/dev/null | tr -d '\n\"')" || return 0
  dec="$(cast abi-decode "checkUpkeep()(bool,address[])" "$raw" 2>/dev/null)" || return 0
  first=$(echo "$dec" | head -1)
  if [ "$first" = "true" ]; then
    log "keeper checkUpkeep=true; performUpkeep"
    if [ -n "${DRY_RUN:-}" ]; then
      log "[dry-run] cast send performUpkeep"
      return 0
    fi
    local out rc
    set +e
    out="$(cast send "$k" "performUpkeep()" --rpc-url "$RPC" --private-key "$KEEPER_PK" \
      --legacy --gas-limit 500000 --gas-price "$MESH_CAST_GAS_PRICE" 2>&1)"
    rc=$?
    set -e
    if [ "$rc" -ne 0 ]; then
      echo "$out"
      if is_retryable_tx_error "$out"; then
        NEXT_KEEPER_TX_AT=$(( now + MESH_TX_BACKOFF_SEC ))
        log "WARN: performUpkeep pending/underpriced; keeper backoff ${MESH_TX_BACKOFF_SEC}s"
      else
        log "WARN: performUpkeep failed (RPC / gas / nonce); next tick in ${INTERVAL}s"
      fi
    fi
  else
    log "keeper checkUpkeep=false (no tx)"
  fi
}

tick_weth_mock_sync() {
  [ "$ENABLE_WETH_MOCK_SYNC" = "1" ] || return 0
  [ -n "${CHAIN138_WETH_MOCK_PRICE_FEED:-}" ] || return 0
  [ -n "$ORACLE_PK" ] || { log "WETH mock sync skipped (set PRIVATE_KEY)"; return 0; }
  [ "$WETH_MOCK_SYNC_EVERY_N" -gt 0 ] || WETH_MOCK_SYNC_EVERY_N=1
  local n="${TICK_COUNT:-0}"
  if (( n % WETH_MOCK_SYNC_EVERY_N != 0 )); then
    return 0
  fi
  local now
  now="$(date +%s)"
  if (( now < NEXT_ORACLE_TX_AT )); then
    log "WETH mock sync cooling down until $(date -d "@$NEXT_ORACLE_TX_AT" -Iseconds)"
    return 0
  fi
  if account_has_pending_nonce_gap "$ORACLE_ADDR"; then
    NEXT_ORACLE_TX_AT=$(( now + MESH_TX_BACKOFF_SEC ))
    log "WETH mock sync tx already pending for $ORACLE_ADDR; backoff ${MESH_TX_BACKOFF_SEC}s"
    return 0
  fi
  if [ -n "${DRY_RUN:-}" ]; then
    log "[dry-run] sync-weth-mock-price.sh"
    return 0
  fi
  local out rc
  set +e
  out="$(MESH_CAST_GAS_PRICE="$MESH_CAST_GAS_PRICE" bash "$SMOM_ROOT/scripts/reserve/sync-weth-mock-price.sh" "$RPC" 2>&1)"
  rc=$?
  set -e
  if [ "$rc" -ne 0 ]; then
    echo "$out"
    if is_retryable_tx_error "$out"; then
      NEXT_ORACLE_TX_AT=$(( now + MESH_TX_BACKOFF_SEC ))
      log "WARN: WETH mock sync pending/underpriced; backoff ${MESH_TX_BACKOFF_SEC}s"
    else
      log "WARN: sync-weth-mock-price.sh failed; next tick in ${INTERVAL}s"
    fi
  else
    echo "$out"
  fi
}

tick_oracle() {
  [ "$ENABLE_ORACLE" = "1" ] || return 0
  local now
  now="$(date +%s)"
  if (( now < NEXT_ORACLE_TX_AT )); then
    log "oracle tick cooling down until $(date -d "@$NEXT_ORACLE_TX_AT" -Iseconds)"
    return 0
  fi
  [ -n "$ORACLE_PK" ] || { log "oracle tick skipped (set PRIVATE_KEY)"; return 0; }
  if [ -n "${DRY_RUN:-}" ]; then
    log "[dry-run] update-oracle-price.sh"
    return 0
  fi
  # Exits 0 when price unchanged (<1%) or on success; failures must not stop the loop
  local out rc
  set +e
  out="$(bash "$SMOM_ROOT/scripts/update-oracle-price.sh" "$RPC" 2>&1)"
  rc=$?
  set -e
  if [ "$rc" -ne 0 ]; then
    echo "$out"
    if is_retryable_tx_error "$out"; then
      NEXT_ORACLE_TX_AT=$(( now + MESH_TX_BACKOFF_SEC ))
      log "WARN: oracle tx pending/underpriced; oracle backoff ${MESH_TX_BACKOFF_SEC}s"
    else
      log "WARN: update-oracle-price.sh failed (rate limit / RPC / gas); next tick in ${INTERVAL}s"
    fi
  fi
}

tick_weth_wrap() {
  (( ${WRAP_WEI:-0} > 0 )) || return 0
  [ -n "$KEEPER_PK" ] || { log "WETH wrap skipped (no KEEPER_PRIVATE_KEY)"; return 0; }
  local n="${TICK_COUNT:-0}"
  [ "$WRAP_EVERY_N" -gt 0 ] || WRAP_EVERY_N=1
  if (( n % WRAP_EVERY_N != 0 )); then
    return 0
  fi
  log "WETH9.deposit value=$WRAP_WEI wei (mesh activity)"
  if [ -n "${DRY_RUN:-}" ]; then
    log "[dry-run] cast send deposit"
    return 0
  fi
  if ! cast send "$WETH9" "deposit()" --value "$WRAP_WEI" --rpc-url "$RPC" --private-key "$KEEPER_PK" \
    --legacy --gas-limit 150000 --gas-price "$MESH_CAST_GAS_PRICE"; then
    log "WARN: WETH9.deposit failed; next tick in ${INTERVAL}s"
  fi
}

TICK_COUNT=0
log "=== PMM mesh automation === interval=${INTERVAL}s RPC=$RPC"
log "DODO=$DODO pools=$POOLS"

while true; do
  TICK_COUNT=$((TICK_COUNT + 1))
  log "--- tick $TICK_COUNT ---"
  tick_pmm_reads
  tick_weth_reads
  tick_weth_mock_sync
  tick_keeper
  tick_oracle
  tick_weth_wrap

  if [ -n "${MAX_TICKS:-}" ] && [ "$TICK_COUNT" -ge "$MAX_TICKS" ]; then
    log "MAX_TICKS=$MAX_TICKS reached, exit"
    break
  fi
  sleep "$INTERVAL"
done
