/**
 * audit3-3 smoke — Rollup uses server-side SQL aggregation, not a
 * Node-side findMany.
 *
 * Run: npx tsx scripts/smoke/audit3-3-rollup-sql.ts
 *
 * Why this exists (2026-05-28):
 *   The old rollup loaded every heartbeat in the retention window into
 *   Node memory before aggregating. On a 500-monitor / 60s-interval
 *   deployment that's ~350M rows transient and predictably OOMed the
 *   worker. The fix pushes the aggregation into MySQL via
 *   INSERT ... SELECT ... GROUP BY ... ON DUPLICATE KEY UPDATE plus a
 *   single DELETE.
 *
 *   This smoke is a regression detector — it reads the rollup source
 *   and asserts no `prisma.heartbeat.findMany` and no
 *   `prisma.heartbeatHourly.upsert` remain in the hot path. Anyone
 *   reverting to the row-by-row pattern will fail this assertion.
 */
import './_lib';
import { ok, fail } from './_lib';
import fs from 'fs';
import path from 'path';

const ROLLUP_SRC = path.resolve(__dirname, '../../src/lib/services/rollup.service.ts');

function main(): void {
    const src = fs.readFileSync(ROLLUP_SRC, 'utf8');

    if (/prisma\.heartbeat\.findMany/.test(src)) {
        fail(
            'audit3-3 no findMany',
            'rollup.service.ts contains prisma.heartbeat.findMany — that is the OOM-prone pattern this fix replaced',
        );
    }
    ok('rollup.service.ts has no prisma.heartbeat.findMany');

    if (/prisma\.heartbeatHourly\.upsert/.test(src)) {
        fail(
            'audit3-3 no per-bucket upsert',
            'rollup.service.ts uses prisma.heartbeatHourly.upsert — per-row Node-side loop reintroduces the perf problem',
        );
    }
    ok('rollup.service.ts has no per-bucket prisma upsert');

    if (!/INSERT\s+INTO\s+HeartbeatHourly[\s\S]+SELECT[\s\S]+GROUP\s+BY[\s\S]+ON\s+DUPLICATE\s+KEY\s+UPDATE/i.test(src)) {
        fail(
            'audit3-3 expected SQL shape missing',
            'rollup.service.ts no longer contains the INSERT ... SELECT ... GROUP BY ... ON DUPLICATE KEY UPDATE construct',
        );
    }
    ok('rollup.service.ts retains INSERT ... SELECT ... GROUP BY ... ON DUPLICATE KEY UPDATE');

    if (!/DELETE\s+FROM\s+Heartbeat\s+WHERE\s+monitorId\s*=\s*\?\s+AND\s+createdAt\s*<\s*\?/i.test(src)) {
        fail(
            'audit3-3 raw delete missing',
            'rollup.service.ts no longer issues the targeted DELETE FROM Heartbeat after aggregation',
        );
    }
    ok('rollup.service.ts retains targeted DELETE FROM Heartbeat');

    // The two WHERE clauses must match (same monitorId + cutoff) so we
    // never delete rows we did not aggregate. Cheap check: both
    // statements use the same parameter binding pattern.
    const insertMatch = src.match(/INSERT[\s\S]+?WHERE\s+(monitorId\s*=\s*\?\s+AND\s+createdAt\s*<\s*\?)/i);
    const deleteMatch = src.match(/DELETE[\s\S]+?WHERE\s+(monitorId\s*=\s*\?\s+AND\s+createdAt\s*<\s*\?)/i);
    if (!insertMatch || !deleteMatch || insertMatch[1].replace(/\s+/g, ' ') !== deleteMatch[1].replace(/\s+/g, ' ')) {
        fail(
            'audit3-3 WHERE clause symmetry',
            'INSERT and DELETE filter clauses must match exactly — risk of dropping rows that were not rolled up',
        );
    }
    ok('INSERT and DELETE share the same WHERE predicate (no orphan deletes)');

    ok('audit3-3 rollup SQL aggregation', 'no Node-side row materialization; INSERT ... SELECT replaces findMany loop');
}

main();
