#!/usr/bin/env bash
# Runs Phase 5 verification checks from docs/plans/phase-5-plan.md.
#
# Covers correction batch creation, supersession on promotion, live observation
# flags, patient digitization history, audit trail integrity, and integration tests.
#
# Prerequisites:
#   docker compose up -d          (PostgreSQL + Redis + MinIO)
#   dotnet ef database update --project VigilCareRecordsAPI
#   dotnet run --project VigilCareRecordsAPI
#   Phase 1–4 seed data (intake1, entry1/2, verifier1/2, approver1/2, clinician1, admin1)
#
# 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–4 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_SKIP_TEST_CHECKS     set to 1 to skip dotnet integration tests
#   VIGILCARE_RECORDED_AT          default: 2024-06-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}"
SKIP_TEST_CHECKS="${VIGILCARE_SKIP_TEST_CHECKS:-0}"

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

PASS_COUNT=0
FAIL_COUNT=0
FAILED_TESTS=()

# Populated by the full correction flow test for downstream checks.
SHARED_ORIGINAL_BATCH_ID=""
SHARED_CORRECTION_BATCH_ID=""
SHARED_PATIENT_ID=""

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
  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
}

new_idempotency_key() {
  if command -v uuidgen >/dev/null 2>&1; then
    uuidgen
  else
    cat /proc/sys/kernel/random/uuid
  fi
}

# Unique PDF per upload — duplicate SHA-256 detection rejects same file for one patient.
create_temp_pdf() {
  local suffix="${1:-$(date +%s%N)}"
  local path
  path="$(mktemp "/tmp/vigilcare-correction-${suffix}-XXXXXX.pdf")"
  printf '%%PDF-1.4 correction-%s\n' "$suffix" > "$path"
  printf '%s' "$path"
}

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 // .extensions.code // empty' <<<"$json" 2>/dev/null ||
    jq -er '.title // empty' <<<"$json" 2>/dev/null || true
}

upload_batch() {
  local token="$1"
  local batch_type="${2:-LAB_RESULTS}"
  local track="${3:-BACKFILL}"
  local file_path="${4:-$FIXTURE_PDF}"
  local supersedes_batch_id="${5:-}"
  local patient_id="${6:-}"

  local -a form_args=(
    -H "Authorization: Bearer $token"
    -F "file=@${file_path};type=application/pdf"
    -F "batchType=$batch_type"
    -F "track=$track"
  )
  if [[ -n "$supersedes_batch_id" ]]; then
    form_args+=(-F "supersedesBatchId=$supersedes_batch_id")
  fi
  if [[ -n "$patient_id" ]]; then
    form_args+=(-F "patientId=$patient_id")
  fi

  curl -sS -X POST "$API_URL/api/v1/digitization-batches" "${form_args[@]}"
}

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"
}

approve_batch() {
  local token="$1"
  local batch_id="$2"
  local idempotency_key="$3"
  local body="${4:-{\"enableRetroactiveAlerts\":false}}"

  curl -sS -X POST "$API_URL/api/v1/digitization-batches/$batch_id/approve" \
    -H "Authorization: Bearer $token" \
    -H 'Content-Type: application/json' \
    -H "Idempotency-Key: $idempotency_key" \
    -d "$body"
}

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 "Apply migrations with: dotnet ef database update --project VigilCareRecordsAPI"
    log "Start API with: dotnet run --project VigilCareRecordsAPI"
    exit 1
  fi
}

# Upload, enter lab draft data, submit, and verify.
# Leaves batch in AWAITING_CLINICAL_APPROVAL (LAB_RESULTS). Prints batch id to stdout.
create_lab_batch_ready_for_approval() {
  local intake_token="$1"
  local potassium_value="${2:-3.5}"
  local sodium_value="${3:-140}"
  local patient_name="${4:-Phase 5 Lab Patient}"
  local patient_dob="${5:-1980-01-15}"

  local entry_token verifier_token batch_id patient_json upload_json verify_json verify_status

  patient_json="$(jq -nc \
    --arg name "$patient_name" \
    --arg dob "$patient_dob" \
    '{fullName: $name, dateOfBirth: $dob, sex: "Female"}')"

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

  batch_id="$(extract_data_field "$upload_json" id)"
  entry_token="$(extract_data_field "$(login entry1)" token)"

  json_put \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/patient" \
    "$patient_json" \
    "$entry_token" >/dev/null

  json_put \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/encounter" \
    '{"admissionDate":"2024-06-01T08:00:00Z","department":"Internal Medicine","roomBed":"4A-12","admissionReason":"Electrolyte panel","status":"active"}' \
    "$entry_token" >/dev/null

  json_post \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/observations" \
    "{\"observationCode\":\"K\",\"value\":$potassium_value,\"unit\":\"mmol/L\",\"recordedAt\":\"$RECORDED_AT\"}" \
    "$entry_token" >/dev/null

  json_post \
    "$API_URL/api/v1/digitization-batches/$batch_id/draft/observations" \
    "{\"observationCode\":\"Na\",\"value\":$sodium_value,\"unit\":\"mmol/L\",\"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

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

  if [[ "$(jq -er '.success' <<<"$verify_json")" != "true" || "$verify_status" != "AWAITING_CLINICAL_APPROVAL" ]]; then
    log "ERROR: verification failed (status=${verify_status:-<none>})"
    return 1
  fi

  printf '%s' "$batch_id"
}

# Promote a correction batch (observations only) through entry, verify, and approve.
# Prints correction batch id to stdout.
promote_correction_batch() {
  local intake_token="$1"
  local original_batch_id="$2"
  local patient_id="$3"
  local potassium_value="${4:-5.3}"
  local sodium_value="${5:-140}"

  local entry_token verifier_token approver_token
  local upload_json correction_batch_id approve_json correction_pdf

  correction_pdf="$(create_temp_pdf "$original_batch_id")"
  upload_json="$(upload_batch "$intake_token" "LAB_RESULTS" "BACKFILL" "$correction_pdf" \
    "$original_batch_id" "$patient_id")"
  rm -f "$correction_pdf"
  if [[ "$(jq -er '.success' <<<"$upload_json")" != "true" ]]; then
    log "ERROR: failed to upload correction batch: $(jq -c '.' <<<"$upload_json" 2>/dev/null || echo "$upload_json")"
    return 1
  fi

  correction_batch_id="$(extract_data_field "$upload_json" id)"
  local upload_status is_correction
  upload_status="$(extract_data_field "$upload_json" status)"
  is_correction="$(extract_data_field "$upload_json" isCorrection)"

  if [[ "$upload_status" != "UPLOADED" || "$is_correction" != "true" ]]; then
    log "ERROR: correction upload status=$upload_status isCorrection=$is_correction"
    return 1
  fi

  entry_token="$(extract_data_field "$(login entry2)" token)"
  json_post \
    "$API_URL/api/v1/digitization-batches/$correction_batch_id/draft/observations" \
    "{\"observationCode\":\"K\",\"value\":$potassium_value,\"unit\":\"mmol/L\",\"recordedAt\":\"$RECORDED_AT\"}" \
    "$entry_token" >/dev/null
  json_post \
    "$API_URL/api/v1/digitization-batches/$correction_batch_id/draft/observations" \
    "{\"observationCode\":\"Na\",\"value\":$sodium_value,\"unit\":\"mmol/L\",\"recordedAt\":\"$RECORDED_AT\"}" \
    "$entry_token" >/dev/null

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

  verifier_token="$(extract_data_field "$(login verifier2)" token)"
  local verify_json
  verify_json="$(verify_batch "$verifier_token" "$correction_batch_id" \
    '{"fieldChecks":[{"fieldName":"observation.K","status":"ok","note":null}],"passed":true}')"
  if [[ "$(jq -er '.success' <<<"$verify_json")" != "true" ]]; then
    log "ERROR: correction verification failed"
    return 1
  fi

  approver_token="$(extract_data_field "$(login approver2)" token)"
  approve_json="$(approve_batch "$approver_token" "$correction_batch_id" "$(new_idempotency_key)")"
  if [[ "$(jq -er '.success' <<<"$approve_json")" != "true" ]]; then
    log "ERROR: correction approve failed"
    return 1
  fi

  if [[ "$(extract_data_field "$approve_json" status)" != "PROMOTED" ]]; then
    log "ERROR: correction batch not promoted"
    return 1
  fi

  printf '%s' "$correction_batch_id"
}

test_schema_supersession_columns() {
  section "1. Schema — live_observations supersession columns and partial index"

  if ! psql_available; then
    log "  SKIP: PostgreSQL not reachable"
    return
  fi

  local columns index_count
  columns="$(psql_query "
    SELECT column_name
    FROM information_schema.columns
    WHERE table_name = 'live_observations'
      AND column_name IN ('is_superseded', 'superseded_by_batch_id', 'superseded_at')
    ORDER BY column_name;
  " | tr '\n' ',' | sed 's/,$//')"

  if [[ "$columns" == "is_superseded,superseded_at,superseded_by_batch_id" ]]; then
    pass "live_observations has is_superseded, superseded_by_batch_id, superseded_at"
  else
    fail "live_observations has supersession columns (got: ${columns:-<none>})"
  fi

  index_count="$(psql_query "
    SELECT count(*)
    FROM pg_indexes
    WHERE tablename = 'live_observations'
      AND indexdef ILIKE '%is_superseded%';
  ")"
  if [[ "$index_count" -ge 1 ]]; then
    pass "partial index on is_superseded exists"
  else
    fail "partial index on is_superseded exists (count=${index_count:-<none>})"
  fi
}

test_no_live_observation_mutation_endpoint() {
  section "2. Invariant — no API endpoint mutates live observations directly"

  local swagger_paths
  swagger_paths="$(curl -sS "$API_URL/swagger/v1/swagger.json")"

  if jq -e '.paths | keys[] | select(test("live-observation|live_observation"; "i"))' \
      <<<"$swagger_paths" >/dev/null 2>&1; then
    fail "swagger exposes live observation mutation routes"
    return
  fi
  pass "swagger has no live observation mutation routes"

  local patch_code
  patch_code="$(http_code -X PATCH \
    "$API_URL/api/v1/live-observations/$(new_idempotency_key)" \
    -H "Authorization: Bearer $(extract_data_field "$(login admin1)" token)" \
    -H 'Content-Type: application/json' \
    -d '{"value":99}')"

  if [[ "$patch_code" == "404" || "$patch_code" == "405" ]]; then
    pass "PATCH /api/v1/live-observations/:id is not available ($patch_code)"
  else
    fail "PATCH /api/v1/live-observations/:id is not available (http=$patch_code)"
  fi
}

test_full_correction_supersession_flow() {
  section "3. Full cycle — wrong K promoted, correction supersedes original"

  local intake_token approver_token original_batch_id correction_batch_id
  local approve_json patient_id

  intake_token="$(extract_data_field "$(login intake1)" token)"
  original_batch_id="$(create_lab_batch_ready_for_approval "$intake_token" "3.5" "140" \
    "Phase5 Correction Patient $(date +%s)" "1980-01-15")" || return

  approver_token="$(extract_data_field "$(login approver1)" token)"
  approve_json="$(approve_batch "$approver_token" "$original_batch_id" "$(new_idempotency_key)")"
  if [[ "$(jq -er '.success' <<<"$approve_json")" != "true" ]]; then
    fail "original lab batch promotes successfully"
    return
  fi

  patient_id="$(extract_data_field "$approve_json" patientId)"
  if [[ -z "$patient_id" ]]; then
    fail "original promotion returns patientId"
    return
  fi
  pass "original lab batch promoted with wrong K=3.5"

  correction_batch_id="$(promote_correction_batch "$intake_token" "$original_batch_id" \
    "$patient_id" "5.3" "140")" || {
    fail "correction batch promoted with corrected K=5.3"
    return
  }
  pass "correction batch promoted with corrected K=5.3"

  SHARED_ORIGINAL_BATCH_ID="$original_batch_id"
  SHARED_CORRECTION_BATCH_ID="$correction_batch_id"
  SHARED_PATIENT_ID="$patient_id"

  if ! psql_available; then
    log "  SKIP: live_observations supersession DB checks"
    return
  fi

  local active_k superseded_k total_count correction_event
  active_k="$(psql_query "
    SELECT value::text
    FROM live_observations
    WHERE patient_id = '$patient_id'
      AND observation_code = 'K'
      AND is_superseded = false;
  ")"
  superseded_k="$(psql_query "
    SELECT value::text
    FROM live_observations
    WHERE patient_id = '$patient_id'
      AND observation_code = 'K'
      AND is_superseded = true
      AND superseded_by_batch_id = '$correction_batch_id';
  ")"
  total_count="$(psql_query "
    SELECT count(*)
    FROM live_observations
    WHERE patient_id = '$patient_id';
  ")"
  correction_event="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$correction_batch_id'
      AND event_type = 'correction_uploaded';
  ")"

  if [[ "$active_k" == "5.300" || "$active_k" == "5.3" ]]; then
    pass "active potassium value is 5.3 after correction"
  else
    fail "active potassium value is 5.3 after correction (got: ${active_k:-<none>})"
  fi

  if [[ "$superseded_k" == "3.500" || "$superseded_k" == "3.5" ]]; then
    pass "superseded potassium value 3.5 preserved for audit"
  else
    fail "superseded potassium value 3.5 preserved for audit (got: ${superseded_k:-<none>})"
  fi

  if [[ "$total_count" == "4" ]]; then
    pass "four live_observations rows retained (2 superseded + 2 active)"
  else
    fail "four live_observations rows retained (got: ${total_count:-<none>})"
  fi

  if [[ "$correction_event" == "1" ]]; then
    pass "correction_uploaded event recorded on correction batch"
  else
    fail "correction_uploaded event recorded on correction batch (count=${correction_event:-<none>})"
  fi
}

test_supersession_validation_non_promoted() {
  section "4. Validation — correction against non-promoted batch returns 422"

  local intake_token upload_json error_code upload_code

  intake_token="$(extract_data_field "$(login intake1)" token)"
  local pending_batch_id
  pending_batch_id="$(extract_data_field "$(upload_batch "$intake_token")" id)"

  upload_json="$(upload_batch "$intake_token" "LAB_RESULTS" "BACKFILL" "$FIXTURE_PDF" \
    "$pending_batch_id")"
  upload_code="$(jq -er '.statusCode // empty' <<<"$upload_json")"
  error_code="$(extract_error_code "$upload_json")"

  if [[ "$upload_code" == "422" && "$error_code" == "SUPERSEDED_BATCH_NOT_PROMOTED" ]]; then
    pass "non-promoted batch supersession returns 422 SUPERSEDED_BATCH_NOT_PROMOTED"
  else
    fail "non-promoted batch supersession returns 422 SUPERSEDED_BATCH_NOT_PROMOTED (http=$upload_code code=${error_code:-<none>})"
  fi
}

test_supersession_validation_not_found() {
  section "5. Validation — correction against missing batch returns 404"

  local intake_token upload_json error_code upload_code fake_id

  fake_id="$(new_idempotency_key)"
  intake_token="$(extract_data_field "$(login intake1)" token)"
  upload_json="$(upload_batch "$intake_token" "LAB_RESULTS" "BACKFILL" "$FIXTURE_PDF" "$fake_id")"
  upload_code="$(jq -er '.statusCode // empty' <<<"$upload_json")"
  error_code="$(extract_error_code "$upload_json")"

  if [[ "$upload_code" == "404" && "$error_code" == "SUPERSEDED_BATCH_NOT_FOUND" ]]; then
    pass "missing batch supersession returns 404 SUPERSEDED_BATCH_NOT_FOUND"
  else
    fail "missing batch supersession returns 404 SUPERSEDED_BATCH_NOT_FOUND (http=$upload_code code=${error_code:-<none>})"
  fi
}

test_supersession_validation_already_superseded() {
  section "6. Validation — second correction against superseded batch returns 409"

  if [[ -z "$SHARED_ORIGINAL_BATCH_ID" || -z "$SHARED_PATIENT_ID" ]]; then
    fail "already-superseded validation requires full correction flow (run test 3 first)"
    return
  fi

  local intake_token upload_json error_code upload_code correction_pdf

  intake_token="$(extract_data_field "$(login intake1)" token)"
  correction_pdf="$(create_temp_pdf "already-superseded")"
  upload_json="$(upload_batch "$intake_token" "LAB_RESULTS" "BACKFILL" "$correction_pdf" \
    "$SHARED_ORIGINAL_BATCH_ID" "$SHARED_PATIENT_ID")"
  rm -f "$correction_pdf"
  upload_code="$(jq -er '.statusCode // empty' <<<"$upload_json")"
  error_code="$(extract_error_code "$upload_json")"

  if [[ "$upload_code" == "409" && "$error_code" == "BATCH_ALREADY_SUPERSEDED" ]]; then
    pass "already-superseded batch returns 409 BATCH_ALREADY_SUPERSEDED"
  else
    fail "already-superseded batch returns 409 BATCH_ALREADY_SUPERSEDED (http=$upload_code code=${error_code:-<none>})"
  fi
}

test_patient_digitization_history() {
  section "7. Patient digitization history — correction chain in API response"

  if [[ -z "$SHARED_PATIENT_ID" || -z "$SHARED_ORIGINAL_BATCH_ID" || -z "$SHARED_CORRECTION_BATCH_ID" ]]; then
    fail "digitization history requires full correction flow (run test 3 first)"
    return
  fi

  local clinician_token history_json
  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  history_json="$(curl -sS \
    "$API_URL/api/v1/patients/$SHARED_PATIENT_ID/digitization-history" \
    -H "Authorization: Bearer $clinician_token")"

  local total promoted superseded
  total="$(extract_data_field "$history_json" totalBatches)"
  promoted="$(extract_data_field "$history_json" promotedBatches)"
  superseded="$(extract_data_field "$history_json" supersededBatches)"

  if [[ "$(jq -er '.success' <<<"$history_json")" == "true" &&
        "$total" == "2" && "$promoted" == "2" && "$superseded" == "1" ]]; then
    pass "history summary counts: totalBatches=2, promotedBatches=2, supersededBatches=1"
  else
    fail "history summary counts (total=$total promoted=$promoted superseded=$superseded)"
    return
  fi

  local original_superseded correction_is_correction
  original_superseded="$(jq -er \
    --arg id "$SHARED_ORIGINAL_BATCH_ID" \
    '.data.entries[] | select(.batchId == $id) | .hasBeenSuperseded' <<<"$history_json")"
  correction_is_correction="$(jq -er \
    --arg id "$SHARED_CORRECTION_BATCH_ID" \
    '.data.entries[] | select(.batchId == $id) | .isCorrection' <<<"$history_json")"

  if [[ "$original_superseded" == "true" && "$correction_is_correction" == "true" ]]; then
    pass "original marked hasBeenSuperseded; correction marked isCorrection"
  else
    fail "original hasBeenSuperseded=$original_superseded correction isCorrection=$correction_is_correction"
  fi

  local audit_len
  audit_len="$(jq -er \
    --arg id "$SHARED_ORIGINAL_BATCH_ID" \
    '.data.entries[] | select(.batchId == $id) | .auditTrail | length' <<<"$history_json")"
  if [[ "$audit_len" -ge 2 ]]; then
    pass "original batch entry includes audit trail events"
  else
    fail "original batch entry includes audit trail events (len=${audit_len:-0})"
  fi
}

test_patient_history_unknown_patient() {
  section "8. Patient digitization history — unknown patient returns 404"

  local admin_token unknown_id history_code history_json error_code

  unknown_id="$(new_idempotency_key)"
  admin_token="$(extract_data_field "$(login admin1)" token)"
  history_json="$(curl -sS \
    "$API_URL/api/v1/patients/$unknown_id/digitization-history" \
    -H "Authorization: Bearer $admin_token")"
  history_code="$(jq -er '.statusCode // empty' <<<"$history_json")"
  error_code="$(extract_error_code "$history_json")"

  if [[ "$history_code" == "404" && "$error_code" == "PATIENT_HISTORY_NOT_FOUND" ]]; then
    pass "unknown patient returns 404 PATIENT_HISTORY_NOT_FOUND"
  else
    fail "unknown patient returns 404 PATIENT_HISTORY_NOT_FOUND (http=$history_code code=${error_code:-<none>})"
  fi
}

test_audit_trail_integrity() {
  section "9. Audit trail — superseded and correction_promoted events"

  if [[ -z "$SHARED_ORIGINAL_BATCH_ID" || -z "$SHARED_CORRECTION_BATCH_ID" ]]; then
    fail "audit trail check requires full correction flow (run test 3 first)"
    return
  fi

  if ! psql_available; then
    log "  SKIP: audit trail DB checks"
    return
  fi

  local original_superseded correction_promoted original_promoted
  original_superseded="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$SHARED_ORIGINAL_BATCH_ID'
      AND event_type = 'superseded';
  ")"
  correction_promoted="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$SHARED_CORRECTION_BATCH_ID'
      AND event_type = 'correction_promoted';
  ")"
  original_promoted="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$SHARED_ORIGINAL_BATCH_ID'
      AND event_type = 'promoted';
  ")"

  if [[ "$original_promoted" == "1" ]]; then
    pass "original batch has promoted event"
  else
    fail "original batch has promoted event (count=${original_promoted:-<none>})"
  fi

  if [[ "$original_superseded" == "1" ]]; then
    pass "original batch has superseded event after correction promotion"
  else
    fail "original batch has superseded event (count=${original_superseded:-<none>})"
  fi

  if [[ "$correction_promoted" == "1" ]]; then
    pass "correction batch has correction_promoted event"
  else
    fail "correction batch has correction_promoted event (count=${correction_promoted:-<none>})"
  fi
}

test_active_observation_filter() {
  section "10. Clinical query — active observations exclude superseded rows"

  if [[ -z "$SHARED_PATIENT_ID" ]]; then
    fail "active observation filter requires full correction flow (run test 3 first)"
    return
  fi

  if ! psql_available; then
    log "  SKIP: active observation filter DB checks"
    return
  fi

  local active_count audit_count
  active_count="$(psql_query "
    SELECT count(*)
    FROM live_observations
    WHERE patient_id = '$SHARED_PATIENT_ID'
      AND is_superseded = false;
  ")"
  audit_count="$(psql_query "
    SELECT count(*)
    FROM live_observations
    WHERE patient_id = '$SHARED_PATIENT_ID'
      AND is_superseded = true;
  ")"

  if [[ "$active_count" == "2" ]]; then
    pass "default active query returns 2 non-superseded observations"
  else
    fail "default active query returns 2 non-superseded observations (got: ${active_count:-<none>})"
  fi

  if [[ "$audit_count" == "2" ]]; then
    pass "audit query returns 2 superseded observations"
  else
    fail "audit query returns 2 superseded observations (got: ${audit_count:-<none>})"
  fi
}

test_integration_tests() {
  section "11. Integration tests — CorrectionSupersessionTests"

  if [[ "$SKIP_TEST_CHECKS" == "1" ]]; then
    log "  SKIP: VIGILCARE_SKIP_TEST_CHECKS=1"
    return
  fi

  if ! command -v dotnet >/dev/null 2>&1; then
    log "  SKIP: dotnet not found"
    return
  fi

  local test_output test_exit
  test_output="$(dotnet test "$REPO_ROOT/VigilCareRecordsAPI.Tests" \
    --filter "FullyQualifiedName~CorrectionSupersessionTests" \
    --verbosity minimal 2>&1)"
  test_exit=$?

  if [[ "$test_exit" -eq 0 ]] && grep -q "Passed!" <<<"$test_output"; then
    pass "CorrectionSupersessionTests pass (dotnet test)"
  else
    fail "CorrectionSupersessionTests pass (dotnet test)"
    log "$test_output"
  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 5 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_schema_supersession_columns
  test_no_live_observation_mutation_endpoint
  test_full_correction_supersession_flow
  test_supersession_validation_non_promoted
  test_supersession_validation_not_found
  test_supersession_validation_already_superseded
  test_patient_digitization_history
  test_patient_history_unknown_patient
  test_audit_trail_integrity
  test_active_observation_filter
  test_integration_tests

  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 5 verification checks passed."
}

main "$@"
