#!/usr/bin/env bash
# Bring up WireGuard + gretap (virtual ETH leg(s) for fixture bridge).
#
# Single-hop (legacy):
#   POG_EDGE_ROLE=office|pos4 sudo -E bash tunnel_up.sh
#
# Hub / spoke (wlan star):
#   EDGE_TOPOLOGY=hub   POG_EDGE_ROLE=hub   sudo -E bash tunnel_up.sh
#   EDGE_TOPOLOGY=spoke POG_EDGE_ROLE=spoke EDGE_SPOKE_ID=2 sudo -E bash tunnel_up.sh
#
# Hub creates deterministic eg-sNN gretaps per persisted peer (no sibling delete).
# Spoke creates one eg-sNN toward the hub.

set -euo pipefail

if [[ "${EUID}" -ne 0 ]]; then
  echo "Run with sudo" >&2
  exit 1
fi

EDGE_ROOT="$(cd "$(dirname "$0")" && pwd)"
# shellcheck disable=SC1091
source "${EDGE_ROOT}/pog_edge_env.sh"

if [[ -z "${POG_EDGE_ROLE:-}" ]]; then
  echo "FATAL: set POG_EDGE_ROLE=office|pos4|hub|spoke or EDGE_ROLE=peer-a|peer-b" >&2
  exit 1
fi

ACTIVE_GRETAP_FILE="${POG_EDGE_ROOT}/.active_gretap"
ACTIVE_SPOKE_FILE="${POG_EDGE_ROOT}/.active_spoke_gretaps"
HUB_STATE_DIR="${EDGE_HUB_STATE_DIR:-/etc/ethergold/hub}"

LOCAL_OVERLAY="${POG_EDGE_OVERLAY_A}"
PEER_OVERLAY="${POG_EDGE_OVERLAY_B}"
if [[ "${POG_EDGE_ROLE}" == "pos4" || "${POG_EDGE_ROLE}" == "spoke" ]]; then
  if [[ "${POG_EDGE_ROLE}" == "spoke" && -n "${EDGE_SPOKE_ID:-}" ]]; then
    LOCAL_OVERLAY="10.99.${EDGE_BANK}.${EDGE_SPOKE_ID}"
    PEER_OVERLAY="10.99.${EDGE_BANK}.1"
  else
    LOCAL_OVERLAY="${POG_EDGE_OVERLAY_B}"
    PEER_OVERLAY="${POG_EDGE_OVERLAY_A}"
  fi
fi
if [[ "${POG_EDGE_ROLE}" == "hub" ]]; then
  LOCAL_OVERLAY="10.99.${EDGE_BANK}.1"
  PEER_OVERLAY="" # multi-peer
fi

gretap_lower_dev() {
  local ifname="$1"
  ip -o link show "${ifname}" 2>/dev/null | sed -n 's/.*@\([^: ]*\).*/\1/p' | head -1
}

gretap_has_overlay_endpoints() {
  local ifname="$1"
  local local_ip="$2"
  local remote_ip="$3"
  local detail
  detail="$(ip -d link show "${ifname}" 2>/dev/null || true)"
  # iproute2 prints "gretap remote X local Y" — accept either order.
  [[ "${detail}" == *"local ${local_ip}"* && "${detail}" == *"remote ${remote_ip}"* ]]
}

ensure_wg_overlay_addr() {
  if ip -4 addr show dev "${WG_IF}" | grep -qF "${LOCAL_OVERLAY}/"; then
    return 0
  fi
  ip addr add "${LOCAL_OVERLAY}/32" dev "${WG_IF}"
  echo "Added ${LOCAL_OVERLAY}/32 on ${WG_IF} (GRE outer — wg-quick Table=off, no auto routes)"
}

# Single-hop: reuse matching gretapN; on mismatch SKIP (do not delete siblings).
# Prefer free name over destructive delete (EG 2.2.2.71 hub-spoke policy).
pick_or_create_gretap_pair() {
  local wg="$1"
  local local_ip="$2"
  local remote_ip="$3"
  local preferred="${4:-}"
  local n=0
  local name lower

  if [[ -n "${preferred}" ]]; then
    if ip link show "${preferred}" >/dev/null 2>&1; then
      if gretap_has_overlay_endpoints "${preferred}" "${local_ip}" "${remote_ip}"; then
        echo "${preferred}"
        return 0
      fi
      echo "WARN: ${preferred} endpoints mismatch — leaving in place, picking another name" >&2
    else
      ip link add "${preferred}" type gretap local "${local_ip}" remote "${remote_ip}" dev "${wg}"
      echo "${preferred}"
      return 0
    fi
  fi

  while [[ "${n}" -le 9 ]]; do
    name="gretap${n}"
    if ip link show "${name}" >/dev/null 2>&1; then
      if gretap_has_overlay_endpoints "${name}" "${local_ip}" "${remote_ip}"; then
        echo "${name}"
        return 0
      fi
      lower="$(gretap_lower_dev "${name}")"
      if [[ "${lower}" == "NONE" || -z "${lower}" ]]; then
        echo "WARN: ${name}@NONE is stuck — skipping (reboot Pi to clear)" >&2
        n=$((n + 1))
        continue
      fi
      # EG 2.2.2.71: do NOT delete mismatched gretaps (may belong to another spoke).
      echo "WARN: ${name} has different endpoints — skipping (no delete)" >&2
      n=$((n + 1))
      continue
    fi
    ip link add "${name}" type gretap local "${local_ip}" remote "${remote_ip}" dev "${wg}"
    echo "${name}"
    return 0
  done
  echo "FATAL: no free gretap name (gretap0..gretap9 busy)" >&2
  return 1
}

# Spoke owns a single eg-sNN leg — recreate on mismatch (safe; not a sibling).
# Do NOT fall through to legacy gretapN names (gretap0@NONE is a kernel stub).
ensure_spoke_gretap() {
  local wg="$1"
  local spoke_id="$2"
  local spoke_ip="$3"
  local hub_ip="$4"
  local name="eg-s$(printf '%02d' "${spoke_id}")"
  if ip link show "${name}" >/dev/null 2>&1; then
    if gretap_has_overlay_endpoints "${name}" "${spoke_ip}" "${hub_ip}"; then
      ip link set "${name}" up
      echo "${name}"
      return 0
    fi
    echo "WARN: ${name} endpoints stale — recreating (spoke owns this name)" >&2
    ip link set "${name}" down 2>/dev/null || true
    ip link del "${name}" 2>/dev/null || true
  fi
  ip link add "${name}" type gretap local "${spoke_ip}" remote "${hub_ip}" dev "${wg}"
  ip link set "${name}" up
  echo "${name}"
}

restore_hub_spoke_gretaps() {
  local wg="$1"
  local hub_ip="$2"
  local peers_json="${HUB_STATE_DIR}/peers.json"
  : > "${ACTIVE_SPOKE_FILE}"
  if [[ ! -f "${peers_json}" ]]; then
    return 0
  fi
  # Minimal JSON scrape without jq dependency.
  python3 - "${peers_json}" "${wg}" "${hub_ip}" "${ACTIVE_SPOKE_FILE}" <<'PY'
import json, subprocess, sys
path, wg, hub_ip, out_path = sys.argv[1:5]
try:
    peers = json.loads(open(path, encoding="utf-8").read())
except Exception:
    peers = {}
names = []
for pub, rec in (peers or {}).items():
    if not isinstance(rec, dict):
        continue
    spoke_id = int(rec.get("spoke_id") or 0)
    overlay = str(rec.get("overlay") or "")
    if spoke_id < 2 or not overlay:
        continue
    name = f"eg-s{spoke_id:02d}"
    check = subprocess.run(["ip", "link", "show", name], capture_output=True)
    if check.returncode != 0:
        subprocess.run(
            ["ip", "link", "add", name, "type", "gretap",
             "local", hub_ip, "remote", overlay, "dev", wg],
            check=False,
        )
    subprocess.run(["ip", "link", "set", name, "up"], check=False)
    # Live-add peer AllowedIPs /32 (idempotent).
    subprocess.run(
        ["wg", "set", wg, "peer", pub, "allowed-ips", f"{overlay}/32",
         "persistent-keepalive", "25"],
        check=False,
    )
    names.append(name)
open(out_path, "w", encoding="utf-8").write("\n".join(names) + ("\n" if names else ""))
print(",".join(names))
PY
}

bash "${EDGE_ROOT}/ensure_wg_keys.sh"
bash "${EDGE_ROOT}/prep_edge_uplink.sh"

if ! command -v wg >/dev/null 2>&1; then
  echo "FATAL: install wireguard — sudo apt install wireguard" >&2
  exit 1
fi

if ip link show "${WG_IF}" >/dev/null 2>&1; then
  # Hub/spoke: live peers may still be the old pos4↔pos5 hop while wg0.conf
  # already points at the hub — syncconf (or bounce) so conf wins.
  if [[ "${EDGE_TOPOLOGY:-}" == "hub" || "${EDGE_TOPOLOGY:-}" == "spoke" ]] \
    && [[ -f "${POG_EDGE_WG_CONF}" ]] && command -v wg-quick >/dev/null 2>&1; then
    echo "WARN: ${WG_IF} already up — syncing from ${POG_EDGE_WG_CONF}" >&2
    if ! wg syncconf "${WG_IF}" <(wg-quick strip "${POG_EDGE_WG_CONF}") 2>/dev/null; then
      echo "WARN: wg syncconf failed — bouncing ${WG_IF}" >&2
      wg-quick down "${POG_EDGE_WG_CONF}" 2>/dev/null || ip link del "${WG_IF}" 2>/dev/null || true
      wg-quick up "${POG_EDGE_WG_CONF}"
    fi
  else
    echo "WARN: ${WG_IF} already up — skipping wg-quick (run tunnel_down.sh to reconfigure keys)" >&2
    if [[ -f "${POG_EDGE_WG_CONF}" ]] && command -v wg >/dev/null 2>&1; then
      _ep="$(awk -F' = ' '/^Endpoint = /{print $2; exit}' "${POG_EDGE_WG_CONF}" || true)"
      _peer="$(wg show "${WG_IF}" peers 2>/dev/null | head -1 || true)"
      if [[ -n "${_ep}" && -n "${_peer}" && "${POG_EDGE_ROLE}" != "hub" ]]; then
        wg set "${WG_IF}" peer "${_peer}" endpoint "${_ep}" || true
        echo "Applied Endpoint ${_ep} from ${POG_EDGE_WG_CONF}"
      fi
    fi
  fi
else
  if command -v wg-quick >/dev/null 2>&1; then
    wg-quick up "${POG_EDGE_WG_CONF}"
  else
    echo "FATAL: wg-quick not found" >&2
    exit 1
  fi
fi

ensure_wg_overlay_addr

case "${POG_EDGE_ROLE}" in
  hub)
    SPOKE_LIST="$(restore_hub_spoke_gretaps "${WG_IF}" "${LOCAL_OVERLAY}" || true)"
    # Primary gretap name for tools that expect one: first spoke or placeholder.
    if [[ -n "${SPOKE_LIST}" ]]; then
      GRETAP_IF="$(printf '%s' "${SPOKE_LIST}" | cut -d, -f1)"
    else
      GRETAP_IF="eg-s02"
      echo "WARN: hub has no enrolled spokes yet — ETH_PORTS will grow on enroll" >&2
    fi
    printf '%s\n' "${GRETAP_IF}" > "${ACTIVE_GRETAP_FILE}"
    ;;
  spoke)
    SID="${EDGE_SPOKE_ID:-2}"
    GRETAP_IF="$(ensure_spoke_gretap "${WG_IF}" "${SID}" "${LOCAL_OVERLAY}" "${PEER_OVERLAY}")"
    printf '%s\n' "${GRETAP_IF}" > "${ACTIVE_GRETAP_FILE}"
    ;;
  *)
    GRETAP_IF="$(pick_or_create_gretap_pair "${WG_IF}" "${LOCAL_OVERLAY}" "${PEER_OVERLAY}" "")"
    ip link set "${GRETAP_IF}" up
    printf '%s\n' "${GRETAP_IF}" > "${ACTIVE_GRETAP_FILE}"
    ;;
esac

chown "${SUDO_USER:-topbox}:${SUDO_USER:-topbox}" "${ACTIVE_GRETAP_FILE}" 2>/dev/null || true
chmod 644 "${ACTIVE_GRETAP_FILE}"
export GRETAP_IF

echo ""
echo "=== tunnel up (${POG_EDGE_ROLE} topo=${EDGE_TOPOLOGY:-single-hop}) gretap=${GRETAP_IF} local=${LOCAL_OVERLAY} ==="
wg show "${WG_IF}" 2>/dev/null || true
ip -br addr show "${WG_IF}" 2>/dev/null || true
ip -d link show "${GRETAP_IF}" 2>/dev/null | head -3 || true
ip -br link show "${WG_IF}" "${GRETAP_IF}" "${POG_EDGE_LAN_IF}" 2>/dev/null || true
if [[ "${POG_EDGE_ROLE}" != "hub" && -n "${PEER_OVERLAY}" ]]; then
  echo ""
  echo "Ping peer overlay (sanity — ICMP may fail; L2 still works):"
  ping -c 2 -W 1 "${PEER_OVERLAY}" 2>/dev/null || echo "  (overlay ping skipped or failed — OK for gretap L2)"
fi
