#!/usr/bin/env bash
# deploy/smoke/compose_smoke.sh — T2 compose smoke (deploy-B §9).
#
# Brings up deploy/compose.yaml with `docker compose up -d --wait`, asserts
# every service reports healthy via `docker compose ps`, then curls
# /healthz through Caddy's TLS (using the internal CA —
# RUTSTER_LOCAL_CERTS=true is set in the smoke's env). The compose smoke
# is intentionally lighter than the all-in-one smoke (which already proves
# the WS path) — its job is to assert the four-service orchestrator shape
# boots + the network_mode: "service:engine" brain->engine tap posture.
#
# T8: the slice-C EventSink-lands-in-stream assertion is wired in below
# (ws_handshake.py + XLEN rutster:events). The all-in-one smoke currently
# imports the same WS frames inline; ws_handshake.py is the shared client.

set -euo pipefail

cd "$(dirname "$0")/.."  # deploy/

IMAGES_TAG="${RUTSTER_IMAGES_TAG:-smoke}"

# T7: compose's `env_file: .env` reads the file, not shell exports. The
# pre-T7 script exported vars via `export RUTSTER_*` — those never reached
# the containers. Create deploy/.env from .env.example + apply smoke
# overrides INTO that file; trap removes it on exit.
cp .env.example .env
trap 'rm -f .env' EXIT

# Rewrite the matching rows in .env (not the shell env).
sed -i 's|^RUTSTER_DOMAIN=.*|RUTSTER_DOMAIN=localhost|' .env
sed -i 's|^RUTSTER_ACME_EMAIL=.*|RUTSTER_ACME_EMAIL=smoke@example.com|' .env
# RUTSTER_LOCAL_CERTS is commented out in .env.example by default; uncomment + set true.
sed -i 's|^# RUTSTER_LOCAL_CERTS=.*|RUTSTER_LOCAL_CERTS=true|' .env
grep -qE '^RUTSTER_LOCAL_CERTS=' .env || echo 'RUTSTER_LOCAL_CERTS=true' >> .env
sed -i 's|^RUTSTER_HTTP_BIND=.*|RUTSTER_HTTP_BIND=0.0.0.0:8080|' .env
sed -i 's|^RUTSTER_MEDIA_BIND_IP=.*|RUTSTER_MEDIA_BIND_IP=127.0.0.1|' .env
sed -i 's|^RUTSTER_MEDIA_PORT_RANGE=.*|RUTSTER_MEDIA_PORT_RANGE=49160-49170|' .env
sed -i 's|^RUTSTER_DRAIN_DEADLINE_SECS=.*|RUTSTER_DRAIN_DEADLINE_SECS=10|' .env
# RUTSTER_VALKEY_URL + RUTSTER_TAP_URL are commented out in .env.example; uncomment + set.
sed -i 's|^# RUTSTER_VALKEY_URL=.*|RUTSTER_VALKEY_URL=redis://valkey:6379/|' .env
grep -qE '^RUTSTER_VALKEY_URL=' .env || echo 'RUTSTER_VALKEY_URL=redis://valkey:6379/' >> .env
sed -i 's|^RUTSTER_TAP_URL=.*|RUTSTER_TAP_URL=ws://127.0.0.1:8082|' .env
grep -qE '^RUTSTER_TAP_URL=' .env || echo 'RUTSTER_TAP_URL=ws://127.0.0.1:8082' >> .env
# Export too — the script's own docker compose exec / curl calls below use these.
export RUTSTER_DOMAIN=localhost
export RUTSTER_ACME_EMAIL=smoke@example.com
export RUTSTER_LOCAL_CERTS=true

echo "[compose_smoke] .env created from .env.example + smoke overrides applied"
echo "[compose_smoke] overriding compose.yaml image tags -> :${IMAGES_TAG}"
# Sed the image: tags from :latest to :smoke so CI's locally-built images
# are picked up. In-place + restored on exit.
cp compose.yaml compose.yaml.smoke-backup
trap 'mv compose.yaml.smoke-backup compose.yaml' EXIT
sed -i "s|image: rutster-edge:latest|image: rutster-edge:${IMAGES_TAG}|" compose.yaml
sed -i "s|image: rutster-engine:latest|image: rutster-engine:${IMAGES_TAG}|" compose.yaml
sed -i "s|image: rutster-brain:latest|image: rutster-brain:${IMAGES_TAG}|" compose.yaml

echo "[compose_smoke] docker compose up -d --wait"
docker compose up -d --wait --timeout 120

echo "[compose_smoke] docker compose ps"
docker compose ps

# Each service should report "running" state. (Healthy flag is set by the
# engine's /readyz via the Docker healthcheck — none of the shipped images
# define a HEALTHCHECK yet; slice-G or a future hardening slice can add one.
# For now State="running" + /healthz 200 + /readyz 200 is the gate.)
HEALTHY_COUNT=$(docker compose ps --format json | jq '[.[] | select(.State == "running")] | length')
EXPECTED=4
if [ "$HEALTHY_COUNT" -ne "$EXPECTED" ]; then
  echo "[compose_smoke] FAIL: expected $EXPECTED running services, got $HEALTHY_COUNT" >&2
  docker compose logs
  exit 1
fi
echo "[compose_smoke] $EXPECTED services running"

# Caddy /healthz via TLS (internal CA). Port 443 is published by the caddy service.
ROOT_CERT=$(docker compose exec -T caddy cat /data/caddy/pki/authorities/local/root.crt)
echo "$ROOT_CERT" > /tmp/rutster-smoke-root.pem

# Wait for /healthz to return 200 via Caddy TLS.
for _ in $(seq 1 60); do
  if curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/healthz \
        | grep -q '^ok$'; then
    echo "[compose_smoke] /healthz ok"
    break
  fi
  sleep 0.5
done
curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/healthz \
  || { echo "[compose_smoke] FAIL: /healthz never returned ok"; docker compose logs; exit 1; }

# Engine /readyz — fresh boot, MUST be 200 (drain happens on shutdown, not
# boot; cap only after RUTSTER_MAX_SESSIONS sessions exist).
curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/readyz \
  || { echo "[compose_smoke] FAIL: /readyz never returned ok"; docker compose logs; exit 1; }
echo "[compose_smoke] /readyz ok"

# Slice C: assert lifecycle event landed in the Valkey stream (valkey_sink.rs:45).
# ws_handshake.py drives one Twilio connected+start WS handshake against the
# engine's /twilio/media-stream path (through Caddy TLS). The engine's
# EventSink::on_event fires XADD MAXLEN ~ into the `rutster:events` stream;
# XLEN asserts >= 1 event landed.
echo "[compose_smoke] driving one WS handshake via ws_handshake.py"
python3 "$(dirname "$0")/ws_handshake.py" \
  --host localhost --port 443 --ca-pem-file /tmp/rutster-smoke-root.pem \
  || { echo "[compose_smoke] FAIL: ws_handshake.py exited non-zero"; docker compose logs; exit 1; }

# Give the EventSink's async XADD a beat to land. The XADD is fire-and-forget
# from the media thread (spec §5.6 — never block the hot path on the bus).
sleep 1
EVENTS=$(docker compose exec -T valkey valkey-cli XLEN rutster:events)
if [ -z "$EVENTS" ] || [ "$EVENTS" -lt 1 ]; then
  echo "[compose_smoke] FAIL: no lifecycle event in rutster:events (got '$EVENTS')" >&2
  docker compose logs engine
  docker compose exec -T valkey valkey-cli XRANGE rutster:events - + 2>&1 | head -40
  exit 1
fi
echo "[compose_smoke] rutster:events stream has $EVENTS event(s)"
echo "[compose_smoke] PASS: T2 compose boots + /healthz + /readyz + EventSink verified"

# Cleanup (compose down; volumes left for warm cache on re-run).
docker compose down --volumes --timeout 30 || true
exit 0
