name: Rebuild audit hash chain via postgres superuser (F1 cleanup)

# Closes deferred F1 item from docs/incidents/2026-05-29-disk-full-pg-recovery.md §4.1.
# Sequential hash recomputation в plpgsql DO-блоке через sudo -u postgres psql.
# Identical алгоритм с trigger audit_chain_hash() (post-F1 advisory-lock version),
# но применённый к existing rows.
#
# Использование:
#   gh workflow run f1-rebuild-via-superuser.yml \
#     -f partition=activity_log_y2026_m05 -f from_id=599 -f confirm_apply=true
#
# Safety:
#   - Partition name whitelist (только заранее известные сломанные партиции).
#   - dry_run=true mode показывает count + anchor prev_hash без UPDATE.
#   - Trigger audit_chain_hash отключён через SET LOCAL session_replication_role=replica
#     (постоянный disable невозможен — после COMMIT триггер опять активен).
#   - audit_block_mutation также подавлен через session_replication_role=replica.

on:
  workflow_dispatch:
    inputs:
      partition:
        description: 'Partition name (whitelist: activity_log_y2026_m05, balance_transactions_y2026_m05)'
        required: true
        type: string
      from_id:
        description: 'First broken id (rebuild from here onward)'
        required: true
        type: string
      dry_run:
        description: 'Dry-run (показать count + anchor без UPDATE)'
        required: false
        default: 'false'
        type: boolean
      confirm_apply:
        description: 'Подтверждаю rebuild на проде (требуется если dry_run=false)'
        required: false
        default: 'false'
        type: boolean

jobs:
  rebuild:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    env:
      LIDERRA_HOST: 111.88.246.137
      LIDERRA_USER: ubuntu
      PARTITION: ${{ github.event.inputs.partition }}
      FROM_ID: ${{ github.event.inputs.from_id }}
      DRY_RUN: ${{ github.event.inputs.dry_run }}
      CONFIRM: ${{ github.event.inputs.confirm_apply }}

    steps:
      - name: Validate inputs
        run: |
          set -euo pipefail

          # Whitelist partition names (защита от arbitrary table names)
          ALLOWED='^(activity_log_y2026_m05|balance_transactions_y2026_m05)$'
          if ! [[ "$PARTITION" =~ $ALLOWED ]]; then
            echo "::error::partition '$PARTITION' not in whitelist: $ALLOWED"
            exit 1
          fi

          # from_id is positive integer
          if ! [[ "$FROM_ID" =~ ^[0-9]+$ ]]; then
            echo "::error::from_id must be positive integer, got '$FROM_ID'"
            exit 1
          fi

          if [[ "$DRY_RUN" != "true" && "$CONFIRM" != "true" ]]; then
            echo "::error::Either dry_run=true OR confirm_apply=true must be set"
            exit 1
          fi

          echo "Inputs OK: partition=$PARTITION, from_id=$FROM_ID, dry_run=$DRY_RUN, confirm_apply=$CONFIRM"

      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy
          chmod 600 ~/.ssh/liderra_deploy
          ssh-keyscan -H ${{ env.LIDERRA_HOST }} >> ~/.ssh/known_hosts 2>/dev/null

      - name: Run rebuild on prod
        run: |
          ssh -i ~/.ssh/liderra_deploy ${{ env.LIDERRA_USER }}@${{ env.LIDERRA_HOST }} \
            "PARTITION='$PARTITION' FROM_ID='$FROM_ID' DRY_RUN='$DRY_RUN' bash -s" <<'REMOTE' | tee /tmp/f1-rebuild.log
          set +e

          echo "=== 1. Anchor + count preview ==="
          sudo -u postgres psql -d liderra -v ON_ERROR_STOP=1 <<SQL
          \set partition $PARTITION
          \set from_id $FROM_ID

          -- Anchor: log_hash of row right BEFORE from_id (=> prev_hash for from_id)
          SELECT
            (SELECT id FROM :"partition" WHERE id < :from_id ORDER BY id DESC LIMIT 1) AS anchor_id,
            encode((SELECT log_hash FROM :"partition" WHERE id < :from_id ORDER BY id DESC LIMIT 1), 'hex') AS anchor_log_hash,
            (SELECT COUNT(*) FROM :"partition" WHERE id >= :from_id) AS rows_to_rebuild,
            (SELECT MIN(id) FROM :"partition" WHERE id >= :from_id) AS first_id,
            (SELECT MAX(id) FROM :"partition" WHERE id >= :from_id) AS last_id;
          SQL
          PRE_RC=$?
          if [[ $PRE_RC -ne 0 ]]; then
            echo "::error::Pre-check failed (RC=$PRE_RC)"
            exit $PRE_RC
          fi

          if [[ "$DRY_RUN" == "true" ]]; then
            echo
            echo "=== DRY RUN — no changes applied ==="
            exit 0
          fi

          echo
          echo "=== 2. APPLY: rebuild hash chain on $PARTITION from id=$FROM_ID ==="
          # Canonical algorithm (mirrors app/app/Console/Commands/AuditRebuildChain.php):
          # builds explicit ROW(col1, col2, ..., NULL::bytea on log_hash position, ..., coln)::text::bytea
          # so hash matches what audit:verify-chains computes (which uses same COLUMN_CONFIG).
          case "$PARTITION" in
            activity_log_*)
              ROW_EXPR="ROW(t.id, t.tenant_id, t.user_id, t.deal_id, t.event, t.old_value, t.new_value, t.context, t.ip_address, t.user_agent, NULL::bytea, t.created_at)"
              ;;
            balance_transactions_*)
              ROW_EXPR="ROW(t.id, t.tenant_id, t.type, t.amount_rub, t.amount_leads, t.balance_rub_after, t.balance_leads_after, t.description, t.related_type, t.related_id, t.user_id, t.admin_user_id, NULL::bytea, t.created_at)"
              ;;
            *)
              echo "::error::Unknown partition family — add ROW_EXPR mapping"
              exit 1
              ;;
          esac
          echo "Using ROW expression: $ROW_EXPR"

          sudo -u postgres psql -d liderra -v ON_ERROR_STOP=1 <<SQL
          BEGIN;
          SET LOCAL session_replication_role = 'replica';

          DO \$rebuild\$
          DECLARE
              cur_id    BIGINT;
              prev_hash BYTEA;
              new_hash  BYTEA;
              cnt       INTEGER := 0;
              partition_name TEXT := '$PARTITION';
              start_id  BIGINT := $FROM_ID;
              row_expr  TEXT := '$ROW_EXPR';
          BEGIN
              EXECUTE format(
                  'SELECT log_hash FROM %I WHERE id < \$1 ORDER BY id DESC LIMIT 1',
                  partition_name
              )
              INTO prev_hash
              USING start_id;

              RAISE NOTICE 'Anchor prev_hash: %', COALESCE(encode(prev_hash, 'hex'), '<NULL — start of chain>');

              FOR cur_id IN
                  EXECUTE format(
                      'SELECT id FROM %I WHERE id >= \$1 ORDER BY id',
                      partition_name
                  )
                  USING start_id
              LOOP
                  -- Compute new_hash with explicit ROW(...) expression (canonical, matches verify-chains)
                  EXECUTE format(
                      'SELECT digest(COALESCE(\$1, ''''::bytea) || %s::text::bytea, ''sha256'') FROM %I t WHERE id = \$2',
                      row_expr, partition_name
                  )
                  INTO new_hash
                  USING prev_hash, cur_id;

                  EXECUTE format('UPDATE %I SET log_hash = \$1 WHERE id = \$2', partition_name)
                      USING new_hash, cur_id;

                  prev_hash := new_hash;
                  cnt := cnt + 1;
              END LOOP;

              RAISE NOTICE 'Rebuilt % rows. Last log_hash: %', cnt, encode(prev_hash, 'hex');
          END
          \$rebuild\$;

          COMMIT;
          SQL
          APPLY_RC=$?

          echo
          echo "=== 3. Verify: no NULL log_hash в обновлённых строках ==="
          sudo -u postgres psql -d liderra <<SQL
          \set partition $PARTITION
          \set from_id $FROM_ID
          SELECT
            COUNT(*) FILTER (WHERE log_hash IS NULL) AS null_count,
            COUNT(*) AS total,
            MIN(id) AS first_id,
            MAX(id) AS last_id
          FROM :"partition"
          WHERE id >= :from_id;
          SQL

          echo
          echo "=== Apply RC: $APPLY_RC ==="
          exit $APPLY_RC
          REMOTE

      - name: Print summary
        if: always()
        run: |
          {
            echo "## F1 chain rebuild — $PARTITION (from_id=$FROM_ID, dry_run=$DRY_RUN)"
            echo
            echo '```'
            cat /tmp/f1-rebuild.log 2>/dev/null || echo "(no log)"
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Cleanup SSH key
        if: always()
        run: rm -f ~/.ssh/liderra_deploy
