#!/usr/bin/env bash
#
# Contract test: SOSL search response shape and projection.
#
# Exercises the Salesforce REST API contract for /search (SOSL)
# as implemented in backend/internal/interfaces/rest/search_sosl.go.
#
# Dimensions:
#   1. SOSL response envelope — HTTP 200, searchRecords (array).
#   2. Scoped search (IN NAME FIELDS / IN ALL FIELDS).
#   3. RETURNING clause with field projection.
#   4. Missing q parameter → HTTP 400.
#   5. Malformed SOSL → HTTP 400 with SF error code.
#
# Usage (from repo root):
#   ./tests/contract/rest_api/verify_search_strict.sh

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "${SCRIPT_DIR}/fixtures.sh"

OUT_DIR="${OUT_DIR:-/tmp/sfmeta/run/contract}"
mkdir -p "$OUT_DIR"

fails=0

echo "═══ REST contract: SOSL search ═══" >&2

# ---------- Pre-flight: login ----------
if ! login_admin; then
  echo "  SKIP: cannot authenticate; backend may not be running at ${BASE_URL}" >&2
  exit 2
fi

# ---------- Dim 1: search response envelope ----------
test_search_envelope() {
  echo "--- dim 1: search response envelope ---" >&2

  local body status resp
  resp=$(authenticated_get "/services/data/${API_VER}/search?q=FIND+%7Badmin%7D+IN+ALL+FIELDS+RETURNING+User")
  status=$(echo "$resp" | tail -1)
  body=$(echo "$resp" | sed '$d')

  if ! assert_eq "HTTP status" "200" "$status"; then return 1; fi
  if ! assert_has_key "envelope" "$body" "searchRecords"; then return 1; fi

  local rec_type
  rec_type=$(echo "$body" | jq -r '.searchRecords | type')
  assert_eq "searchRecords type" "array" "$rec_type" || return 1

  echo "  ✅ envelope OK" >&2
}

# ---------- Dim 2: scoped search (IN NAME FIELDS) ----------
test_search_scoped() {
  echo "--- dim 2: scoped search (IN NAME FIELDS) ---" >&2

  local body status resp
  resp=$(authenticated_get "/services/data/${API_VER}/search?q=FIND+%7Badmin%7D+IN+NAME+FIELDS+RETURNING+User")
  status=$(echo "$resp" | tail -1)
  body=$(echo "$resp" | sed '$d')

  if ! assert_eq "HTTP status" "200" "$status"; then return 1; fi
  if ! assert_has_key "scoped envelope" "$body" "searchRecords"; then return 1; fi

  echo "  ✅ scoped search OK" >&2
}

# ---------- Dim 3: RETURNING clause with field projection ----------
test_search_projection() {
  echo "--- dim 3: RETURNING with field projection ---" >&2

  # Query a real User Email to use as SOSL search term — Email is a
  # reliable SOQL-projectable field on User (unlike Name, which is a
  # virtual compound field not exposed by this backend's SOQL layer).
  local user_resp user_body user_email
  user_resp=$(authenticated_get "/services/data/${API_VER}/query?q=SELECT+Email+FROM+User+LIMIT+1")
  user_body=$(echo "$user_resp" | sed '$d')
  user_email=$(echo "$user_body" | jq -r '.records[0].Email // empty')
  if [[ -z "$user_email" ]]; then
    echo "  FAIL: no User records to search; fixture must contain at least one User" >&2
    return 1
  fi

  # Build SOSL query URL manually — encode all chars that affect URL parsing.
  # Use Email (not Name) in RETURNING: User.Name is not a SOQL-projectable
  # field in this backend, and requesting it in RETURNING suppresses results.
  local sosl_query="FIND {${user_email}} IN ALL FIELDS RETURNING User(Id,Email)"
  sosl_query="${sosl_query//\{/%7B}"
  sosl_query="${sosl_query//\}/%7D}"
  sosl_query="${sosl_query//@/%40}"
  sosl_query="${sosl_query//\(/%28}"
  sosl_query="${sosl_query//\)/%29}"
  sosl_query="${sosl_query// /+}"

  local body status resp
  resp=$(authenticated_get "/services/data/${API_VER}/search?q=${sosl_query}")
  status=$(echo "$resp" | tail -1)
  body=$(echo "$resp" | sed '$d')

  if ! assert_eq "HTTP status" "200" "$status"; then return 1; fi

  local rec_count
  rec_count=$(echo "$body" | jq -r '.searchRecords | length')
  if [[ "$rec_count" -eq 0 ]]; then
    echo "  FAIL: SOSL returned 0 records for '${user_email}'; index gap or SOSL broken" >&2
    return 1
  fi

  # v63 REST Search records MUST include the 'attributes' envelope
  # (attributes.type and attributes.url). This is a hard contract, not
  # a convention.
  local has_attrs attr_type attr_url
  has_attrs=$(echo "$body" | jq -r '.searchRecords[0] | has("attributes")')
  if [[ "$has_attrs" != "true" ]]; then
    echo "  FAIL: search record missing 'attributes' envelope (v63 contract violation)" >&2
    return 1
  fi

  attr_type=$(echo "$body" | jq -r '.searchRecords[0].attributes.type // empty')
  attr_url=$(echo "$body" | jq -r '.searchRecords[0].attributes.url // empty')
  if [[ -z "$attr_type" ]]; then
    echo "  FAIL: search record attributes.type is empty (v63 contract violation)" >&2
    return 1
  fi
  if [[ -z "$attr_url" ]]; then
    echo "  FAIL: search record attributes.url is empty (v63 contract violation)" >&2
    return 1
  fi

  echo "  ✅ projection OK (${rec_count} records, attributes.type=${attr_type})" >&2
}

# ---------- Dim 4: missing q parameter → 400 ----------
test_missing_q_param() {
  echo "--- dim 4: missing q parameter → 400 ---" >&2

  local body status resp
  resp=$(authenticated_get "/services/data/${API_VER}/search")
  status=$(echo "$resp" | tail -1)
  body=$(echo "$resp" | sed '$d')

  if ! assert_eq "missing q status" "400" "$status"; then return 1; fi

  echo "  ✅ missing q → 400 OK" >&2
}

# ---------- Dim 5: malformed SOSL → 400 with SF error code ----------
test_malformed_sosl() {
  echo "--- dim 5: malformed SOSL → 400 ---" >&2

  local body status resp
  # Query without FIND keyword — not valid SOSL
  resp=$(authenticated_get "/services/data/${API_VER}/search?q=SELECT+Id+FROM+User")
  status=$(echo "$resp" | tail -1)
  body=$(echo "$resp" | sed '$d')

  if ! assert_eq "malformed SOSL status" "400" "$status"; then return 1; fi

  # Body should be an SF error array with errorCode
  local err_code
  err_code=$(echo "$body" | jq -r '.[0].errorCode // empty' 2>/dev/null)
  if [[ -z "$err_code" ]]; then
    echo "  FAIL malformed SOSL: no SF errorCode in body" >&2
    return 1
  fi

  echo "  ✅ malformed SOSL → 400 (${err_code}) OK" >&2
}

# ---------- Run ----------
test_search_envelope    || fails=$((fails+1))
test_search_scoped      || fails=$((fails+1))
test_search_projection  || fails=$((fails+1))
test_missing_q_param    || fails=$((fails+1))
test_malformed_sosl     || fails=$((fails+1))

echo "" >&2
if [[ $fails -eq 0 ]]; then
  echo "════════ SOSL search: ALL PASS (5/5 dimensions) ════════" >&2
else
  echo "════════ SOSL search: ${fails} FAIL(S) ════════" >&2
fi
exit "$fails"
