#!/usr/bin/env bash
# lib/http.sh — curl wrappers that capture latency, status, body.
#
# Each request emits a JSON file under raw/http/<case_id>/<probe>.json
# describing the round-trip. Cases consume the JSON via assert.sh.
#
# Why JSON files instead of bash variables: gives the final report a
# diffable, replayable record. Future runs can compare on disk without
# re-executing the case.
#
# Functions:
#   proof_get   <case_id> <probe_name> <url> [extra-curl-args...]
#   proof_post  <case_id> <probe_name> <url> <content-type> <body> [extra-curl-args...]
#   proof_put   <case_id> <probe_name> <url> <content-type> <body|@file> [extra-curl-args...]
#   proof_delete<case_id> <probe_name> <url> [extra-curl-args...]
#
# Returns 0 always (capture is independent of HTTP outcome).
# Stores result at:  $PROOF_REPORT_DIR/raw/http/<case_id>/<probe>.json
# Stores body at:    $PROOF_REPORT_DIR/raw/http/<case_id>/<probe>.body

_proof_http_emit() {
    local case_id="$1" probe="$2" method="$3" url="$4" status="$5" latency_ms="$6" body_path="$7" headers_path="$8"
    local dir="${PROOF_REPORT_DIR}/raw/http/${case_id}"
    mkdir -p "$dir"
    local body_sha=""
    [ -s "$body_path" ] && body_sha="$(sha256sum "$body_path" | awk '{print $1}')"
    cat > "${dir}/${probe}.json" <<JSON
{
  "case_id": "${case_id}",
  "probe": "${probe}",
  "method": "${method}",
  "url": "${url}",
  "status": ${status},
  "latency_ms": ${latency_ms},
  "body_path": "raw/http/${case_id}/${probe}.body",
  "body_sha256": "${body_sha}",
  "headers_path": "raw/http/${case_id}/${probe}.headers"
}
JSON
}

# Internal common runner — populates a temp body+headers file, times
# the request, emits the per-probe JSON, prints the body to stdout for
# convenience (cases can capture or discard).
_proof_http_run() {
    local case_id="$1" probe="$2" method="$3" url="$4"; shift 4
    local dir="${PROOF_REPORT_DIR}/raw/http/${case_id}"
    mkdir -p "$dir"
    local body_path="${dir}/${probe}.body"
    local headers_path="${dir}/${probe}.headers"
    local start_ms end_ms
    start_ms=$(date +%s%3N)
    local status
    status=$(curl -sS -X "$method" -o "$body_path" -D "$headers_path" -w "%{http_code}" "$@" "$url" 2>/dev/null || echo 0)
    end_ms=$(date +%s%3N)
    local latency_ms=$((end_ms - start_ms))
    _proof_http_emit "$case_id" "$probe" "$method" "$url" "$status" "$latency_ms" "$body_path" "$headers_path"
    cat "$body_path"
}

proof_get() {
    local case_id="$1" probe="$2" url="$3"; shift 3
    _proof_http_run "$case_id" "$probe" GET "$url" "$@"
}

proof_post() {
    local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5
    _proof_http_run "$case_id" "$probe" POST "$url" \
        -H "Content-Type: ${content_type}" \
        --data "$body" \
        "$@"
}

# proof_put accepts either an inline body or @-prefixed file path
# (curl --upload-file semantics for streaming).
proof_put() {
    local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5
    if [[ "$body" == @* ]]; then
        local file="${body#@}"
        _proof_http_run "$case_id" "$probe" PUT "$url" \
            -H "Content-Type: ${content_type}" \
            --upload-file "$file" \
            "$@"
    else
        _proof_http_run "$case_id" "$probe" PUT "$url" \
            -H "Content-Type: ${content_type}" \
            --data "$body" \
            "$@"
    fi
}

proof_delete() {
    local case_id="$1" probe="$2" url="$3"; shift 3
    _proof_http_run "$case_id" "$probe" DELETE "$url" "$@"
}

# proof_call: escape hatch for cases that need full control of curl
# args — multipart uploads (-F), custom headers, --form-string, etc.
# proof_post / proof_put add a Content-Type header and --data body
# that conflict with -F multipart, so use this for those cases.
#
#   proof_call <case_id> <probe> <method> <url> [curl-args...]
#
# Example multipart POST:
#   proof_call "$CASE_ID" "ingest" POST "$URL" -F "file=@${PATH}"
proof_call() {
    local case_id="$1" probe="$2" method="$3" url="$4"; shift 4
    _proof_http_run "$case_id" "$probe" "$method" "$url" "$@"
}

# proof_wait_for_sql: wait for a SQL probe to return 200, up to budget
# seconds. Use when a case follows an ingest and queryd's view-refresh
# (default 500ms tick) may not have fired yet. NOT a retry — a wait
# for a known eventual-consistency event. No evidence emitted (this
# is test setup, not a claim).
#
#   proof_wait_for_sql <budget_sec> <sql>
#
# Returns 0 if the probe succeeded; 1 on timeout.
proof_wait_for_sql() {
    local budget="${1:-10}" sql="$2"
    local deadline=$(($(date +%s) + budget))
    local body
    body=$(jq -nc --arg s "$sql" '{sql:$s}')
    while [ "$(date +%s)" -lt "$deadline" ]; do
        if curl -sf --max-time 1 -X POST \
                -H 'Content-Type: application/json' \
                -d "$body" \
                "${PROOF_GATEWAY_URL}/v1/sql" >/dev/null 2>&1; then
            return 0
        fi
        sleep 0.1
    done
    return 1
}

# Helper accessors — reads the per-probe JSON.
proof_status_of() {
    local case_id="$1" probe="$2"
    jq -r '.status' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json"
}

proof_body_of() {
    local case_id="$1" probe="$2"
    cat "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.body"
}

proof_latency_of() {
    local case_id="$1" probe="$2"
    jq -r '.latency_ms' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json"
}
