#!/usr/bin/env node
// cleanup-audit-spam.mjs — one-shot maintenance script.
//
// Trims `audit_logs` rows produced by two pre-fix bugs:
//   1. `agent.create` rows written on every re-seed of the personal-*
//      template agents (fixed in lib/runtime.js::createAgent and in
//      lib/agent-templates.js).
//   2. `disk_encryption.health` rows written by each Next.js dev-mode
//      worker on every boot (fixed in lib/seeds.js::runDiskEncryptionCheck).
//
// The audit_logs table has BEFORE UPDATE and BEFORE DELETE triggers that
// raise at the database layer to make the audit trail tamper-evident.
// Operational maintenance must drop the trigger, perform the targeted
// DELETE, and re-create the trigger inside one transaction. This script
// does exactly that and nothing else. It does NOT touch user-authored
// audit rows.
//
// Usage:
//   node scripts/cleanup-audit-spam.mjs            # dry run — counts only
//   MADARX_AUDIT_CLEANUP_CONFIRM=1 \
//     node scripts/cleanup-audit-spam.mjs --apply  # run cleanup + VACUUM
//
// Idempotent: a second run finds nothing to delete and exits 0.

import { DatabaseSync } from 'node:sqlite';
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DB_FILE = process.env.MADARX_DB_FILE
  ? path.resolve(process.env.MADARX_DB_FILE)
  : path.resolve(__dirname, '..', 'data', 'madarx.sqlite');

const APPLY = process.argv.includes('--apply');

function main() {
  console.log(`[cleanup] db: ${DB_FILE}`);
  console.log(`[cleanup] mode: ${APPLY ? 'APPLY' : 'dry-run (re-run with --apply to actually delete)'}`);

  const db = new DatabaseSync(DB_FILE);
  db.exec('PRAGMA journal_mode=WAL;');
  db.exec('PRAGMA busy_timeout=10000;');

  // Fast counts — avoid the correlated subquery aggregation that was
  // taking 30+ seconds against an 800K-row audit_logs table. We estimate
  // delete counts from totals minus the rows we plan to keep:
  //   - agent.create: keep one row per distinct agent_id
  //   - disk_encryption.health: keep the most recent row only
  // The exact deletion runs inside the transaction below; this section
  // is only the operator-visible preview.
  const counts = db.prepare(`
    SELECT
      (SELECT COUNT(*) FROM audit_logs) AS all_total,
      (SELECT COUNT(*) FROM audit_logs WHERE type = 'agent.create') AS agent_create_total,
      (SELECT COUNT(DISTINCT json_extract(meta, '$.agent_id')) FROM audit_logs WHERE type = 'agent.create') AS agent_create_distinct,
      (SELECT COUNT(*) FROM audit_logs WHERE type = 'disk_encryption.health') AS disk_total
  `).get();

  const agentToDelete = Math.max(0, counts.agent_create_total - counts.agent_create_distinct);
  const diskToDelete = Math.max(0, counts.disk_total - 1);

  console.log(`[cleanup] audit_logs total: ${counts.all_total}`);
  console.log(`[cleanup] agent.create:        ${counts.agent_create_total} total, ${counts.agent_create_distinct} distinct agents, would delete ~${agentToDelete}`);
  console.log(`[cleanup] disk_encryption.h.:  ${counts.disk_total} total, would delete ~${diskToDelete}`);

  if (!APPLY) {
    console.log('[cleanup] dry run — exiting without changes. Pass --apply to delete.');
    db.close();
    return;
  }

  if (process.env.MADARX_AUDIT_CLEANUP_CONFIRM !== '1') {
    console.error('[cleanup] MADARX_AUDIT_CLEANUP_CONFIRM=1 required to run with --apply.');
    process.exitCode = 2;
    db.close();
    return;
  }

  console.log('[cleanup] running cleanup transaction...');
  db.exec('BEGIN IMMEDIATE');
  try {
    db.exec('DROP TRIGGER IF EXISTS audit_logs_no_update');
    db.exec('DROP TRIGGER IF EXISTS audit_logs_no_delete');

    const beforeAgent = db.prepare("SELECT COUNT(*) c FROM audit_logs WHERE type = 'agent.create'").get().c;
    const beforeDisk = db.prepare("SELECT COUNT(*) c FROM audit_logs WHERE type = 'disk_encryption.health'").get().c;

    db.exec(`
      DELETE FROM audit_logs
      WHERE type = 'agent.create'
        AND id NOT IN (
          SELECT id FROM audit_logs a1
          WHERE a1.type = 'agent.create'
            AND a1.at = (
              SELECT MIN(a2.at) FROM audit_logs a2
              WHERE a2.type = 'agent.create'
                AND json_extract(a2.meta, '$.agent_id') = json_extract(a1.meta, '$.agent_id')
            )
        )
    `);

    db.exec(`
      DELETE FROM audit_logs
      WHERE type = 'disk_encryption.health'
        AND id NOT IN (
          SELECT id FROM audit_logs
          WHERE type = 'disk_encryption.health'
          ORDER BY at DESC LIMIT 1
        )
    `);

    const afterAgent = db.prepare("SELECT COUNT(*) c FROM audit_logs WHERE type = 'agent.create'").get().c;
    const afterDisk = db.prepare("SELECT COUNT(*) c FROM audit_logs WHERE type = 'disk_encryption.health'").get().c;

    db.exec(
      `CREATE TRIGGER IF NOT EXISTS audit_logs_no_update
       BEFORE UPDATE ON audit_logs
       BEGIN
         SELECT RAISE(ABORT, 'audit_logs is append-only');
       END;`
    );
    db.exec(
      `CREATE TRIGGER IF NOT EXISTS audit_logs_no_delete
       BEFORE DELETE ON audit_logs
       BEGIN
         SELECT RAISE(ABORT, 'audit_logs is append-only');
       END;`
    );

    db.prepare(`
      INSERT INTO audit_logs (id, at, type, summary, status, meta)
      VALUES (?, ?, 'audit.cleanup', 'Trimmed audit_logs spam from pre-fix loggers', 'ok', ?)
    `).run(
      `audit-cleanup-${Date.now().toString(36)}`,
      new Date().toISOString(),
      JSON.stringify({
        deleted: {
          'agent.create': beforeAgent - afterAgent,
          'disk_encryption.health': beforeDisk - afterDisk,
        },
        kept: { 'agent.create': afterAgent, 'disk_encryption.health': afterDisk },
        notes: 'Append-only triggers temporarily dropped + recreated. See scripts/cleanup-audit-spam.mjs.',
      })
    );

    db.exec('COMMIT');
    console.log(`[cleanup] deleted ${beforeAgent - afterAgent} agent.create rows (kept ${afterAgent}).`);
    console.log(`[cleanup] deleted ${beforeDisk - afterDisk} disk_encryption.health rows (kept ${afterDisk}).`);
  } catch (err) {
    db.exec('ROLLBACK');
    console.error('[cleanup] FAILED, rolled back:', err);
    process.exitCode = 1;
    db.close();
    return;
  }

  console.log('[cleanup] running VACUUM (this rebuilds the file; may take a minute)...');
  db.exec('VACUUM');

  // VACUUM in WAL mode writes the rebuilt pages into the WAL file, not into
  // the main DB file. Without an explicit checkpoint, `ls -lh` keeps showing
  // the pre-VACUUM size and the WAL holds the freed pages — defeating the
  // entire cleanup. TRUNCATE merges WAL back into the main file and resets
  // the WAL to zero bytes. We do this only after VACUUM has committed.
  try {
    db.exec('PRAGMA wal_checkpoint(TRUNCATE)');
    console.log('[cleanup] WAL checkpoint truncated.');
  } catch (err) {
    console.warn('[cleanup] WAL checkpoint failed (run `sqlite3 data/madarx.sqlite "PRAGMA wal_checkpoint(TRUNCATE);"` manually):', err?.message || err);
  }

  try {
    const stat = fs.statSync(DB_FILE);
    console.log(`[cleanup] final db size: ${(stat.size / 1024 / 1024).toFixed(1)} MB`);
  } catch {}

  console.log('[cleanup] done.');
  db.close();
}

main();
