#!/usr/bin/env bash
# labctl — Adjacency Labs launcher
# Usage: ./labctl <deploy|verify|reset|destroy|status>
set -euo pipefail

LAB_NAME="ospf-area-boundary-trap"
TOPO="topology.yml"
FAULT_FILE="faults/inject.b64"
NODES=(r1 r2 r3)
CONVERGE_TIMEOUT=90
DEFAULT_CTX="router ospf"
SUMMARY="10.3.0.0/22"

cd "$(dirname "$0")"

# ---------- helpers ----------
c() { docker exec "clab-${LAB_NAME}-$1" vtysh -c "$2" 2>/dev/null; }
# conf <node> <context> <command> — context is a config-mode line such as
# "router ospf", "router bgp 65001" or "interface eth1".
conf() {
  docker exec "clab-${LAB_NAME}-$1" vtysh \
    -c "configure terminal" -c "$2" -c "$3" >/dev/null 2>&1
}
ok()   { printf '  \033[32m✔\033[0m %s\n' "$1"; }
fail() { printf '  \033[31m✘\033[0m %s\n' "$1"; }
info() { printf '\033[2m%s\033[0m\n' "$1"; }

wait_converged() {
  info "waiting for OSPF to converge..."
  local t=0
  while (( t < CONVERGE_TIMEOUT )); do
    local full
    full=$(c r2 "show ip ospf neighbor" | grep -c "Full" || true)
    if (( full >= 2 )); then return 0; fi
    sleep 3; t=$((t+3))
  done
  fail "OSPF did not converge within ${CONVERGE_TIMEOUT}s"; return 1
}

# Fault lines are "node|command" (applied under DEFAULT_CTX) or
# "node|context|command" when a fault needs a different config context.
apply_fault() {
  info "applying scenario..."
  local node a b ctx cmd
  while IFS='|' read -r node a b; do
    [[ -z "$node" ]] && continue
    if [[ -n "${b:-}" ]]; then ctx="$a"; cmd="$b"; else ctx="$DEFAULT_CTX"; cmd="$a"; fi
    conf "$node" "$ctx" "$cmd"
  done < <(base64 -d "$FAULT_FILE")
  sleep 8
}

# ---------- commands ----------
cmd_deploy() {
  sudo containerlab deploy -t "$TOPO" --reconfigure >/dev/null
  wait_converged
  apply_fault
  echo
  echo "Lab is up. Start on r1:"
  echo "  docker exec -it clab-${LAB_NAME}-r1 vtysh"
  echo "  r1# show ip route ospf"
  echo
  echo "Read scenario.md, then run ./labctl verify when you think it's fixed."
}

cmd_verify() {
  echo "Verifying..."
  local pass=0 total=4
  local r1routes r2cfg r3cfg

  r1routes=$(c r1 "show ip route ospf" || true)
  r2cfg=$(c r2 "show running-config" || true)
  r3cfg=$(c r3 "show running-config" || true)

  # 1. r1 learns the summary through OSPF. Reading "show ip route ospf"
  #    means a static route added by hand cannot satisfy this.
  if echo "$r1routes" | grep -qE "^O[^[:space:]]*[[:space:]]+10\.3\.0\.0/22([[:space:]]|$)"; then
    ok "r1 has ${SUMMARY} via OSPF"; pass=$((pass+1))
  else
    fail "r1 has no OSPF route for ${SUMMARY}"
  fi

  # 2. the component prefixes are no longer reaching area 0
  if ! echo "$r1routes" | grep -qE '10\.3\.[123]\.0/24'; then
    ok "no component /24s on r1"; pass=$((pass+1))
  else
    fail "r1 still has the component /24s"
  fi

  # 3/4. where the range is configured
  if echo "$r2cfg" | grep -qE "area 1 range 10\.3\.0\.0/22"; then
    ok "r2 has an area range for ${SUMMARY}"; pass=$((pass+1))
  else
    fail "r2 has no area range for ${SUMMARY}"
  fi

  if ! echo "$r3cfg" | grep -qE 'area 1 range'; then
    ok "r3 has no area range configured"; pass=$((pass+1))
  else
    fail "r3 still has an area range configured"
  fi

  echo
  if (( pass == total )); then
    printf '\033[32mPASS\033[0m  %d/%d — lab complete.\n' "$pass" "$total"
  else
    printf '\033[33mNOT YET\033[0m  %d/%d — keep going.\n' "$pass" "$total"
    exit 1
  fi
}

cmd_reset() {
  info "resetting to scenario start..."
  sudo containerlab deploy -t "$TOPO" --reconfigure >/dev/null
  wait_converged
  apply_fault
  echo "Reset. Lab is back to the broken state."
}

cmd_status() {
  for n in "${NODES[@]}"; do
    echo "== $n =="
    c "$n" "show ip ospf neighbor" | tail -n +2
  done
  echo "== r1 OSPF routes =="
  c r1 "show ip route ospf" | grep -E '^O' || echo "(none)"
}

cmd_destroy() {
  sudo containerlab destroy -t "$TOPO" >/dev/null && echo "Lab destroyed."
}

case "${1:-}" in
  deploy)  cmd_deploy ;;
  verify)  cmd_verify ;;
  reset)   cmd_reset ;;
  status)  cmd_status ;;
  destroy) cmd_destroy ;;
  *) echo "Usage: ./labctl <deploy|verify|reset|status|destroy>"; exit 1 ;;
esac
