#!/bin/bash
# A Parent Call sanctuary that cannot reach the sanctuary.
#
# The real coordinator, from the control computer, running in the lab
# against a broker on loopback and a ProPresenter that is a mock. The
# church's own networks are rejected from inside the container first, so
# a wrong host in a config file cannot put a child's name on the real
# screen. Coordinator development has done that once already.
#
#   tools/lab/parent-call-rig up [MODE]   # off | approval | auto
#   tools/lab/parent-call-rig mode MODE
#   tools/lab/parent-call-rig approve|reject
#   tools/lab/parent-call-rig watch
#   tools/lab/parent-call-rig triggers
#   tools/lab/parent-call-rig down
set -uo pipefail

NAME="${LAB_NAME:-classroom-v2-lab}"
SRC="${PARENT_CALL_SRC:-classroom-control:/home/media/docker/parent-call}"
ROOT=classroom/parent-call

inlab() { docker exec "$NAME" "$@"; }

guard() {
    # Container-local. This netns belongs to the container alone, so
    # nothing on the host or the church network is changed by it.
    # Blackhole routes, not iptables: the lab image has no iptables at
    # all, so these two lines had been failing silently since they were
    # written. `tools/lab/lab up` now does the same thing at start-up, so
    # this is a second statement of it rather than the only one.
    inlab sh -c '
        for net in 10.30.0.0/16 10.40.0.0/16; do
            ip route replace blackhole "$net" || exit 1
        done'

    # bash, not sh. /dev/tcp is a bash feature and this probe reported
    # every target unreachable under dash, whatever the network was doing.
    for target in 10.40.0.2/1883 10.30.0.2/1025; do
        if inlab timeout 4 bash -c "exec 3<>/dev/tcp/${target}" 2>/dev/null; then
            echo "REFUSING TO CONTINUE: ${target} is still reachable" >&2
            exit 1
        fi
    done
    echo "  church networks unreachable from the lab"
}

case "${1:-up}" in
up)
    mode="${2:-approval}"

    inlab sh -c 'apt-get install -y -qq --no-install-recommends \
        mosquitto mosquitto-clients python3-paho-mqtt iptables >/dev/null 2>&1'
    guard

    inlab sh -c 'mkdir -p /opt/pc-test'
    ssh -o BatchMode=yes "${SRC%%:*}" "cd ${SRC#*:} && tar cf - src" \
        | docker exec -i "$NAME" sh -c 'cd /opt/pc-test && tar xf -'
    echo "  coordinator source copied from the control computer"

    docker exec -i "$NAME" sh -c 'cat > /opt/pc-test/mosquitto.conf' <<CONF
# Loopback only. Nothing published here can leave the container.
listener 1883 127.0.0.1
allow_anonymous true
persistence false
CONF

    docker exec -i "$NAME" sh -c 'cat > /opt/pc-test/coordinator.env' <<ENV
MQTT_HOST=127.0.0.1
MQTT_PORT=1883
MQTT_CLIENT_ID=parent-call-lab
TOPIC_ROOT=$ROOT
PROPRESENTER_BASE_URL=http://127.0.0.1:1025
PROPRESENTER_MESSAGE='Parent Caller'
PROPRESENTER_CHILD_TOKEN=Name
PROPRESENTER_ROOM_TOKEN=Room
PROPRESENTER_TIMEOUT=4
PROPRESENTER_CLEAR_AFTER=20
ROOM_NAMES='nursery=Nursery,k2=K-2,older=Older Kids'
REQUEST_TTL=180
MAX_CHILD_LENGTH=40
MAX_ROOM_LENGTH=32
MAX_CLOCK_SKEW=120
DEFAULT_MODE=$mode
HEALTH_PORT=8099
ENV

    docker exec -i "$NAME" sh -c 'cat > /opt/propresenter-mock.py' \
        < "$(dirname "$0")/propresenter-mock.py"

    # Debian's own mosquitto unit would listen where the lab does not want
    # it to. The rig runs its own, on loopback, and nothing else.
    inlab sh -c 'systemctl disable --now mosquitto >/dev/null 2>&1;
        pkill -f propresenter-mock; pkill -f parent_call.main; pkill mosquitto;
        fuser -k 1025/tcp 1883/tcp 2>/dev/null; true'
    sleep 3
    inlab sh -c 'setsid mosquitto -c /opt/pc-test/mosquitto.conf > /tmp/mosquitto.log 2>&1 &'
    inlab sh -c 'setsid python3 /opt/propresenter-mock.py 1025 > /tmp/ppmock.log 2>&1 &'
    sleep 2
    inlab sh -c 'cd /opt/pc-test && set -a && . ./coordinator.env && set +a &&
        setsid env PYTHONPATH=/opt/pc-test/src python3 -m parent_call.main \
            > /tmp/coordinator.log 2>&1 &'
    sleep 4

    # The room points at the loopback broker, never at the church's.
    # docker exec needs -i or the heredoc reaches nothing at all, which is
    # how this silently left the room pointing nowhere.
    docker exec -i "$NAME" python3 - <<'PY'
import pathlib
p = pathlib.Path("/etc/ekklesia-classroom/device.conf")
t = p.read_text()
t = t.replace("MQTT_ENABLED=false", "MQTT_ENABLED=true")
if "MQTT_HOST=127.0.0.1" not in t:
    t = t.replace("MQTT_HOST=", "MQTT_HOST=127.0.0.1", 1)
if "\nROOM=" not in t:
    t += "\n# Lab only.\nROOM=nursery\n"
p.write_text(t)
PY

    "$0" mode "$mode"
    inlab tail -3 /tmp/coordinator.log
    echo "  rig up in mode $mode"
    ;;

mode)
    inlab mosquitto_pub -h 127.0.0.1 -t "$ROOT/mode/set" -m "${2:?mode}"
    sleep 1
    echo -n "  mode is now: "
    inlab timeout 3 mosquitto_sub -h 127.0.0.1 -t "$ROOT/mode" -C 1 || true
    ;;

approve|reject)
    id=$(inlab timeout 3 mosquitto_sub -h 127.0.0.1 -t "$ROOT/pending" -C 1 |
         python3 -c 'import json,sys; print(json.load(sys.stdin).get("id") or "")')
    [ -n "$id" ] || { echo "  nothing is waiting"; exit 0; }
    inlab mosquitto_pub -h 127.0.0.1 -t "$ROOT/$1" -m "{\"id\": \"$id\"}"
    echo "  $1ed $id"
    ;;

watch)
    inlab mosquitto_sub -h 127.0.0.1 -t "$ROOT/#" -v
    ;;

triggers)
    inlab sh -c 'cat /tmp/propresenter-mock.log 2>/dev/null || echo "(none)"'
    ;;

log)
    inlab tail -n "${2:-25}" /tmp/coordinator.log
    ;;

down)
    inlab sh -c 'pkill -f parent_call.main; pkill -f propresenter-mock; pkill mosquitto; true'
    echo "  rig down"
    ;;

*)
    sed -n '2,14p' "$0"
    exit 1
    ;;
esac
