#!/usr/bin/env bash
# scripts/phase-00-maas-standup.sh [--apply]
#
# MAAS topology stand-up for the v1 (VR0 / Baldurkeep) plane scheme (D-058).
# Idempotently brings MAAS to the target fabric/VLAN/subnet/space layout so the
# carve + bundle can resolve every plane. Useful BOTH for a fresh test cloud
# (everything absent -> full create plan) and for an existing cloud (present-and-
# correct -> SKIP; present-but-wrong -> reported as DRIFT, never silently changed).
#
# Default is DRY-RUN (the audit): resolves every id live BY CIDR/NAME (PATTERN-1,
# no hardcoded MAAS ids) and prints each mutation it WOULD run, changing nothing.
# Pass --apply to execute. Re-runnable; anything already correct is SKIPped.
#
# SAFETY: this script NEVER deletes. A re-CIDR (a subnet present at a CIDR that
# D-058 reassigns to a different plane -- e.g. the live D-052/053 cloud where
# 10.12.8/22 is metal-admin but D-058 wants it for provider-vip) is DESTRUCTIVE
# (MAAS cannot change a subnet's CIDR in place) and is therefore OUT OF SCOPE:
# it is reported in the DRIFT section as MIGRATE-NEEDED and gated to a separate
# human teardown step. This script will refuse to create a target subnet whose
# CIDR is occupied by the wrong plane.
#
# Scope boundary vs phase-00-maas-carve.sh: THIS script owns topology (fabric/
# VLAN/subnet/space/gateway/managed/dns) + the per-plane API-VIP reserve bands
# (.2-.100 on the three VIP-bearing planes), which the bundle deploy depends on.
# The FIP pool, mgmt reserves, and stale-range cleanup stay in phase-00-maas-carve.
#
# Order matters (MAAS semantics + fresh-fabric bootstrap): untagged base planes
# first (each owns a fabric), then their tagged siblings ride that fabric, so a
# fresh MAAS bootstraps provider-public -> provider-vip, metal-admin -> metal-internal.
#
# Exit: 0 ok (no drift) | 1 fatal or unresolved drift | 2 precondition
# CLI forms verified against Canonical MAAS how-to-manage-networks.
# ASCII + LF.

set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/lib-net.sh
. "$SCRIPT_DIR/lib-net.sh"

MAAS_PROFILE="${MAAS_PROFILE:-admin}"
MODE="dryrun"; [ "${1:-}" = "--apply" ] && MODE="apply"
FATAL=0; DRIFT=0

hdr()  { echo; echo "=== $* ==="; }
note() { echo "  - $*"; }
fail() { echo "FAIL: $*" >&2; FATAL=$((FATAL+1)); }
need_jq || exit 1
maas_q() { maas "$MAAS_PROFILE" "$@"; }

emit() {  # <desc> <maas args...>
  local desc="$1"; shift
  if [ "$MODE" = "apply" ]; then
    echo "  DO: $desc"
    local out
    if ! out="$(maas "$MAAS_PROFILE" "$@" 2>&1)"; then
      fail "$desc"
      echo "       MAAS said: $(printf '%s' "$out" | grep -viE '^(Success|Machine-readable)' | head -3 | tr '\n' ' ')" >&2
      return 1
    fi
  else
    echo "  WOULD: $desc"
    echo "         maas $MAAS_PROFILE $*"
  fi
}

# --- PATTERN-1 resolve-by-CIDR/name helpers (no hardcoded ids) ---------------
sub_id()      { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|.id' | head -1; }
sub_vid()     { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|(.vlan.vid|tostring)' | head -1; }
sub_fabid()   { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|(.vlan.fabric_id|tostring)' | head -1; }
sub_space()   { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|(.space // "")' | head -1; }
sub_field()   { maas_q subnets read | jq -r --arg c "$1" --arg f "$2" '.[]|select(.cidr==$c)|(.[$f] // "")' | head -1; }
sub_mtu()     { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|(.vlan.mtu|tostring)' | head -1; }
sub_dns()     { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|(.dns_servers // []|join(","))' | head -1; }
space_id()    { maas_q spaces  read | jq -r --arg n "$1" '.[]|select(.name==$n)|(.id|tostring)' | head -1; }
fab_byname()  { maas_q fabrics read | jq -r --arg n "$1" '.[]|select(.name==$n)|(.id|tostring)' | head -1; }
vlanobj()     { maas_q vlans read "$1" | jq -r --arg v "$2" '.[]|select((.vid|tostring)==$v)|(.id|tostring)' | head -1; }
vlanspace()   { maas_q vlans read "$1" | jq -r --arg v "$2" '.[]|select((.vid|tostring)==$v)|(.space // "")' | head -1; }
vlan0obj()    { vlanobj "$1" 0; }   # the untagged (vid 0) default VLAN of a fabric

# --- target plane table (D-058): name|cidr|kind|vid|parent_cidr|gw|viplo|viphi|dnssrc
#   kind=untagged owns a fabric; kind=tagged rides parent_cidr's fabric on <vid>.
#   "-" = none. dnssrc = a CIDR whose dns_servers to mirror, or "-".
PLANES="$(cat <<TBL
provider-public|10.12.4.0/22|untagged|-|-|10.12.4.1|-|-|-
provider-vip|10.12.8.0/22|tagged|104|10.12.4.0/22|10.12.8.1|10.12.8.2|10.12.8.100|10.12.16.0/22
metal-admin|10.12.12.0/22|untagged|-|-|10.12.12.1|10.12.12.2|10.12.12.100|-
metal-internal|10.12.16.0/22|tagged|103|10.12.12.0/22|-|10.12.16.2|10.12.16.100|-
data-tenant|10.12.20.0/22|untagged|-|-|-|-|-|-
storage|10.12.32.0/22|untagged|-|-|-|-|-|-
replication|10.12.36.0/22|untagged|-|-|-|-|-|-
TBL
)"

dt() { [ "$1" = "-" ] && echo "" || echo "$1"; }   # decode "-" sentinel to empty

hdr "MAAS stand-up  mode=$MODE  (D-058 target scheme)"

# ------------------------------------------------------------------ DRIFT scan
# Refuse-to-clobber: any live subnet sitting at a TARGET cidr but bound to the
# WRONG plane (space) or wrong vid is a re-CIDR/migration the human must resolve.
hdr "drift scan (non-destructive; reports re-CIDR/migration needs)"
declare -A WRONG_CIDR=()
while IFS='|' read -r name cidr kind vid _rest; do
  [ -n "$name" ] || continue
  curspace="$(sub_space "$cidr")"
  [ -n "$curspace" ] || continue                      # absent -> nothing to drift
  if [ "$curspace" != "$name" ]; then
    note "DRIFT: $cidr is space '$curspace' but D-058 assigns it to '$name' -- MIGRATE (delete+recreate; gated, NOT done here)"
    WRONG_CIDR["$cidr"]=1; DRIFT=$((DRIFT+1)); continue
  fi
  if [ "$kind" = tagged ]; then
    cv="$(sub_vid "$cidr")"
    if [ "$cv" != "$vid" ]; then
      note "DRIFT: $cidr space ok ('$name') but VID '$cv' != target $vid -- MIGRATE (gated)"
      WRONG_CIDR["$cidr"]=1; DRIFT=$((DRIFT+1))
    fi
  fi
done <<< "$PLANES"
[ "$DRIFT" -eq 0 ] && note "no drift: no target CIDR is occupied by the wrong plane"

# ------------------------------------------------------------- per-plane standup
while IFS='|' read -r name cidr kind vid parent gw viplo viphi dnssrc; do
  [ -n "$name" ] || continue
  gw="$(dt "$gw")"; viplo="$(dt "$viplo")"; viphi="$(dt "$viphi")"; dnssrc="$(dt "$dnssrc")"
  hdr "plane $name ($cidr, $kind${vid:+ }$( [ "$kind" = tagged ] && echo "VID $vid" ))"

  # refuse to build onto a CIDR the drift scan flagged as the wrong plane
  if [ -n "${WRONG_CIDR[$cidr]:-}" ]; then
    fail "$cidr occupied by the wrong plane (see DRIFT) -- resolve the migration first; skipping $name"
    continue
  fi

  # ---- resolve the fabric this plane lives on ----
  fab=""
  if [ -n "$(sub_id "$cidr")" ]; then
    fab="$(sub_fabid "$cidr")"
  elif [ "$kind" = tagged ]; then
    fab="$(sub_fabid "$parent")"
    if [ -z "$fab" ]; then
      [ "$MODE" = apply ] && { fail "$name: parent subnet $parent absent -- create it first"; continue; }
      fab="<fabric-of-$parent>"   # dry-run: parent is planned above; show the plan
    fi
  else
    fab="$(fab_byname "$name")"
    if [ -z "$fab" ]; then
      emit "create fabric $name" fabrics create name="$name"
      fab="$(fab_byname "$name")"               # re-resolve (apply: now exists)
      [ -n "$fab" ] || fab="<fabric-$name-id>"  # dry-run placeholder
    fi
  fi
  note "fabric = $fab"

  # ---- space ----
  if [ -z "$(space_id "$name")" ]; then
    emit "create space $name" spaces create name="$name"
  else note "space $name exists -- SKIP"; fi
  sid="$(space_id "$name")"; [ -n "$sid" ] || sid="<space-$name-id>"

  # ---- VLAN + the vlan-obj the subnet will ride ----
  if [ "$kind" = tagged ]; then
    if [ -z "$(vlanobj "$fab" "$vid")" ]; then
      mtu="$(sub_mtu "$parent")"; { [ -n "$mtu" ] && [ "$mtu" != null ]; } || mtu="1500"
      emit "create VLAN vid=$vid name=$name mtu=$mtu on fabric $fab" \
        vlans create "$fab" name="$name" vid="$vid" mtu="$mtu"
    else note "VID $vid on fabric $fab exists -- SKIP"; fi
    if [ "$(vlanspace "$fab" "$vid")" != "$name" ]; then
      emit "assign fabric $fab vid $vid -> space $name (id $sid)" vlan update "$fab" "$vid" space="$sid"
    else note "VID $vid already in space $name -- SKIP"; fi
    vobj="$(vlanobj "$fab" "$vid")"; [ -n "$vobj" ] || vobj="<vid-$vid-on-fab-$fab-id>"
  else
    # untagged: rides the fabric default (vid 0); assign that vid-0 VLAN to the space
    if [ "$(vlanspace "$fab" 0)" != "$name" ]; then
      emit "assign fabric $fab untagged(vid 0) -> space $name (id $sid)" vlan update "$fab" 0 space="$sid"
    else note "untagged VLAN on fabric $fab already in space $name -- SKIP"; fi
    vobj="$(vlan0obj "$fab")"; [ -n "$vobj" ] || vobj="<untagged-vlan-on-fab-$fab-id>"
  fi

  # ---- subnet (guard wrong-VID if present) ----
  if [ -z "$(sub_id "$cidr")" ]; then
    emit "create subnet $cidr vlan=$vobj" subnets create cidr="$cidr" vlan="$vobj"
  else
    if [ "$kind" = tagged ]; then
      got="$(sub_vid "$cidr")"
      [ "$got" = "$vid" ] || { fail "subnet $cidr exists on VID '$got', expected $vid -- refusing"; continue; }
    fi
    note "subnet $cidr exists -- SKIP create"
  fi

  # ---- gateway / managed / dns ----
  if [ -n "$gw" ]; then
    if [ "$(sub_field "$cidr" gateway_ip)" != "$gw" ]; then
      emit "subnet $cidr -> gateway_ip=$gw" subnet update "$cidr" gateway_ip="$gw"
    else note "gateway_ip already $gw -- SKIP"; fi
  fi
  if [ "$(sub_field "$cidr" managed)" != "true" ]; then
    emit "subnet $cidr -> managed=true" subnet update "$cidr" managed=true
  else note "subnet $cidr already managed -- SKIP"; fi
  if [ -n "$dnssrc" ]; then
    dns="$(sub_dns "$dnssrc")"
    if [ -n "$dns" ] && [ "$dns" != null ]; then
      if [ "$(sub_dns "$cidr")" != "$dns" ]; then
        emit "subnet $cidr -> dns_servers=$dns (mirrors $dnssrc)" subnet update "$cidr" dns_servers="$dns"
      else note "dns_servers already $dns -- SKIP"; fi
    else note "dns source $dnssrc has no dns_servers -- leaving $cidr dns unset"; fi
  fi

  # ---- reserved API-VIP band ----
  if [ -n "$viplo" ]; then
    if maas_q ipranges read | jq -e --arg lo "$viplo" '.[]|select(.start_ip==$lo)' >/dev/null 2>&1; then
      note "reserved range starting $viplo exists -- SKIP"
    else
      rsid="$(sub_id "$cidr")"; [ -n "$rsid" ] || rsid="<subnet-$cidr-id>"
      emit "create reserved API-VIP band $viplo-$viphi on subnet $rsid" \
        ipranges create type=reserved subnet="$rsid" start_ip="$viplo" end_ip="$viphi" \
        comment="$name API HA VIP band (D-058)"
    fi
  fi
done <<< "$PLANES"

# ----------------------------------------------------------------------- result
hdr "result"
[ "$DRIFT" -eq 0 ] || echo "  $DRIFT plane(s) need a gated re-CIDR/migration before they can be stood up (see DRIFT)."
if [ "$FATAL" -ne 0 ]; then echo "  completed with $FATAL failure(s)"; exit 1; fi
if [ "$DRIFT" -ne 0 ]; then echo "  OK ($MODE) -- but exit 1 due to unresolved drift"; exit 1; fi
echo "  OK ($MODE) -- topology consistent with D-058"
