#!/usr/bin/env bash
set -euo pipefail

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

if [[ -f "$REPO_ROOT/scripts/load-env.sh" ]]; then
  # shellcheck disable=SC1090
  source "$REPO_ROOT/scripts/load-env.sh" >/dev/null
elif [[ -f "$REPO_ROOT/../scripts/lib/load-project-env.sh" ]]; then
  # shellcheck disable=SC1090
  source "$REPO_ROOT/../scripts/lib/load-project-env.sh" >/dev/null
fi

APPLY=false
CHAIN_FILTER=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --apply) APPLY=true ;;
    --chain=*) CHAIN_FILTER="${1#*=}" ;;
    --chain)
      CHAIN_FILTER="${2:-}"
      shift
      ;;
    *)
      echo "Unknown arg: $1" >&2
      echo "Usage: $0 [--apply] [--chain <chain_id>]" >&2
      exit 1
      ;;
  esac
  shift
done

declare -A CHAIN_NAME=(
  [1]="Ethereum Mainnet"
  [10]="Optimism"
  [25]="Cronos"
  [56]="BSC"
  [100]="Gnosis"
  [137]="Polygon"
  [8453]="Base"
  [42161]="Arbitrum"
  [42220]="Celo"
  [43114]="Avalanche"
)

declare -A CHAIN_SELECTOR=(
  [1]="5009297550715157269"
  [10]="3734403246176062136"
  [25]="1456215246176062136"
  [56]="11344663589394136015"
  [100]="465200170687744372"
  [137]="4051577828743386545"
  [8453]="15971525489660198786"
  [42161]="4949039107694359620"
  [42220]="1346049177634351622"
  [43114]="6433500567565415381"
)

rpc_for_chain() {
  case "$1" in
    1) echo "${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-${ETHEREUM_RPC_URL:-}}}" ;;
    10) echo "${OPTIMISM_RPC_URL:-${OPTIMISM_MAINNET_RPC:-}}" ;;
    25) echo "${CRONOS_RPC_URL:-}" ;;
    56) echo "${BSC_RPC_URL:-${BSC_MAINNET_RPC:-}}" ;;
    100) echo "${GNOSIS_RPC_URL:-${GNOSIS_MAINNET_RPC:-${GNOSIS_RPC:-}}}" ;;
    137) echo "${POLYGON_RPC_URL:-${POLYGON_MAINNET_RPC:-}}" ;;
    8453) echo "${BASE_RPC_URL:-${BASE_MAINNET_RPC:-}}" ;;
    42161) echo "${ARBITRUM_RPC_URL:-${ARBITRUM_MAINNET_RPC:-}}" ;;
    42220) echo "${CELO_RPC_URL:-}" ;;
    43114) echo "${AVALANCHE_RPC_URL:-${AVALANCHE_MAINNET_RPC:-}}" ;;
    *) echo "" ;;
  esac
}

bridge_for_chain() {
  case "$1" in
    1) echo "${CW_BRIDGE_MAINNET:-}" ;;
    10) echo "${CW_BRIDGE_OPTIMISM:-}" ;;
    25) echo "${CW_BRIDGE_CRONOS:-}" ;;
    56) echo "${CW_BRIDGE_BSC:-}" ;;
    100) echo "${CW_BRIDGE_GNOSIS:-}" ;;
    137) echo "${CW_BRIDGE_POLYGON:-}" ;;
    8453) echo "${CW_BRIDGE_BASE:-}" ;;
    42161) echo "${CW_BRIDGE_ARBITRUM:-}" ;;
    42220) echo "${CW_BRIDGE_CELO:-}" ;;
    43114) echo "${CW_BRIDGE_AVALANCHE:-}" ;;
    *) echo "" ;;
  esac
}

probe_generation() {
  local bridge="$1" rpc="$2"
  if timeout 10 cast call "$bridge" 'sendRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
    echo "new"
    return
  fi
  if timeout 10 cast call "$bridge" 'ccipRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
    echo "old"
    return
  fi
  echo "unknown"
}

old_has_key() {
  local bridge="$1" rpc="$2" key="$3"
  local destinations
  destinations="$(timeout 12 cast call "$bridge" 'getDestinationChains()(uint64[])' --rpc-url "$rpc" 2>/dev/null || true)"
  [[ -z "$destinations" ]] && return 1

  DESTINATIONS_PAYLOAD="$destinations" python3 - "$key" <<'PY'
import re
import os
import sys

target = sys.argv[1]
payload = os.environ.get("DESTINATIONS_PAYLOAD", "")
numbers = re.findall(r'\d+', payload)
sys.exit(0 if target in numbers else 1)
PY
}

new_read_destination() {
  local bridge="$1" rpc="$2" key="$3"
  timeout 12 cast call "$bridge" 'destinations(uint64)((address,bool))' "$key" --rpc-url "$rpc" 2>/dev/null || true
}

normalize_addr() {
  printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

new_matches() {
  local raw="$1" expected="$2"
  local lower_raw lower_expected
  lower_raw="$(normalize_addr "$raw")"
  lower_expected="$(normalize_addr "$expected")"
  [[ "$lower_raw" == *"$lower_expected"* && "$lower_raw" == *"true"* ]]
}

send_tx() {
  local rpc="$1" bridge="$2" signature="$3"
  shift 3
  cast send "$bridge" "$signature" "$@" --rpc-url "$rpc" --private-key "$PRIVATE_KEY"
}

configure_old() {
  local source_chain="$1" target_key="$2" target_bridge="$3"
  local rpc bridge op
  rpc="$(rpc_for_chain "$source_chain")"
  bridge="$(bridge_for_chain "$source_chain")"
  if old_has_key "$bridge" "$rpc" "$target_key"; then
    op="updateDestination(uint64,address)"
  else
    op="addDestination(uint64,address)"
  fi
  if [[ "$APPLY" == true ]]; then
    send_tx "$rpc" "$bridge" "$op" "$target_key" "$target_bridge"
  else
    echo "DRY-RUN old   chain=$source_chain key=$target_key bridge=$target_bridge op=$op"
  fi
}

configure_new() {
  local source_chain="$1" target_key="$2" target_bridge="$3"
  local rpc bridge raw
  rpc="$(rpc_for_chain "$source_chain")"
  bridge="$(bridge_for_chain "$source_chain")"
  raw="$(new_read_destination "$bridge" "$rpc" "$target_key")"
  if new_matches "$raw" "$target_bridge"; then
    echo "SKIP   new   chain=$source_chain key=$target_key already=$raw"
    return
  fi
  if [[ "$APPLY" == true ]]; then
    send_tx "$rpc" "$bridge" 'configureDestination(uint64,address,bool)' "$target_key" "$target_bridge" true
  else
    echo "DRY-RUN new   chain=$source_chain key=$target_key bridge=$target_bridge"
  fi
}

configure_key() {
  local source_chain="$1" target_key="$2" target_bridge="$3" generation="$4"
  case "$generation" in
    old) configure_old "$source_chain" "$target_key" "$target_bridge" ;;
    new) configure_new "$source_chain" "$target_key" "$target_bridge" ;;
    *)
      echo "ERROR unknown generation for chain $source_chain" >&2
      return 1
      ;;
  esac
}

echo "Public cW bridge mesh configuration"
echo "Apply mode: $APPLY"
echo

declare -A GENERATION=()

for chain_id in 1 10 25 56 100 137 8453 42161 42220 43114; do
  [[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$chain_id" ]] && continue
  rpc="$(rpc_for_chain "$chain_id")"
  bridge="$(bridge_for_chain "$chain_id")"
  if [[ -z "$rpc" || -z "$bridge" ]]; then
    echo "WARN chain=$chain_id missing rpc or bridge env; skipping"
    continue
  fi
  generation="$(probe_generation "$bridge" "$rpc")"
  GENERATION["$chain_id"]="$generation"
  echo "chain=$chain_id name='${CHAIN_NAME[$chain_id]}' generation=$generation bridge=$bridge"
done

echo

for source_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
  [[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$source_chain" ]] && continue
  [[ -z "${GENERATION[$source_chain]:-}" ]] && continue
  for target_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
    [[ "$source_chain" == "$target_chain" ]] && continue
    target_bridge="$(bridge_for_chain "$target_chain")"
    [[ -z "$target_bridge" ]] && continue

    # Outbound path on source uses the target CCIP selector.
    configure_key "$source_chain" "${CHAIN_SELECTOR[$target_chain]}" "$target_bridge" "${GENERATION[$source_chain]}"

    # Inbound receive path on source uses the remote public chain id.
    configure_key "$source_chain" "$target_chain" "$target_bridge" "${GENERATION[$source_chain]}"
  done
done

echo
echo "Done."
