#!/usr/bin/env bash
# Runs Phase 6 verification checks from docs/plans/phase-6-plan.md.
#
# Covers Track B live capture: clinician attestation, synchronous promotion,
# critical threshold alerting, outbox events, open-encounter workflow, and
# integration tests.
#
# Prerequisites:
#   docker compose up -d          (PostgreSQL + Redis + MinIO)
#   dotnet ef database update --project VigilCareRecordsAPI
#   dotnet run --project VigilCareRecordsAPI
#   Phase 1 seed data (clinician1, entry1, and other demo users)
#
# 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–5 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: 2026-06-25T10:00:00Z

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

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:-2026-06-25T10:00:00Z}"
RECORDED_AT_CRITICAL="${VIGILCARE_RECORDED_AT_CRITICAL:-2026-06-25T10:05:00Z}"
RECORDED_AT_OUTPATIENT="${VIGILCARE_RECORDED_AT_OUTPATIENT:-2026-06-25T11:00:00Z}"

PASS_COUNT=0
FAIL_COUNT=0
FAILED_TESTS=()

# Populated by promotion / alert tests for downstream checks.
SHARED_LIVE_OBS_ID=""
SHARED_BATCH_ID=""
SHARED_ALERT_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
}

new_uuid() {
  if command -v uuidgen >/dev/null 2>&1; then
    uuidgen
  else
    cat /proc/sys/kernel/random/uuid
  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
}

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
}

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
}

extract_status_code() {
  local json="$1"
  jq -er '.statusCode // empty' <<<"$json"
}

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
}

seed_potassium_threshold() {
  if ! psql_available; then
    return 1
  fi
  psql_query "
    INSERT INTO clinical.alert_thresholds
      (id, observation_code, display_name, unit,
       critical_low, warning_low, warning_high, critical_high, created_at)
    SELECT gen_random_uuid(), 'POTASSIUM_MEQ_L', 'Serum Potassium', 'mEq/L',
           2.5, 3.5, 5.0, 6.5, NOW()
    WHERE NOT EXISTS (
      SELECT 1 FROM clinical.alert_thresholds
      WHERE observation_code = 'POTASSIUM_MEQ_L'
    );
  " >/dev/null
}

# Creates clinical.patients + clinical.encounters (active). Prints patient_id|encounter_id.
create_patient_and_encounter() {
  local status="${1:-active}"
  local patient_id encounter_id mrn

  if ! psql_available; then
    return 1
  fi

  patient_id="$(new_uuid)"
  encounter_id="$(new_uuid)"
  # mrn is varchar(20); use a short unique prefix + uuid fragment
  mrn="P6-$(echo "$patient_id" | tr -d '-' | cut -c1-13)"

  psql_query "
    INSERT INTO clinical.patients
      (id, mrn, full_name, no_known_allergies, created_at, updated_at)
    VALUES ('$patient_id', '$mrn', 'Phase 6 Patient', false, NOW(), NOW());
  " >/dev/null

  psql_query "
    INSERT INTO clinical.encounters
      (id, patient_id, department, room_bed, admission_reason, status,
       admission_date, created_at, updated_at)
    VALUES (
      '$encounter_id', '$patient_id', 'Internal Medicine', 'IM-201A',
      'Observation', '$status', NOW(), NOW(), NOW()
    );
  " >/dev/null

  printf '%s|%s' "$patient_id" "$encounter_id"
}

# Creates clinical.patients only. Prints patient_id.
create_patient() {
  local patient_id mrn

  if ! psql_available; then
    return 1
  fi

  patient_id="$(new_uuid)"
  mrn="P6-$(echo "$patient_id" | tr -d '-' | cut -c1-13)"

  psql_query "
    INSERT INTO clinical.patients
      (id, mrn, full_name, no_known_allergies, created_at, updated_at)
    VALUES ('$patient_id', '$mrn', 'Phase 6 Patient', false, NOW(), NOW());
  " >/dev/null

  printf '%s' "$patient_id"
}

live_capture_record() {
  local token="$1"
  local encounter_id="$2"
  local body="$3"
  json_post "$API_URL/api/v1/live-capture/encounters/$encounter_id/observations" \
    "$body" "$token"
}

live_capture_open_encounter() {
  local token="$1"
  local body="$2"
  json_post "$API_URL/api/v1/live-capture/encounters" "$body" "$token"
}

normal_observations_body() {
  local password_confirm="${1:-password}"
  jq -nc \
    --arg recorded_at "$RECORDED_AT" \
    --arg password "$password_confirm" \
    '{
      observations: [
        {observationCode: "HEART_RATE", value: 72, unit: "bpm", recordedAt: $recorded_at, note: null},
        {observationCode: "TEMP_C", value: 36.8, unit: "C", recordedAt: $recorded_at, note: null},
        {observationCode: "SPO2", value: 98, unit: "%", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: true,
      passwordConfirm: $password
    }'
}

test_schema_alert_tables() {
  section "1. Schema — clinical.alert_thresholds and clinical.clinical_alerts"

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

  local threshold_table alert_table
  threshold_table="$(psql_query "
    SELECT count(*)
    FROM information_schema.tables
    WHERE table_schema = 'clinical' AND table_name = 'alert_thresholds';
  ")"
  alert_table="$(psql_query "
    SELECT count(*)
    FROM information_schema.tables
    WHERE table_schema = 'clinical' AND table_name = 'clinical_alerts';
  ")"

  if [[ "$threshold_table" == "1" ]]; then
    pass "clinical.alert_thresholds table exists"
  else
    fail "clinical.alert_thresholds table exists"
  fi

  if [[ "$alert_table" == "1" ]]; then
    pass "clinical.clinical_alerts table exists"
  else
    fail "clinical.clinical_alerts table exists"
  fi
}

test_swagger_live_capture_routes() {
  section "2. API surface — live-capture routes in swagger"

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

  if jq -e '.paths["/api/v1/live-capture/encounters/{encounterId}/observations"].post' \
      <<<"$swagger_paths" >/dev/null; then
    pass "POST /api/v1/live-capture/encounters/{encounterId}/observations documented"
  else
    fail "POST /api/v1/live-capture/encounters/{encounterId}/observations documented"
  fi

  if jq -e '.paths["/api/v1/live-capture/encounters"].post' \
      <<<"$swagger_paths" >/dev/null; then
    pass "POST /api/v1/live-capture/encounters documented"
  else
    fail "POST /api/v1/live-capture/encounters documented"
  fi
}

test_attestation_wrong_password() {
  section "3. Attestation — wrong password returns 422 PASSWORD_CONFIRM_INVALID"

  local clinician_token ids encounter_id body result http_code error_code

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for wrong-password test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(normal_observations_body wrong)"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"
  http_code="$(extract_status_code "$result")"
  error_code="$(extract_error_code "$result")"

  if [[ "$http_code" == "422" && "$error_code" == "PASSWORD_CONFIRM_INVALID" ]]; then
    pass "wrong password returns 422 PASSWORD_CONFIRM_INVALID"
  else
    fail "wrong password returns 422 PASSWORD_CONFIRM_INVALID (http=$http_code code=${error_code:-<none>})"
  fi
}

test_attestation_non_clinician() {
  section "4. Authorization — entry clerk returns 403 Forbidden"

  local clerk_token ids encounter_id body http_result

  clerk_token="$(extract_data_field "$(login entry1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for non-clinician test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(normal_observations_body)"
  http_result="$(http_code -X POST \
    "$API_URL/api/v1/live-capture/encounters/$encounter_id/observations" \
    -H "Authorization: Bearer $clerk_token" \
    -H 'Content-Type: application/json' \
    -d "$body")"

  if [[ "$http_result" == "403" ]]; then
    pass "entry clerk receives 403 Forbidden"
  else
    fail "entry clerk receives 403 Forbidden (http=$http_result)"
  fi
}

test_attestation_false() {
  section "5. Attestation — false attestation returns 422 ATTESTATION_REQUIRED"

  local clinician_token ids encounter_id body result http_code error_code

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for false-attestation test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(jq -nc \
    --arg recorded_at "$RECORDED_AT" \
    '{
      observations: [
        {observationCode: "HEART_RATE", value: 72, unit: "bpm", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: false,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"
  http_code="$(extract_status_code "$result")"
  error_code="$(extract_error_code "$result")"

  if [[ "$http_code" == "422" && "$error_code" == "ATTESTATION_REQUIRED" ]]; then
    pass "false attestation returns 422 ATTESTATION_REQUIRED"
  else
    fail "false attestation returns 422 ATTESTATION_REQUIRED (http=$http_code code=${error_code:-<none>})"
  fi
}

test_normal_promotion() {
  section "6. Synchronous promotion — normal vitals promoted with no alert"

  local clinician_token ids encounter_id body result obs_count alert_count live_obs_id batch_id

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for normal promotion test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(normal_observations_body)"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"

  if [[ "$(jq -er '.success' <<<"$result")" != "true" ]]; then
    fail "normal vitals live capture returns success"
    return
  fi

  obs_count="$(extract_data_field "$result" 'observations | length')"
  alert_count="$(extract_data_field "$result" criticalAlertCount)"
  live_obs_id="$(jq -er '.data.observations[0].liveObservationId' <<<"$result")"
  batch_id="$(extract_data_field "$result" batchId)"

  if [[ "$obs_count" == "3" && "$alert_count" == "0" ]]; then
    pass "response has 3 observations and criticalAlertCount=0"
  else
    fail "response has 3 observations and criticalAlertCount=0 (obs=$obs_count alerts=$alert_count)"
  fi

  SHARED_LIVE_OBS_ID="$live_obs_id"
  SHARED_BATCH_ID="$batch_id"

  if ! psql_available; then
    log "  SKIP: DB checks for normal promotion"
    return
  fi

  local obs_source batch_status batch_track batch_attestation
  obs_source="$(psql_query "
    SELECT source FROM clinical.observations WHERE id = '$live_obs_id';
  ")"
  batch_status="$(psql_query "
    SELECT status FROM digitization_batches WHERE id = '$batch_id';
  ")"
  batch_track="$(psql_query "
    SELECT track FROM digitization_batches WHERE id = '$batch_id';
  ")"
  batch_attestation="$(psql_query "
    SELECT clinician_attestation FROM digitization_batches WHERE id = '$batch_id';
  ")"

  if [[ "$obs_source" == "live_capture" ]]; then
    pass "clinical.observations.source is live_capture"
  else
    fail "clinical.observations.source is live_capture (got: ${obs_source:-<none>})"
  fi

  if [[ "$batch_status" == "PROMOTED" && "$batch_track" == "LIVE_CAPTURE" && "$batch_attestation" == "t" ]]; then
    pass "digitization_batches: PROMOTED, LIVE_CAPTURE, clinician_attestation=true"
  else
    fail "digitization_batches state (status=$batch_status track=$batch_track attestation=$batch_attestation)"
  fi
}

test_critical_low_potassium() {
  section "7. Critical alert — potassium 2.1 mEq/L fires CRITICAL_LOW synchronously"

  seed_potassium_threshold

  local clinician_token ids encounter_id body result alert_count severity bound threshold message alert_id

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for critical-low test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(jq -nc \
    --arg recorded_at "$RECORDED_AT_CRITICAL" \
    '{
      observations: [
        {
          observationCode: "POTASSIUM_MEQ_L",
          value: 2.1,
          unit: "mEq/L",
          recordedAt: $recorded_at,
          note: "Bedside iSTAT result"
        }
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"

  if [[ "$(jq -er '.success' <<<"$result")" != "true" ]]; then
    fail "critical potassium live capture returns success"
    return
  fi

  alert_count="$(extract_data_field "$result" criticalAlertCount)"
  severity="$(jq -er '.data.observations[0].criticalAlert.severity' <<<"$result")"
  bound="$(jq -er '.data.observations[0].criticalAlert.thresholdBound' <<<"$result")"
  threshold="$(jq -er '.data.observations[0].criticalAlert.thresholdValue' <<<"$result")"
  message="$(jq -er '.data.observations[0].criticalAlert.message' <<<"$result")"
  alert_id="$(jq -er '.data.observations[0].criticalAlert.alertId' <<<"$result")"

  SHARED_ALERT_ID="$alert_id"

  if [[ "$alert_count" == "1" && "$severity" == "CRITICAL" && "$bound" == "CRITICAL_LOW" &&
        "$threshold" == 2.5* && "$message" == *"below critical low"* ]]; then
    pass "response includes inline CRITICAL_LOW alert for potassium 2.1"
  else
    fail "response includes inline CRITICAL_LOW alert (count=$alert_count severity=$severity bound=$bound)"
  fi

  if ! psql_available; then
    log "  SKIP: DB checks for critical alert"
    return
  fi

  local db_severity db_status db_details
  db_severity="$(psql_query "
    SELECT severity FROM clinical.clinical_alerts WHERE id = '$alert_id';
  ")"
  db_status="$(psql_query "
    SELECT status FROM clinical.clinical_alerts WHERE id = '$alert_id';
  ")"
  db_details="$(psql_query "
    SELECT details FROM clinical.clinical_alerts WHERE id = '$alert_id';
  ")"

  if [[ "$db_severity" == "CRITICAL" && "$db_status" == "OPEN" && "$db_details" == *"below critical low"* ]]; then
    pass "clinical.clinical_alerts row committed (CRITICAL, OPEN)"
  else
    fail "clinical.clinical_alerts row (severity=$db_severity status=$db_status)"
  fi
}

test_critical_high_potassium() {
  section "8. Critical alert — potassium 7.2 mEq/L fires CRITICAL_HIGH"

  seed_potassium_threshold

  local clinician_token ids encounter_id body result bound threshold message

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for critical-high test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(jq -nc \
    --arg recorded_at "$RECORDED_AT_CRITICAL" \
    '{
      observations: [
        {
          observationCode: "POTASSIUM_MEQ_L",
          value: 7.2,
          unit: "mEq/L",
          recordedAt: $recorded_at,
          note: null
        }
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"

  bound="$(jq -er '.data.observations[0].criticalAlert.thresholdBound // empty' <<<"$result")"
  threshold="$(jq -er '.data.observations[0].criticalAlert.thresholdValue // empty' <<<"$result")"
  message="$(jq -er '.data.observations[0].criticalAlert.message // empty' <<<"$result")"

  if [[ "$bound" == "CRITICAL_HIGH" && "$threshold" == 6.5* && "$message" == *"above critical high"* ]]; then
    pass "response includes CRITICAL_HIGH alert for potassium 7.2"
  else
    fail "response includes CRITICAL_HIGH alert (bound=$bound threshold=$threshold)"
  fi
}

test_mixed_batch() {
  section "9. Mixed batch — only critical observation gets alert"

  seed_potassium_threshold

  local clinician_token ids encounter_id body result alert_count hr_alert k_alert temp_alert

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for mixed-batch test"
    return
  }
  encounter_id="${ids#*|}"

  body="$(jq -nc \
    --arg recorded_at "$RECORDED_AT_CRITICAL" \
    '{
      observations: [
        {observationCode: "HEART_RATE", value: 80, unit: "bpm", recordedAt: $recorded_at, note: null},
        {observationCode: "POTASSIUM_MEQ_L", value: 2.1, unit: "mEq/L", recordedAt: $recorded_at, note: null},
        {observationCode: "TEMP_C", value: 37.0, unit: "C", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"

  alert_count="$(extract_data_field "$result" criticalAlertCount)"
  hr_alert="$(jq -er '.data.observations[0].criticalAlert // "null"' <<<"$result")"
  k_alert="$(jq -er '.data.observations[1].criticalAlert.severity // empty' <<<"$result")"
  temp_alert="$(jq -er '.data.observations[2].criticalAlert // "null"' <<<"$result")"

  if [[ "$alert_count" == "1" && "$hr_alert" == "null" && "$k_alert" == "CRITICAL" && "$temp_alert" == "null" ]]; then
    pass "mixed batch: single CRITICAL alert on potassium only"
  else
    fail "mixed batch alert shape (count=$alert_count hr=$hr_alert k=$k_alert temp=$temp_alert)"
  fi
}

test_outbox_events() {
  section "10. Outbox — observation.recorded and alert.generated events"

  if [[ -z "$SHARED_LIVE_OBS_ID" || -z "$SHARED_ALERT_ID" ]]; then
    fail "outbox checks require normal promotion and critical-low tests first"
    return
  fi

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

  local obs_event_type alert_event_type
  obs_event_type="$(psql_query "
    SELECT event_type
    FROM clinical.outbox_events
    WHERE aggregate_id = '$SHARED_LIVE_OBS_ID'
    LIMIT 1;
  ")"
  alert_event_type="$(psql_query "
    SELECT event_type
    FROM clinical.outbox_events
    WHERE aggregate_id = '$SHARED_ALERT_ID'
    LIMIT 1;
  ")"

  if [[ "$obs_event_type" == "observation.recorded" ]]; then
    pass "outbox event observation.recorded written for live observation"
  else
    fail "outbox event observation.recorded (got: ${obs_event_type:-<none>})"
  fi

  if [[ "$alert_event_type" == "alert.generated" ]]; then
    pass "outbox event alert.generated written for clinical alert"
  else
    fail "outbox event alert.generated (got: ${alert_event_type:-<none>})"
  fi

  local obs_payload_source
  obs_payload_source="$(psql_query "
    SELECT payload_json::text
    FROM clinical.outbox_events
    WHERE aggregate_id = '$SHARED_LIVE_OBS_ID'
    LIMIT 1;
  ")"
  if [[ "$obs_payload_source" == *"live_capture"* ]]; then
    pass "observation outbox payload includes source live_capture"
  else
    fail "observation outbox payload includes source live_capture"
  fi
}

test_open_encounter_with_vitals() {
  section "11. Open encounter + vitals — outpatient workflow"

  local clinician_token patient_id body result encounter_id obs_count department

  if ! psql_available; then
    log "  SKIP: open-encounter test requires DB for patient setup"
    return
  fi

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  patient_id="$(create_patient)" || {
    fail "setup patient for open-encounter test"
    return
  }

  body="$(jq -nc \
    --arg patient_id "$patient_id" \
    --arg recorded_at "$RECORDED_AT_OUTPATIENT" \
    '{
      patientId: $patient_id,
      department: "Outpatient Clinic",
      roomBed: "OPD-3",
      admissionReason: "Follow-up",
      observations: [
        {observationCode: "HEART_RATE", value: 68, unit: "bpm", recordedAt: $recorded_at, note: null},
        {observationCode: "BP_SYSTOLIC", value: 120, unit: "mmHg", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_open_encounter "$clinician_token" "$body")"

  if [[ "$(jq -er '.success' <<<"$result")" != "true" ]]; then
    fail "open encounter with vitals returns success"
    return
  fi

  encounter_id="$(extract_data_field "$result" encounterId)"
  obs_count="$(extract_data_field "$result" 'observations | length')"

  if [[ "$obs_count" == "2" && -n "$encounter_id" ]]; then
    pass "open encounter response has encounterId and 2 observations"
  else
    fail "open encounter response (encounterId=$encounter_id obs=$obs_count)"
    return
  fi

  department="$(psql_query "
    SELECT department FROM clinical.encounters WHERE id = '$encounter_id';
  ")"
  if [[ "$department" == "Outpatient Clinic" ]]; then
    pass "new encounter department is Outpatient Clinic"
  else
    fail "new encounter department is Outpatient Clinic (got: ${department:-<none>})"
  fi
}

test_duplicate_active_encounter() {
  section "12. Validation — duplicate active encounter returns 409 ACTIVE_ENCOUNTER_EXISTS"

  local clinician_token ids patient_id body result http_code error_code

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient with active encounter for duplicate test"
    return
  }
  patient_id="${ids%%|*}"

  body="$(jq -nc \
    --arg patient_id "$patient_id" \
    --arg recorded_at "$RECORDED_AT" \
    '{
      patientId: $patient_id,
      department: "Emergency Department",
      roomBed: "ER-1",
      admissionReason: "Chest pain",
      observations: [
        {observationCode: "HEART_RATE", value: 90, unit: "bpm", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_open_encounter "$clinician_token" "$body")"
  http_code="$(extract_status_code "$result")"
  error_code="$(extract_error_code "$result")"

  if [[ "$http_code" == "409" && "$error_code" == "ACTIVE_ENCOUNTER_EXISTS" ]]; then
    pass "second active encounter returns 409 ACTIVE_ENCOUNTER_EXISTS"
  else
    fail "second active encounter returns 409 ACTIVE_ENCOUNTER_EXISTS (http=$http_code code=${error_code:-<none>})"
  fi
}

test_discharged_encounter() {
  section "13. Validation — discharged encounter returns 409 ENCOUNTER_NOT_ACTIVE"

  local clinician_token ids encounter_id body result http_code error_code

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter discharged)" || {
    fail "setup discharged encounter"
    return
  }
  encounter_id="${ids#*|}"

  body="$(jq -nc \
    --arg recorded_at "$RECORDED_AT" \
    '{
      observations: [
        {observationCode: "HEART_RATE", value: 72, unit: "bpm", recordedAt: $recorded_at, note: null}
      ],
      clinicianAttestation: true,
      passwordConfirm: "password"
    }')"
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"
  http_code="$(extract_status_code "$result")"
  error_code="$(extract_error_code "$result")"

  if [[ "$http_code" == "409" && "$error_code" == "ENCOUNTER_NOT_ACTIVE" ]]; then
    pass "discharged encounter returns 409 ENCOUNTER_NOT_ACTIVE"
  else
    fail "discharged encounter returns 409 ENCOUNTER_NOT_ACTIVE (http=$http_code code=${error_code:-<none>})"
  fi
}

test_empty_observations() {
  section "14. Validation — empty observations returns 422 EMPTY_OBSERVATIONS"

  local clinician_token ids encounter_id body result http_code error_code

  clinician_token="$(extract_data_field "$(login clinician1)" token)"
  ids="$(create_patient_and_encounter)" || {
    fail "setup patient/encounter for empty-observations test"
    return
  }
  encounter_id="${ids#*|}"

  body='{"observations":[],"clinicianAttestation":true,"passwordConfirm":"password"}'
  result="$(live_capture_record "$clinician_token" "$encounter_id" "$body")"
  http_code="$(extract_status_code "$result")"
  error_code="$(extract_error_code "$result")"

  if [[ "$http_code" == "422" && "$error_code" == "EMPTY_OBSERVATIONS" ]]; then
    pass "empty observations returns 422 EMPTY_OBSERVATIONS"
  else
    fail "empty observations returns 422 EMPTY_OBSERVATIONS (http=$http_code code=${error_code:-<none>})"
  fi
}

test_digitization_events() {
  section "15. Audit trail — live_capture_attested and promoted digitization events"

  if [[ -z "$SHARED_BATCH_ID" ]]; then
    fail "digitization event check requires normal promotion test first"
    return
  fi

  if ! psql_available; then
    log "  SKIP: digitization event DB checks"
    return
  fi

  local attested_count promoted_count
  attested_count="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$SHARED_BATCH_ID'
      AND event_type = 'live_capture_attested';
  ")"
  promoted_count="$(psql_query "
    SELECT count(*)
    FROM digitization_events
    WHERE batch_id = '$SHARED_BATCH_ID'
      AND event_type = 'promoted';
  ")"

  if [[ "$attested_count" == "1" && "$promoted_count" == "1" ]]; then
    pass "batch has live_capture_attested and promoted digitization events"
  else
    fail "digitization events (attested=$attested_count promoted=$promoted_count)"
  fi
}

test_integration_tests() {
  section "16. Integration tests — LiveCaptureIntegrationTests"

  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~LiveCaptureIntegrationTests" \
    --verbosity minimal 2>&1)"
  test_exit=$?

  if [[ "$test_exit" -eq 0 ]] && grep -q "Passed!" <<<"$test_output"; then
    pass "LiveCaptureIntegrationTests pass (dotnet test)"
  else
    fail "LiveCaptureIntegrationTests pass (dotnet test)"
    log "$test_output"
  fi
}

main() {
  require_cmd curl
  require_cmd jq
  require_cmd docker

  log "VigilCare Records — Phase 6 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_alert_tables
  test_swagger_live_capture_routes
  test_attestation_wrong_password
  test_attestation_non_clinician
  test_attestation_false
  test_normal_promotion
  test_critical_low_potassium
  test_critical_high_potassium
  test_mixed_batch
  test_outbox_events
  test_open_encounter_with_vitals
  test_duplicate_active_encounter
  test_discharged_encounter
  test_empty_observations
  test_digitization_events
  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 6 verification checks passed."
}

main "$@"
