#!/usr/bin/env bash
# HTTP smoke test for the ezbeq server.
# By default starts its own stub server on a temporary port, runs checks, then shuts it down.
#
# Usage:
#   bin/smoke-test                # start stub on port 18080, run checks, stop
#   bin/smoke-test --port 9999    # same, but on a different port
#   bin/smoke-test --no-start     # check a server already running on --port (default 8080)
set -euo pipefail
cd "$(dirname "$0")/.."

START_SERVER=true
PORT=18080
LOG_FILE=""
SERVER_PID=""
PASS=0
FAIL=0

while [[ $# -gt 0 ]]; do
    case "$1" in
        --no-start) START_SERVER=false; PORT=8080 ;;
        --port)     PORT="$2"; shift ;;
        *)          echo "Unknown argument: $1"; exit 1 ;;
    esac
    shift
done

BASE="http://localhost:$PORT"

# ── Colour helpers ────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; RESET='\033[0m'; BOLD='\033[1m'
ok()   { echo -e "  ${GREEN}✓${RESET} $*"; (( PASS++ )) || true; }
fail() { echo -e "  ${RED}✗${RESET} $*"; (( FAIL++ )) || true; }
info() { echo -e "  ${YELLOW}→${RESET} $*"; }

# ── Port helper ───────────────────────────────────────────────────────────────
port_in_use() { lsof -ti :"$1" >/dev/null 2>&1; }

kill_port() {
    local pids; pids=$(lsof -ti :"$1" 2>/dev/null || true)
    if [[ -n "$pids" ]]; then
        echo -e "${YELLOW}  Port $1 already in use (pid $pids) — killing...${RESET}"
        kill $pids 2>/dev/null || true
        sleep 0.5
    fi
}

cleanup() {
    if [[ -n "$SERVER_PID" ]]; then
        kill "$SERVER_PID" 2>/dev/null || true
        wait "$SERVER_PID" 2>/dev/null || true
    fi
}
trap cleanup EXIT

# ── Start server (unless --no-start) ─────────────────────────────────────────
if $START_SERVER; then
    kill_port "$PORT"
    STUB_CONFIG_DIR="${TMPDIR:-/tmp}/ezbeq-smoke-test"
    LOG_FILE="$STUB_CONFIG_DIR/server.log"
    mkdir -p "$STUB_CONFIG_DIR"
    cat > "$STUB_CONFIG_DIR/ezbeq.yml" << EOF
port: $PORT
accessLogging: false
debugLogging: true
devices:
  master:
    type: minidsp
    exe: stub
EOF
    echo -e "${BOLD}Starting stub server on port $PORT...${RESET}"
    # EZBEQ_ACCESS_LOG_STDOUT=1 makes each HTTP request appear in the server log
    # (stdout/stderr) in addition to any access.log file.
    EZBEQ_CONFIG_HOME="$STUB_CONFIG_DIR" EZBEQ_ACCESS_LOG_STDOUT=1 \
        uv run ezbeq >"$LOG_FILE" 2>&1 &
    SERVER_PID=$!

    echo -n "  Waiting for server to be ready"
    READY=false
    for i in $(seq 1 40); do
        if curl -sf "$BASE/api/1/devices" >/dev/null 2>&1; then
            READY=true; break
        fi
        echo -n "."; sleep 0.3
    done
    echo

    if ! $READY; then
        echo -e "${RED}Server did not start in time. Log:${RESET}"
        cat "$LOG_FILE"
        exit 1
    fi
    echo -e "  ${GREEN}Server ready${RESET} (pid $SERVER_PID, log: $LOG_FILE)\n"
else
    if ! port_in_use "$PORT"; then
        echo -e "${RED}No server found on port $PORT.${RESET}"
        echo -e "Start one with ${BOLD}bin/run-server-stub${RESET}, or drop --no-start to have this script do it."
        exit 1
    fi
fi

# ── HTTP check helper (GET) ───────────────────────────────────────────────────
check() {
    local label="$1" url="$2" expect_status="$3" expect_not="${4:-}" expect_contains="${5:-}"

    echo -e "${CYAN}── $label${RESET}"
    info "GET $url"

    local tmp; tmp=$(mktemp)
    local http_status
    http_status=$(curl -s -o "$tmp" -w "%{http_code}" "$url" 2>/dev/null) || true
    local body; body=$(cat "$tmp"); rm -f "$tmp"

    if [[ "$http_status" == "000" ]]; then
        fail "HTTP $http_status — connection refused (is the server running on port $PORT?)"
    elif [[ "$http_status" == "$expect_status" ]]; then
        ok "HTTP $http_status"
    else
        fail "HTTP $http_status (expected $expect_status)"
    fi

    if [[ -n "$expect_not" ]]; then
        if echo "$body" | grep -q "$expect_not"; then
            fail "Response body contains '$expect_not'"
        else
            ok "Response body does not contain '$expect_not'"
        fi
    fi

    if [[ -n "$expect_contains" ]]; then
        if echo "$body" | grep -q "$expect_contains"; then
            ok "Response body contains '$expect_contains'"
        else
            fail "Response body missing '$expect_contains'"
        fi
    fi

    # Pretty-print JSON, or show a trimmed preview for non-JSON responses
    if echo "$body" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
        info "Body (JSON):"
        local pretty; pretty=$(echo "$body" | python3 -m json.tool 2>/dev/null)
        local lines; lines=$(echo "$pretty" | wc -l)
        echo "$pretty" | head -30 | sed 's/^/    /'
        if [[ $lines -gt 30 ]]; then
            echo "    ... ($((lines - 30)) more lines)"
        fi
    else
        local preview; preview=$(echo "$body" | tr -d '\n' | cut -c1-200)
        info "Body: $preview"
    fi
    echo
}

# ── Non-GET HTTP check helper ─────────────────────────────────────────────────
request_check() {
    local label="$1" method="$2" url="$3" expect_status="$4" data="${5:-}"

    echo -e "${CYAN}── $label${RESET}"
    info "$method $url"

    local tmp; tmp=$(mktemp)
    local http_status
    local curl_args=(-s -X "$method" -o "$tmp" -w "%{http_code}")
    if [[ -n "$data" ]]; then
        curl_args+=(-H "Content-Type: application/json" -d "$data")
    fi
    http_status=$(curl "${curl_args[@]}" "$url" 2>/dev/null) || true
    local body; body=$(cat "$tmp"); rm -f "$tmp"

    if [[ "$http_status" == "000" ]]; then
        fail "HTTP $http_status — connection refused (is the server running on port $PORT?)"
    elif [[ "$http_status" == "$expect_status" ]]; then
        ok "HTTP $http_status"
    else
        fail "HTTP $http_status (expected $expect_status)"
    fi

    if echo "$body" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
        info "Body (JSON):"
        # buffer the pretty-printed JSON into a variable first, then pipe *that*
        # through head - piping python3 -m json.tool straight into head risks
        # SIGPIPE (and, under set -o pipefail, killing the whole script) if head
        # closes the pipe before python finishes writing a large body.
        local pretty; pretty=$(echo "$body" | python3 -m json.tool 2>/dev/null)
        echo "$pretty" | head -20 | sed 's/^/    /'
    fi
    echo
}

# ── Log check helper (only meaningful in self-start mode) ────────────────────
log_check() {
    local label="$1" pattern="$2"

    echo -e "${CYAN}── $label${RESET}"
    if [[ -z "$LOG_FILE" ]]; then
        info "Skipped (no log file — only in self-start mode)"
        echo; return
    fi
    if grep -q "$pattern" "$LOG_FILE"; then
        ok "Log contains: $pattern"
        # Show the matching line(s) for confirmation
        grep "$pattern" "$LOG_FILE" | head -3 | sed 's/^/    /'
    else
        fail "Log missing: $pattern"
        info "Last 15 log lines:"
        tail -15 "$LOG_FILE" | sed 's/^/    /'
    fi
    echo
}

# ── HTTP checks ───────────────────────────────────────────────────────────────
echo -e "${BOLD}Smoke-testing $BASE${RESET}\n"

check "Root / (expect 200 with React app)" \
    "$BASE/"              "200" "Processing Failed"

check "GET /api/1/devices — expect device state" \
    "$BASE/api/1/devices" "200" "Processing Failed" '"masterVolume"'

check "GET /api/1/version — expect version field" \
    "$BASE/api/1/version" "200" "Processing Failed" '"version"'

check "GET /static/foo.js (not built — expect 404, not a crash)" \
    "$BASE/static/foo.js" "404" "Processing Failed"

# Whether an unknown path 200s (React SPA serves index.html) or 404s depends on
# whether `ezbeq/ui` has actually been built (see main.py's uiRoot check) - true
# after `npm run build`, false on a clean backend-only checkout like CI's.
if [[ -f "ezbeq/ui/index.html" ]]; then
    UI_BUILT_STATUS="200"
else
    UI_BUILT_STATUS="404"
fi
check "GET /somepage (unknown path — expect $UI_BUILT_STATUS, UI built: $([[ $UI_BUILT_STATUS == 200 ]] && echo yes || echo no))" \
    "$BASE/somepage"      "$UI_BUILT_STATUS" "Processing Failed"

check "GET /ws (WebSocket endpoint — expect Autobahn info page)" \
    "$BASE/ws"            "200" "Processing Failed"

# Clear a filter slot — this drives a full round-trip through the minidsp layer
# (stub mode) so we can verify the debug logging output below.
request_check "DELETE /api/1/devices/master/filter/1 — clear slot (exercises minidsp stub)" \
    DELETE "$BASE/api/1/devices/master/filter/1" "200"

# ── Log checks ────────────────────────────────────────────────────────────────
echo -e "${BOLD}Log checks${RESET}\n"

log_check "Access log echoed to stdout (EZBEQ_ACCESS_LOG_STDOUT)" \
    "ezbeq.access"

log_check "minidsp stub runner invoked (debugLogging=true)" \
    "MinidspStubRunner"

log_check "Command count and timing logged" \
    "commands to slot"

# ── Summary ───────────────────────────────────────────────────────────────────
if [[ $FAIL -gt 0 ]]; then
    echo -e "${BOLD}Results: ${GREEN}$PASS passed${RESET}, ${RED}$FAIL failed${RESET}"
else
    echo -e "${BOLD}Results: ${GREEN}$PASS passed, $FAIL failed${RESET}"
fi

# ── Server log (always shown so failures are diagnosable) ─────────────────────
if [[ -n "$LOG_FILE" ]]; then
    echo
    echo -e "${BOLD}═══ Server log ($LOG_FILE) ═══${RESET}"
    cat "$LOG_FILE"
fi

[[ $FAIL -eq 0 ]]
