#!/usr/bin/env bash
# Runs Phase 3 verification checks from docs/plans/phase-3-plan.md.
#
# Covers verification, rejection, separation of duties, clinical approval routing,
# work queues, and digitization event trails.
#
# Prerequisites:
#   docker compose up -d          (PostgreSQL + Redis; psql via docker compose exec)
#   dotnet run --project VigilCareRecordsAPI
#   Phase 1 seed data (entry1, entry2, verifier1, verifier2, intake1 users)
#   Phase 2 draft entry API (submit-for-verification, draft CRUD)
#
# PostgreSQL checks use docker compose exec when the postgres service is running,
# otherwise host psql against VIGILCARE_PG_HOST:VIGILCARE_PG_PORT.
# Environment overrides (same defaults as Phase 1/2 scripts):
#   VIGILCARE_API_URL          default: http://localhost:5217
#   VIGILCARE_COMPOSE_FILE     default: <repo>/docker-compose.yml
#   VIGILCARE_PG_HOST          default: localhost
#   VIGILCARE_PG_PORT          default: 5437
#   VIGILCARE_PG_DB            default: vigilcare_records
#   VIGILCARE_PG_USER          default: postgres
#   VIGILCARE_PG_PASSWORD      default: password
#   VIGILCARE_SKIP_DB_CHECKS   set to 1 to skip PostgreSQL assertions
#   VIGILCARE_RECORDED_AT      default: 2025-01-01T10:00:00Z

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FIXTURE_PDF="$SCRIPT_DIR/fixtures/test-scan.pdf"

API_URL="${VIGILCARE_API_URL:-http://localhost:5217}"
COMPOSE_FILE="${VIGILCARE_COMPOSE_FILE:-$REPO_ROOT/docker-compose.yml}"
COMPOSE=(docker compose -f "$COMPOSE_FILE")
PG_HOST="${VIGILCARE_PG_HOST:-localhost}"
PG_PORT="${VIGILCARE_PG_PORT:-5437}"
PG_DB="${VIGILCARE_PG_DB:-vigilcare_records}"
PG_USER="${VIGILCARE_PG_USER:-postgres}"
PG_PASSWORD="${VIGILCARE_PG_PASSWORD:-password}"
SKIP_DB_CHECKS="${VIGILCARE_SKIP_DB_CHECKS:-0}"

RECORDED_AT="${VIGILCARE_RECORDED_AT:-2025-01-01T10:00:00Z}"

PASS_COUNT=0
FAIL_COUNT=0
FAILED_TESTS=()

log() {
  printf '%s\n' "$*"
}

section() {
  log ""
  log "== $1 =="
}

pass() {
  PASS_COUNT=$((PASS_COUNT + 1))
  log "  PASS: $1"
}

fail() {
  FAIL_COUNT=$((FAIL_COUNT + 1))
  FAILED_TESTS+=("$1")
  log "  FAIL: $1"
}

require_cmd() {
  local cmd="$1"
  if ! command -v "$cmd" >/dev/null 2>&1; then
    log "ERROR: required command not found: $cmd"
    exit 1
  fi
}

compose_service_running() {
  local service="$1"
  "${COMPOSE[@]}" ps --status running --services 2>/dev/null | grep -qx "$service"
}

psql_available() {
  [[ "$SKIP_DB_CHECKS" == "1" ]] && return 1
  compose_service_running postgres && return 0
  command -v psql >/dev/null 2>&1 && return 0
  return 1
}

psql_query() {
  if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
    return 1
  fi
  # Prefer docker compose postgres — matches local dev setup and avoids host psql gaps.
  if compose_service_running postgres; then
    "${COMPOSE[@]}" exec -T postgres \
      psql -U "$PG_USER" -d "$PG_DB" -Atqc "$1"
  elif command -v psql >/dev/null 2>&1; then
    PGPASSWORD="$PG_PASSWORD" psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -Atqc "$1"
  else
    return 1
  fi
}

http_code() {
  curl -sS -o /dev/null -w '%{http_code}' "$@"
}

json_post() {
  local url="$1"
  local body="$2"
  local token="${3:-}"
  if [[ -n "$token" ]]; then
    curl -sS -X POST "$url" \
      -H "Authorization: Bearer $token" \
      -H 'Content-Type: application/json' \
      -d "$body"
  else
    curl -sS -X POST "$url" \
      -H 'Content-Type: application/json' \
      -d "$body"
  fi
}

json_put() {
  local url="$1"
  local body="$2"
  local token="$3"
  curl -sS -X PUT "$url" \
    -H "Authorization: Bearer $token" \
    -H 'Content-Type: application/json' \
    -d "$body"
}

login() {
  local username="$1"
  local password="${2:-password}"
  json_post "$API_URL/api/v1/auth/login" \
    "{\"username\":\"$username\",\"password\":\"$password\"}"
}

extract_data_field() {
  local json="$1"
  local field="$2"
  jq -er ".data.$field // empty" <<<"$json"
}

extract_error_code() {
  local json="$1"
  jq -er '.error.code // empty' <<<"$json"
}

upload_batch() {
  local token="$1"
  local batch_type="${2:-VITALS_SHEET}"
  local file_path="${3:-$FIXTURE_PDF}"

  curl -sS -X POST "$API_URL/api/v1/digitization-batches" \
    -H "Authorization: Bearer $token" \
    -F "file=@${file_path};type=application/pdf" \
    -F "batchType=$batch_type"
}

assign_batch() {
  local token="$1"
  local batch_id="$2"
  local entry_clerk_id="$3"

  curl -sS -X PATCH "$API_URL/api/v1/digitization-batches/$batch_id/assign" \
    -H "Authorization: Bearer $token" \
    -H 'Content-Type: application/json' \
    -d "{\"entryClerkUserId\":\"$entry_clerk_id\"}"
}

verify_batch() {
  local token="$1"
  local batch_id="$2"
  local body="$3"

  json_post "$API_URL/api/v1/digitization-batches/$batch_id/verify" "$body" "$token"
}

reject_batch() {
  local token="$1"
  local batch_id="$2"
  local reason="$3"

  json_post "$API_URL/api/v1/digitization-batches/$batch_id/reject" \
    "{\"reason\":\"$reason\"}" "$token"
}

assert_api_reachable() {
  local code
  code="$(http_code "$API_URL/swagger/index.html" || true)"
  if [[ "$code" != "200" ]]; then
    log "ERROR: API not reachable at $API_URL (HTTP $code)."
    log "Start infrastructure with: docker compose up -d"
    log "Start API with: dotnet run --project VigilCareRecordsAPI"
    exit 1
  fi
}

# Returns 0 if batch_id appears anywhere in the paginated work queue.
queue_contains_batch() {
  local token="$1"
  local queue_name="$2"
  local batch_id="$3"
  local page=1 page_size=100 total_pages=1

  while (( page <= total_pages )); do
    local json found
    json="$(curl -sS "$API_URL/api/v1/work-queue/$queue_name?page=$page&pageSize=$page_size" \
      -H "Authorization: Bearer $token")"
    found="$(jq -er --arg id "$batch_id" \
      'if (.data.items | map(.batchId) | index($id)) != null then "true" else "false" end' \
      <<<"$json")"
    if [[ "$found" == "true" ]]; then
      return 0
    fi
    total_pages="$(jq -er '.data.totalPages // 1' <<<"$json")"
    page=$((page + 1))
  done

  return 1
}

# Upload a batch, assign to entry1. Prints batch id to stdout.
create_assigned_batch() {
  local intake_token="$1"
  local batch_type="${2:-VITALS_SHEET}"
  local upload_json batch_id entry_json entry_id

  upload_json="$(upload_batch "$intake_token" "$batch_type")"
  if [[ "$(jq -er '.success' <<<"$upload_json")" != "true" ]]; then
    log "ERROR: failed to upload $batch_type batch for test setup"
    return 1
  fi

  batch_id="$(extract_data_field "$upload_json" id)"
  entry_json="$(login entry1)"
  entry_id="$(extract_data_field "$entry_json" userId)"
  assign_batch "$intake_token" "$batch_id" "$entry_id" >/dev/null

  printf '%s' "$batch_id"
}

# Drive a VITALS_SHEET batch through data entry and submit to PENDING_VERIFICATION.
# Prints batch id to stdout.
create_pending_vitals_batch() {
  local intake_token="$1"
  local entry_token batch_id

  batch_id="$(create_assigned_batch "$intake_token" "VITALS_SHEET")" || return 1
  entry_token="$(extract_data_field "$(login entry1)" token)"

  json_put \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/patient" \
    '{"fullName":"Chen Wei-Lin","dateOfBirth":"1985-03-15","sex":"M","noKnownAllergies":true,"noActiveMedications":true}' \
    "$entry_token" >/dev/null

  json_put \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/encounter" \
    '{"admissionDate":"2025-01-01T08:00:00Z","department":"ICU","admissionReason":"Chest pain"}' \
    "$entry_token" >/dev/null

  json_post \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/observations" \
    "{\"observationCode\":\"HEART_RATE\",\"value\":88,\"unit\":\"bpm\",\"recordedAt\":\"$RECORDED_AT\"}" \
    "$entry_token" >/dev/null

  local submit_code
  submit_code="$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/submit-for-verification" \
    -H "Authorization: Bearer $entry_token")"
  if [[ "$submit_code" != "200" ]]; then
    log "ERROR: submit-for-verification failed (HTTP $submit_code)"
    return 1
  fi

  printf '%s' "$batch_id"
}

# Drive a PATIENT_REGISTRATION batch to PENDING_VERIFICATION.
create_pending_patient_registration_batch() {
  local intake_token="$1"
  local entry_token batch_id

  batch_id="$(create_assigned_batch "$intake_token" "PATIENT_REGISTRATION")" || return 1
  entry_token="$(extract_data_field "$(login entry1)" token)"

  json_put \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/patient" \
    '{"fullName":"Maria Santos","dateOfBirth":"1992-07-20","sex":"F","noKnownAllergies":true,"noActiveMedications":true}' \
    "$entry_token" >/dev/null

  local submit_code
  submit_code="$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/submit-for-verification" \
    -H "Authorization: Bearer $entry_token")"
  if [[ "$submit_code" != "200" ]]; then
    log "ERROR: patient registration submit failed (HTTP $submit_code)"
    return 1
  fi

  printf '%s' "$batch_id"
}

test_separation_of_duties() {
  section "1. Separation of duties — same user cannot verify own batch (409)"

  if ! psql_available; then
    if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
      log "  SKIP: separation of duties (VIGILCARE_SKIP_DB_CHECKS=1)"
    else
      log "  SKIP: separation of duties (postgres not reachable)"
    fi
    return
  fi

  local intake_json intake_token batch_id verifier_json verifier_id
  local verify_json verify_code error_code

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_json="$(login verifier1)"
  verifier_id="$(extract_data_field "$verifier_json" userId)"

  # Simulate a batch entered by verifier1 — they must not verify their own work.
  psql_query "UPDATE digitization_batches SET entered_by_user_id = '$verifier_id' WHERE id = '$batch_id';" >/dev/null

  verify_json="$(verify_batch "$(extract_data_field "$verifier_json" token)" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null}],"passed":true}')"
  verify_code="$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/verify" \
    -H "Authorization: Bearer $(extract_data_field "$verifier_json" token)" \
    -H 'Content-Type: application/json' \
    -d '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null}],"passed":true}')"
  error_code="$(extract_error_code "$verify_json")"

  if [[ "$verify_code" == "409" && "$error_code" == "SEPARATION_OF_DUTIES_VIOLATION" ]]; then
    pass "verifier cannot verify batch they entered (409 SEPARATION_OF_DUTIES_VIOLATION)"
  else
    fail "verifier cannot verify batch they entered (409 SEPARATION_OF_DUTIES_VIOLATION) (http=$verify_code code=${error_code:-<none>})"
  fi
}

test_different_verifier_succeeds() {
  section "2. Different verifier succeeds — vitals batch transitions to AWAITING_CLINICAL_APPROVAL"

  local intake_json intake_token batch_id verifier_json verifier_token
  local verify_json status

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_json="$(login verifier1)"
  verifier_token="$(extract_data_field "$verifier_json" token)"

  verify_json="$(verify_batch "$verifier_token" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null},{"fieldName":"observation.heartRate","status":"ok","note":"Within range"}],"passed":true}')"
  status="$(extract_data_field "$verify_json" status)"

  if [[ "$(jq -er '.success' <<<"$verify_json")" == "true" && "$status" == "AWAITING_CLINICAL_APPROVAL" ]]; then
    pass "verifier1 verifies entry1 batch (AWAITING_CLINICAL_APPROVAL for VITALS_SHEET)"
  else
    fail "verifier1 verifies entry1 batch (AWAITING_CLINICAL_APPROVAL for VITALS_SHEET) (status=${status:-<none>})"
  fi
}

test_clinical_approval_routing_patient_registration() {
  section "3. Clinical approval routing — PATIENT_REGISTRATION goes directly to VERIFIED"

  local intake_json intake_token batch_id verifier_token verify_json status

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_patient_registration_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"

  verify_json="$(verify_batch "$verifier_token" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null},{"fieldName":"patient.dateOfBirth","status":"ok","note":null}],"passed":true}')"
  status="$(extract_data_field "$verify_json" status)"

  if [[ "$(jq -er '.success' <<<"$verify_json")" == "true" && "$status" == "VERIFIED" ]]; then
    pass "PATIENT_REGISTRATION verification skips clinical approval (VERIFIED)"
  else
    fail "PATIENT_REGISTRATION verification skips clinical approval (VERIFIED) (status=${status:-<none>})"
  fi
}

test_reject_with_valid_reason() {
  section "4. Rejection with reason — batch becomes REJECTED and appears in entry queue"

  local intake_json intake_token batch_id verifier_token entry_token
  local reject_json status queue_json queue_contains

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"
  entry_token="$(extract_data_field "$(login entry1)" token)"

  reject_json="$(reject_batch "$verifier_token" "$batch_id" \
    "Patient name does not match the scanned registration form. Please re-enter from chart.")"
  status="$(extract_data_field "$reject_json" status)"

  if [[ "$(jq -er '.success' <<<"$reject_json")" == "true" && "$status" == "REJECTED" ]]; then
    pass "reject with valid reason transitions to REJECTED"
  else
    fail "reject with valid reason transitions to REJECTED (status=${status:-<none>})"
    return
  fi

  if psql_available; then
    local db_status
    db_status="$(psql_query "SELECT status FROM digitization_batches WHERE id = '$batch_id';")"
    if [[ "$db_status" == "REJECTED" ]]; then
      pass "rejected batch status confirmed in PostgreSQL"
    else
      fail "rejected batch status confirmed in PostgreSQL (got: ${db_status:-<none>})"
    fi
  fi

  if queue_contains_batch "$entry_token" "entry" "$batch_id"; then
    pass "rejected batch appears in entry work queue"
  else
    fail "rejected batch appears in entry work queue"
  fi
}

test_reject_validation() {
  section "5. Rejection validation — missing or short reason returns 422"

  local intake_json intake_token batch_id verifier_token
  local empty_json empty_code empty_error short_json short_error

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"

  empty_json="$(reject_batch "$verifier_token" "$batch_id" "")"
  empty_code="$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/reject" \
    -H "Authorization: Bearer $verifier_token" \
    -H 'Content-Type: application/json' \
    -d '{"reason":""}')"
  empty_error="$(extract_error_code "$empty_json")"

  if [[ "$empty_code" == "422" && "$empty_error" == "REJECTION_REASON_REQUIRED" ]]; then
    pass "reject without reason returns 422 REJECTION_REASON_REQUIRED"
  else
    fail "reject without reason returns 422 REJECTION_REASON_REQUIRED (http=$empty_code code=${empty_error:-<none>})"
  fi

  short_json="$(reject_batch "$verifier_token" "$batch_id" "Bad data")"
  short_error="$(extract_error_code "$short_json")"
  if [[ "$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/reject" \
    -H "Authorization: Bearer $verifier_token" \
    -H 'Content-Type: application/json' \
    -d '{"reason":"Bad data"}')" == "422" &&
        "$short_error" == "REJECTION_REASON_TOO_SHORT" ]]; then
    pass "reject with short reason returns 422 REJECTION_REASON_TOO_SHORT"
  else
    fail "reject with short reason returns 422 REJECTION_REASON_TOO_SHORT (code=${short_error:-<none>})"
  fi
}

test_verify_wrong_status() {
  section "6. Status guard — verify on non-pending batch returns 409"

  if ! psql_available; then
    if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
      log "  SKIP: verify wrong status (VIGILCARE_SKIP_DB_CHECKS=1)"
    else
      log "  SKIP: verify wrong status (postgres not reachable)"
    fi
    return
  fi

  local intake_json intake_token batch_id verifier_token verify_json error_code

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  psql_query "UPDATE digitization_batches SET status = 'REJECTED' WHERE id = '$batch_id';" >/dev/null

  verifier_token="$(extract_data_field "$(login verifier1)" token)"
  verify_json="$(verify_batch "$verifier_token" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null}],"passed":true}')"
  error_code="$(extract_error_code "$verify_json")"

  if [[ "$(http_code -X POST \
    "$API_URL/api/v1/digitization-batches/$batch_id/verify" \
    -H "Authorization: Bearer $verifier_token" \
    -H 'Content-Type: application/json' \
    -d '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null}],"passed":true}')" == "409" &&
        "$error_code" == "ILLEGAL_STATUS_TRANSITION" ]]; then
    pass "verify on REJECTED batch returns 409 ILLEGAL_STATUS_TRANSITION"
  else
    fail "verify on REJECTED batch returns 409 ILLEGAL_STATUS_TRANSITION (code=${error_code:-<none>})"
  fi
}

test_verify_passed_false() {
  section "7. Verification failed — passed=false transitions to REJECTED with field errors"

  local intake_json intake_token batch_id verifier_token verify_json status reason

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"

  verify_json="$(verify_batch "$verifier_token" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null},{"fieldName":"observation.heartRate","status":"error","note":"Value 350 is not physiologically possible"}],"passed":false}')"
  status="$(extract_data_field "$verify_json" status)"
  reason="$(jq -er '.data.rejectionReason // empty' <<<"$verify_json")"

  if [[ "$(jq -er '.success' <<<"$verify_json")" == "true" && "$status" == "REJECTED" &&
        "$reason" == *"350 is not physiologically possible"* ]]; then
    pass "passed=false verification rejects batch with field error details"
  else
    fail "passed=false verification rejects batch with field error details (status=${status:-<none>})"
  fi
}

test_verification_queue() {
  section "8. Work queues — verification queue lists PENDING_VERIFICATION batches only"

  local intake_json intake_token batch_id verifier_token queue_json
  local item_count all_pending

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"

  if queue_contains_batch "$verifier_token" "verification" "$batch_id"; then
    pass "verification queue includes test batch"
  else
    fail "verification queue includes test batch"
    return
  fi

  queue_json="$(curl -sS "$API_URL/api/v1/work-queue/verification?page=1&pageSize=100" \
    -H "Authorization: Bearer $verifier_token")"
  item_count="$(jq -er '.data.items | length' <<<"$queue_json")"
  all_pending="$(jq -er 'if (.data.items | length) == 0 then true else ([.data.items[].status] | all(. == "PENDING_VERIFICATION")) end' <<<"$queue_json")"

  if [[ "$item_count" -ge 1 && "$all_pending" == "true" ]]; then
    pass "verification queue returns only PENDING_VERIFICATION batches"
  else
    fail "verification queue returns only PENDING_VERIFICATION batches (count=$item_count)"
  fi
}

test_event_trail() {
  section "9. Event trail — digitization_events recorded for verify/reject"

  if ! psql_available; then
    if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
      log "  SKIP: event trail (VIGILCARE_SKIP_DB_CHECKS=1)"
    else
      log "  SKIP: event trail (postgres not reachable)"
    fi
    return
  fi

  local intake_json intake_token batch_id verifier_token verify_json event_types

  intake_json="$(login intake1)"
  intake_token="$(extract_data_field "$intake_json" token)"
  batch_id="$(create_pending_vitals_batch "$intake_token")" || return

  verifier_token="$(extract_data_field "$(login verifier1)" token)"

  verify_json="$(verify_batch "$verifier_token" "$batch_id" \
    '{"fieldChecks":[{"fieldName":"patient.fullName","status":"ok","note":null}],"passed":true}')"
  if [[ "$(jq -er '.success' <<<"$verify_json")" != "true" ]]; then
    fail "event trail setup — verify succeeded"
    return
  fi

  event_types="$(psql_query "SELECT event_type FROM digitization_events WHERE batch_id = '$batch_id' ORDER BY occurred_at;")"
  if grep -q 'submitted_for_verification' <<<"$event_types" &&
     grep -q 'verified_pending_clinical' <<<"$event_types"; then
    pass "digitization_events include submitted_for_verification and verified_pending_clinical"
  else
    fail "digitization_events include submitted_for_verification and verified_pending_clinical"
    log "       events: $(tr '\n' ' ' <<<"$event_types")"
  fi

  local metadata
  metadata="$(psql_query "SELECT metadata_json FROM digitization_events WHERE batch_id = '$batch_id' AND event_type = 'verified_pending_clinical' LIMIT 1;")"
  if [[ "$metadata" == *"fieldChecks"* ]]; then
    pass "verify event metadata_json contains fieldChecks"
  else
    fail "verify event metadata_json contains fieldChecks"
  fi
}

main() {
  require_cmd curl
  require_cmd jq
  require_cmd docker

  if [[ ! -f "$FIXTURE_PDF" ]]; then
    log "ERROR: missing fixture PDF at $FIXTURE_PDF"
    exit 1
  fi

  log "VigilCare Records — Phase 3 verification"
  log "API: $API_URL"
  if compose_service_running postgres; then
    log "PostgreSQL: docker compose exec (service: postgres)"
  elif command -v psql >/dev/null 2>&1; then
    log "PostgreSQL: host psql ($PG_HOST:$PG_PORT)"
  fi

  assert_api_reachable

  test_separation_of_duties
  test_different_verifier_succeeds
  test_clinical_approval_routing_patient_registration
  test_reject_with_valid_reason
  test_reject_validation
  test_verify_wrong_status
  test_verify_passed_false
  test_verification_queue
  test_event_trail

  log ""
  log "Results: $PASS_COUNT passed, $FAIL_COUNT failed"
  if (( FAIL_COUNT > 0 )); then
    log "Failed checks:"
    for item in "${FAILED_TESTS[@]}"; do
      log "  - $item"
    done
    exit 1
  fi

  log "All Phase 3 verification checks passed."
}

main "$@"
