#!/usr/bin/env bash
# Hostel Management add-on scenario suite.
#
# Exercises the add-on's authorization scope, allocation invariants, and the
# exeat lifecycle against a live tenant — in lieu of in-memory feature tests,
# because the schema-per-tenant setup needs a real Postgres connection (same
# reasoning as tests/smoke.sh).
#
# It logs in as a school-admin and a house-master teacher, provisions two
# throwaway QA hostels, runs positive + negative assertions, and tears the
# fixtures down. Safe to re-run; it cleans up after itself.
#
# Usage:
#   php artisan serve --port=8000 &
#   ./tests/hostel.sh
#
# Override via env: BASE, TENANT, PASSWORD, ADMIN_EMAIL, TEACHER_EMAIL
# (TEACHER_EMAIL must be a teacher who can be a house master).
#
# Exits non-zero on the first failed assertion.

set -u

BASE="${BASE:-http://127.0.0.1:8000/api/v1}"
TENANT="${TENANT:-blouza}"
PASSWORD="${PASSWORD:-password123}"
ADMIN_EMAIL="${ADMIN_EMAIL:-info@blouza.edu.ng}"
TEACHER_EMAIL="${TEACHER_EMAIL:-teacher@edusol.com}"

H_TEN="X-Tenant-Id: $TENANT"
H_ACC="Accept: application/json"
H_CT="Content-Type: application/json"
PASS=0

fail() { echo "FAIL: $1"; [ -s /tmp/hostel.body ] && cat /tmp/hostel.body; echo; cleanup; exit 1; }
ok()   { echo "ok   $1"; PASS=$((PASS+1)); }

jget() { python3 -c "import sys,json;d=json.load(open('/tmp/hostel.body'));print(eval(sys.argv[1]))" "$1" 2>/dev/null; }

login() {
    curl -s -o /tmp/hostel.body -X POST "$BASE/auth/login" -H "$H_TEN" -H "$H_CT" -H "$H_ACC" \
        -d "{\"email\":\"$1\",\"password\":\"$PASSWORD\"}"
    local t; t=$(jget "d['data']['token']")
    [ -n "$t" ] || fail "login $1 (check email/password/tenant)"
    echo "$t"
}

# code=$(req METHOD URL TOKEN [json]) — body lands in /tmp/hostel.body
req() {
    local method="$1" url="$2" token="$3" body="${4:-}"
    if [ -n "$body" ]; then
        curl -s -o /tmp/hostel.body -w "%{http_code}" -X "$method" "$BASE$url" \
            -H "Authorization: Bearer $token" -H "$H_TEN" -H "$H_CT" -H "$H_ACC" -d "$body"
    else
        curl -s -o /tmp/hostel.body -w "%{http_code}" -X "$method" "$BASE$url" \
            -H "Authorization: Bearer $token" -H "$H_TEN" -H "$H_ACC"
    fi
}

# expect EXPECTED_CODE label METHOD URL TOKEN [json]
expect() {
    local want="$1" label="$2"; shift 2
    local got; got=$(req "$@")
    [ "$got" = "$want" ] || fail "$label — expected $want, got $got"
    ok "$label ($got)"
}

# expect a 422 whose message contains a substring (precise negative tests)
expect_422_msg() {
    local label="$1" needle="$2"; shift 2
    local got; got=$(req "$@")
    [ "$got" = "422" ] || fail "$label — expected 422, got $got"
    local msg; msg=$(jget "d.get('message','')")
    case "$msg" in
        *"$needle"*) ok "$label (422: \"$msg\")" ;;
        *) fail "$label — 422 but message was \"$msg\" (wanted \"$needle\")" ;;
    esac
}

cleanup() {
    [ -n "${MIXED:-}" ] && [ -n "${ADMIN:-}" ] && req DELETE "/logistics/hostels/$MIXED" "$ADMIN" >/dev/null
    [ -n "${GIRLS:-}" ] && [ -n "${ADMIN:-}" ] && req DELETE "/logistics/hostels/$GIRLS" "$ADMIN" >/dev/null
}

echo "Hostel suite — tenant=$TENANT base=$BASE"
echo

ADMIN=$(login "$ADMIN_EMAIL")
TEACHER=$(login "$TEACHER_EMAIL")
# login() leaves the teacher's login response (which embeds the user) on disk.
TEACHER_UID=$(jget "d['data']['user']['id']")
[ -n "$TEACHER_UID" ] || fail "could not resolve teacher user id"
echo "admin + teacher (uid=$TEACHER_UID) authenticated"
echo

# --- Fixtures ---------------------------------------------------------------
req POST "/logistics/hostels" "$ADMIN" "{\"name\":\"QA Mixed Hall\",\"type\":\"mixed\",\"capacity\":50,\"house_master_user_id\":$TEACHER_UID,\"status\":\"active\"}" >/dev/null
MIXED=$(jget "d['data']['id']"); [ -n "$MIXED" ] || fail "create QA Mixed Hall"
req POST "/logistics/hostels" "$ADMIN" "{\"name\":\"QA Girls Hall\",\"type\":\"girls\",\"capacity\":50,\"status\":\"active\"}" >/dev/null
GIRLS=$(jget "d['data']['id']"); [ -n "$GIRLS" ] || fail "create QA Girls Hall"
echo "fixtures: MIXED=$MIXED (house master=teacher), GIRLS=$GIRLS (unassigned)"
echo

# Candidate students.
req GET "/students" "$ADMIN" >/dev/null
read -r -a MALE_BOARDERS <<< "$(python3 -c "import json;d=json.load(open('/tmp/hostel.body'))['data'];print(' '.join(str(s['student_id']) for s in d if s.get('residency')=='boarding' and s.get('gender')=='male'))")"
DAY_STUDENT=$(python3 -c "import json;d=json.load(open('/tmp/hostel.body'))['data'];print(next((str(s['student_id']) for s in d if s.get('residency')=='day'),''))")
[ "${#MALE_BOARDERS[@]}" -ge 2 ] || fail "need >=2 male boarders in $TENANT for the suite (found ${#MALE_BOARDERS[@]})"
[ -n "$DAY_STUDENT" ] || fail "need at least one day student in $TENANT"
echo "candidates: ${#MALE_BOARDERS[@]} male boarders, day student=$DAY_STUDENT"
echo

# --- 1. Add-on gate ---------------------------------------------------------
expect 200 "admin can list hostels (add-on active)" GET "/logistics/hostels" "$ADMIN"
req GET "/logistics/hostels" "$ADMIN" >/dev/null
HM_FIELD=$(jget "[h['id'] for h in d['data'] if 'house_master_user_id' in h][0:1]")
[ -n "$HM_FIELD" ] || fail "shape() must expose house_master_user_id"
ok "shape() exposes house_master_user_id"

# --- 2. House-master scope --------------------------------------------------
req GET "/logistics/hostels" "$TEACHER" >/dev/null
SEES_GIRLS=$(jget "any(h['id']==$GIRLS for h in d['data'])")
SEES_MIXED=$(jget "any(h['id']==$MIXED for h in d['data'])")
[ "$SEES_GIRLS" = "False" ] || fail "teacher must NOT see GIRLS hall in their list"
[ "$SEES_MIXED" = "True" ]  || fail "teacher MUST see their own MIXED hall"
ok "teacher list scoped to own hostel"

expect 403 "teacher cannot edit a hostel they don't manage"      PUT  "/logistics/hostels/$GIRLS" "$TEACHER" '{"name":"HACKED"}'
expect 403 "teacher cannot add a room to a hostel they don't manage" POST "/logistics/hostels/$GIRLS/rooms" "$TEACHER" '{"room_number":"X1"}'
expect 200 "teacher CAN edit their own hostel"                    PUT  "/logistics/hostels/$MIXED" "$TEACHER" '{"description":"managed by house master"}'
expect 201 "teacher CAN add a room to their own hostel"           POST "/logistics/hostels/$MIXED/rooms" "$TEACHER" '{"room_number":"QA-1","capacity":4}'
expect 403 "teacher cannot allocate into a hostel they don't manage" \
    POST "/logistics/hostel/allocate" "$TEACHER" "{\"student_id\":${MALE_BOARDERS[0]},\"hostel_id\":$GIRLS}"

# --- 3. Gender enforcement --------------------------------------------------
expect_422_msg "male boarder rejected from a girls hall" "Gender" \
    POST "/logistics/hostel/allocate" "$ADMIN" "{\"student_id\":${MALE_BOARDERS[0]},\"hostel_id\":$GIRLS}"

# --- 4. Day-student exclusion ----------------------------------------------
expect_422_msg "day student cannot be allocated" "day student" \
    POST "/logistics/hostel/allocate" "$ADMIN" "{\"student_id\":$DAY_STUDENT,\"hostel_id\":$MIXED}"

# --- 5. Allocate → duplicate → invariant → vacate ---------------------------
# Find a boarder that allocates cleanly (others may already hold a bed).
SUBJECT=""; ALLOC=""
for sid in "${MALE_BOARDERS[@]}"; do
    code=$(req POST "/logistics/hostel/allocate" "$ADMIN" "{\"student_id\":$sid,\"hostel_id\":$MIXED}")
    if [ "$code" = "201" ]; then SUBJECT=$sid; ALLOC=$(jget "d['data']['id']"); break; fi
done
[ -n "$SUBJECT" ] || fail "no allocatable boarder found"
ok "allocate boarder $SUBJECT → MIXED (alloc $ALLOC)"

expect 422 "duplicate active allocation blocked" \
    POST "/logistics/hostel/allocate" "$ADMIN" "{\"student_id\":$SUBJECT,\"hostel_id\":$MIXED}"
expect_422_msg "cannot mark an allocated boarder as a day student" "Vacate" \
    PUT "/students/$SUBJECT" "$ADMIN" '{"residency":"day"}'
expect 200 "vacate the allocation" POST "/logistics/hostel/vacate/$ALLOC" "$ADMIN"

# --- 6. Exeat lifecycle -----------------------------------------------------
req POST "/logistics/hostel/allocate" "$ADMIN" "{\"student_id\":$SUBJECT,\"hostel_id\":$MIXED}" >/dev/null
ALLOC2=$(jget "d['data']['id']"); [ -n "$ALLOC2" ] || fail "re-allocate for exeat test"
req POST "/logistics/exeat" "$ADMIN" "{\"student_id\":$SUBJECT,\"reason\":\"QA weekend\",\"depart_at\":\"2026-07-01T08:00:00\",\"return_due_at\":\"2026-07-03T18:00:00\"}" >/dev/null
EXEAT=$(jget "d['data']['ulid']"); [ -n "$EXEAT" ] || fail "create exeat"
ok "exeat created ($EXEAT)"
expect 422 "cannot check-in before approval/check-out" POST "/logistics/exeat/$EXEAT/check-in" "$ADMIN"
expect 200 "approve exeat"   POST "/logistics/exeat/$EXEAT/approve"   "$ADMIN"
expect 422 "cannot approve twice" POST "/logistics/exeat/$EXEAT/approve" "$ADMIN"
expect 200 "check out"       POST "/logistics/exeat/$EXEAT/check-out" "$ADMIN"
expect 200 "check in"        POST "/logistics/exeat/$EXEAT/check-in"  "$ADMIN"
req POST "/logistics/hostel/vacate/$ALLOC2" "$ADMIN" >/dev/null

# --- Teardown ---------------------------------------------------------------
expect 200 "delete QA Mixed Hall" DELETE "/logistics/hostels/$MIXED" "$ADMIN"
expect 200 "delete QA Girls Hall" DELETE "/logistics/hostels/$GIRLS" "$ADMIN"
MIXED=""; GIRLS=""

echo
echo "All $PASS hostel assertions green."
