#!/usr/bin/env bash
# Create an immutable Staging DAX-retained safety receipt from live readback.
set -euo pipefail

readonly RECEIPT_ID="${HELIX_STAGING_DAX_RECEIPT_ID:-}"
readonly COMPOSE_FILE='/opt/helix/staging/docker-compose.staging.yml'
readonly RUNTIME_ENV='/etc/helix/staging/runtime.env'
readonly DEPLOY_ENV='/etc/helix/staging/deploy.env'
readonly RELEASE_RECEIPT='/opt/helix/releases/staging-current.json'

[[ "$RECEIPT_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]] || {
  echo 'ERROR: HELIX_STAGING_DAX_RECEIPT_ID is invalid.' >&2
  exit 2
}

set -a
# shellcheck source=/dev/null
source "$DEPLOY_ENV"
set +a

readonly SOURCE_RECEIPT="$STAGING_SANITIZATION_RECEIPT"
readonly OUTPUT_RECEIPT="/opt/helix/releases/staging-dax-retained-${RECEIPT_ID}.json"
[[ -f "$SOURCE_RECEIPT" ]] || { echo 'ERROR: source safety receipt is absent.' >&2; exit 3; }
[[ -f "$RELEASE_RECEIPT" ]] || { echo 'ERROR: release receipt is absent.' >&2; exit 4; }
[[ ! -e "$OUTPUT_RECEIPT" ]] || { echo 'ERROR: retained receipt already exists.' >&2; exit 5; }
jq -e '
  .environment == "staging" and
  (.sanitized_at | type == "string" and length > 0) and
  (.sanitizer_source_sha | type == "string" and test("^[0-9a-f]{40}$")) and
  (.database.sanitized_from_snapshot == true) and
  (.effects.scheduled_jobs_disabled == true) and
  (.effects.identity_targets_disabled == true) and
  (.effects.tokens_revoked == true) and
  (.effects.people_pseudonymized == true) and
  (.effects.synthetic_cohort_seeded == true)
' "$SOURCE_RECEIPT" >/dev/null || {
  echo 'ERROR: source receipt does not prove the retained sanitizer boundary.' >&2
  exit 10
}

export STAGING_SERVER_IMAGE="$(jq -er '.server_image' "$RELEASE_RECEIPT")"
export STAGING_WEB_IMAGE="$(jq -er '.web_image' "$RELEASE_RECEIPT")"
export STAGING_RUNTIME_ENV="$RUNTIME_ENV"
services="$(docker compose --project-name helix-staging --env-file "$RUNTIME_ENV" \
  -f "$COMPOSE_FILE" config --services | LC_ALL=C sort | tr '\n' ' ')"
[[ "$services" == 'backend frontend redis ' ]] || {
  echo "ERROR: unexpected Staging services: ${services}" >&2
  exit 6
}

schema_current="$(docker compose --project-name helix-staging --env-file "$RUNTIME_ENV" \
  -f "$COMPOSE_FILE" exec -T --workdir /app -e PYTHONPATH=/app backend \
  /app/.venv/bin/alembic current | awk '/^[0-9]+/ {print $1}')"
[[ "$schema_current" =~ ^[0-9]+$ ]] || { echo 'ERROR: live schema is invalid.' >&2; exit 7; }

live_readback="$(
  docker compose --project-name helix-staging --env-file "$RUNTIME_ENV" \
    -f "$COMPOSE_FILE" exec -T --workdir /app -e PYTHONPATH=/app backend \
    /app/.venv/bin/python - <<'PY'
import asyncio
import json

from sqlalchemy import text

from app.core.database import async_session_factory, engine


def _is_false(value: object) -> bool:
    return str(value).strip().casefold() in {"false", "0", "none", "null", ""}


async def main() -> None:
    async with async_session_factory() as db:
        integration_rows = list((await db.execute(text(
            "SELECT enabled, "
            "(secrets_encrypted IS NOT NULL AND secrets_nonce IS NOT NULL) AS has_secret, "
            "JSON_UNQUOTE(JSON_EXTRACT(config_public, '$.apply_mode_enabled')) AS apply_mode_enabled, "
            "JSON_UNQUOTE(JSON_EXTRACT(config_public, '$.personal_apply_enabled')) AS personal_apply_enabled, "
            "JSON_UNQUOTE(JSON_EXTRACT(config_public, '$.service_account_apply_enabled')) AS service_account_apply_enabled "
            "FROM integrations WHERE kind = 'data_access' AND provider = 'bigquery'"
        ))).mappings())
        enabled = [row for row in integration_rows if bool(row["enabled"])]
        scheduled_jobs_enabled = int((await db.execute(text(
            "SELECT COUNT(*) FROM scheduled_jobs WHERE enabled = 1"
        ))).scalar_one())
        identity_targets_enabled = int((await db.execute(text(
            "SELECT COUNT(*) FROM identity_sync_targets WHERE enabled = 1"
        ))).scalar_one())
        catalog_dataset_count = int((await db.execute(text(
            "SELECT COUNT(*) FROM data_access_resources "
            "WHERE resource_type = 'dataset' AND status = 'active'"
        ))).scalar_one())
        semantic_counts = {}
        for label, table in {
            "requests": "data_access_requests",
            "request_items": "data_access_request_items",
            "bindings": "data_access_bindings",
            "compiled_effects": "data_access_compiled_effects",
            "effect_runs": "data_access_effect_runs",
        }.items():
            semantic_counts[label] = int((await db.execute(text(
                f"SELECT COUNT(*) FROM {table}"
            ))).scalar_one())

    assert len(enabled) == 1, "exactly one enabled data_access/bigquery integration is required"
    integration = enabled[0]
    assert bool(integration["has_secret"]), "metadata credential is absent"
    assert _is_false(integration["apply_mode_enabled"]), "apply mode is enabled"
    assert _is_false(integration["personal_apply_enabled"]), "personal apply is enabled"
    assert _is_false(integration["service_account_apply_enabled"]), "service-account apply is enabled"
    assert scheduled_jobs_enabled == 0, "scheduled jobs are enabled"
    assert identity_targets_enabled == 0, "identity targets are enabled"
    assert catalog_dataset_count >= 1, "retained DAX catalog is empty"

    print(json.dumps({
        "retained_for_user_validation": True,
        "worker_enabled": False,
        "metadata_only_credential": True,
        "enabled_integration_count": 1,
        "catalog_dataset_count": catalog_dataset_count,
        "apply_mode_enabled": False,
        "personal_apply_enabled": False,
        "service_account_apply_enabled": False,
        "scheduled_jobs_enabled": scheduled_jobs_enabled,
        "identity_targets_enabled": identity_targets_enabled,
        "semantic_counts": semantic_counts,
    }, sort_keys=True))
    await engine.dispose()


asyncio.run(main())
PY
)"
jq -e '
  .retained_for_user_validation == true and .worker_enabled == false and
  .metadata_only_credential == true and .enabled_integration_count == 1 and
  .catalog_dataset_count >= 1 and .apply_mode_enabled == false and
  .personal_apply_enabled == false and .service_account_apply_enabled == false and
  .scheduled_jobs_enabled == 0 and .identity_targets_enabled == 0
' <<<"$live_readback" >/dev/null

source_revision="$(jq -er '.database.alembic_revision' "$SOURCE_RECEIPT")"
# This helper re-reads live safety state at an UNCHANGED schema; it copies the
# source receipt's `database` block verbatim and therefore cannot attest a
# revision the sanitizer never saw. After a migration it must refuse: the only
# way forward is a full sanitizer run against the new revision.
[[ "$source_revision" == "$schema_current" ]] || {
  echo "ERROR: source receipt schema ${source_revision} != live ${schema_current}." >&2
  echo "Re-attestation cannot advance a revision. Staging has been migrated since" >&2
  echo "${SOURCE_RECEIPT} was written, so re-run the Staging Branch sanitizer against" >&2
  echo "schema ${schema_current} and point STAGING_SANITIZATION_RECEIPT at its new" >&2
  echo "staging-sanitization-*.json receipt. See docs/deployment/staging.md." >&2
  exit 8
}
source_git_sha="$(jq -er '.git_sha' "$RELEASE_RECEIPT")"
[[ "$source_git_sha" =~ ^[0-9a-f]{40}$ ]] || { echo 'ERROR: release SHA is invalid.' >&2; exit 9; }
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
receipt_tmp="${OUTPUT_RECEIPT}.tmp"
jq -n \
  --slurpfile source "$SOURCE_RECEIPT" \
  --arg source_git_sha "$source_git_sha" \
  --arg retained_at "$now" --arg live_readback_at "$now" \
  --argjson dax_safety "$live_readback" \
  '{
    schema_version: 2,
    environment: "staging",
    receipt_type: "dax_retained_validation",
    retention_policy: "preserve_across_deploys",
    cleanup_required_before_next_deploy: false,
    sanitized_at: $source[0].sanitized_at,
    sanitizer_source_sha: $source[0].sanitizer_source_sha,
    retained_at: $retained_at,
    live_readback_at: $live_readback_at,
    source_git_sha: $source_git_sha,
    database: $source[0].database,
    effects: {
      scheduled_jobs_disabled: true,
      integrations_disabled: false,
      identity_targets_disabled: true,
      provider_credentials_removed: false,
      tokens_revoked: true,
      provider_locators_removed: false,
      people_pseudonymized: true,
      synthetic_cohort_seeded: true
    },
    dax_safety: $dax_safety
  }' > "$receipt_tmp"
chmod 0644 "$receipt_tmp"
mv "$receipt_tmp" "$OUTPUT_RECEIPT"
printf 'retained_receipt=%s\n' "$OUTPUT_RECEIPT"
jq '{schema_version,environment,receipt_type,retention_policy,cleanup_required_before_next_deploy,source_git_sha,database:{alembic_revision:.database.alembic_revision},effects,dax_safety}' "$OUTPUT_RECEIPT"
