#!/usr/bin/env bun
/**
 * Backfill migration: encrypt PII fields at rest
 *
 * Usage: docker exec encryptid bun run src/encryptid/migrations/encrypt-pii.ts
 *
 * Idempotent — only processes rows where _enc IS NULL.
 * Batched for notifications (500/batch), unbatched for smaller tables.
 */

import postgres from 'postgres';
import { encryptField, hashForLookup } from '../server-crypto';

const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
  console.error('DATABASE_URL required');
  process.exit(1);
}

const sql = postgres(DATABASE_URL, { max: 5 });

async function backfillUsers() {
  const rows = await sql`
    SELECT id, email, profile_email, bio, avatar_url, wallet_address, up_address
    FROM users WHERE email_enc IS NULL AND (email IS NOT NULL OR bio IS NOT NULL OR avatar_url IS NOT NULL OR wallet_address IS NOT NULL OR up_address IS NOT NULL)
  `;
  console.log(`[users] ${rows.length} rows to backfill`);
  for (const row of rows) {
    const [emailEnc, emailHash, profileEmailEnc, bioEnc, avatarUrlEnc, walletEnc, upEnc, upHash] = await Promise.all([
      encryptField(row.email),
      row.email ? hashForLookup(row.email) : null,
      encryptField(row.profile_email),
      encryptField(row.bio),
      encryptField(row.avatar_url),
      encryptField(row.wallet_address),
      encryptField(row.up_address),
      row.up_address ? hashForLookup(row.up_address) : null,
    ]);
    await sql`
      UPDATE users SET
        email_enc = ${emailEnc}, email_hash = ${emailHash},
        profile_email_enc = ${profileEmailEnc},
        bio_enc = ${bioEnc}, avatar_url_enc = ${avatarUrlEnc},
        wallet_address_enc = ${walletEnc},
        up_address_enc = ${upEnc}, up_address_hash = ${upHash}
      WHERE id = ${row.id}
    `;
  }
  console.log(`[users] done`);
}

async function backfillGuardians() {
  const rows = await sql`
    SELECT id, name, email FROM guardians WHERE name_enc IS NULL
  `;
  console.log(`[guardians] ${rows.length} rows to backfill`);
  for (const row of rows) {
    const [nameEnc, emailEnc] = await Promise.all([
      encryptField(row.name),
      encryptField(row.email),
    ]);
    await sql`UPDATE guardians SET name_enc = ${nameEnc}, email_enc = ${emailEnc} WHERE id = ${row.id}`;
  }
  console.log(`[guardians] done`);
}

async function backfillIdentityInvites() {
  const rows = await sql`
    SELECT id, email, message FROM identity_invites WHERE email_enc IS NULL
  `;
  console.log(`[identity_invites] ${rows.length} rows to backfill`);
  for (const row of rows) {
    const [emailEnc, emailHash, messageEnc] = await Promise.all([
      encryptField(row.email),
      hashForLookup(row.email),
      encryptField(row.message),
    ]);
    await sql`UPDATE identity_invites SET email_enc = ${emailEnc}, email_hash = ${emailHash}, message_enc = ${messageEnc} WHERE id = ${row.id}`;
  }
  console.log(`[identity_invites] done`);
}

async function backfillSpaceInvites() {
  const rows = await sql`
    SELECT id, email FROM space_invites WHERE email_enc IS NULL AND email IS NOT NULL
  `;
  console.log(`[space_invites] ${rows.length} rows to backfill`);
  for (const row of rows) {
    const emailEnc = await encryptField(row.email);
    await sql`UPDATE space_invites SET email_enc = ${emailEnc} WHERE id = ${row.id}`;
  }
  console.log(`[space_invites] done`);
}

async function backfillNotifications() {
  const BATCH = 500;
  let offset = 0;
  let total = 0;
  while (true) {
    const rows = await sql`
      SELECT id, title, body, actor_username FROM notifications WHERE title_enc IS NULL
      ORDER BY created_at LIMIT ${BATCH} OFFSET ${offset}
    `;
    if (rows.length === 0) break;
    total += rows.length;
    console.log(`[notifications] processing batch at offset ${offset} (${rows.length} rows)`);
    await Promise.all(rows.map(async (row) => {
      const [titleEnc, bodyEnc, actorEnc] = await Promise.all([
        encryptField(row.title),
        encryptField(row.body),
        encryptField(row.actor_username),
      ]);
      await sql`UPDATE notifications SET title_enc = ${titleEnc}, body_enc = ${bodyEnc}, actor_username_enc = ${actorEnc} WHERE id = ${row.id}`;
    }));
    offset += BATCH;
  }
  console.log(`[notifications] ${total} rows done`);
}

async function backfillFundClaims() {
  const rows = await sql`
    SELECT id, email, wallet_address FROM fund_claims WHERE email_enc IS NULL AND (email IS NOT NULL OR wallet_address IS NOT NULL)
  `;
  console.log(`[fund_claims] ${rows.length} rows to backfill`);
  for (const row of rows) {
    const [emailEnc, emailHmac, walletEnc] = await Promise.all([
      encryptField(row.email),
      row.email ? hashForLookup(row.email) : null,
      encryptField(row.wallet_address),
    ]);
    await sql`UPDATE fund_claims SET email_enc = ${emailEnc}, email_hmac = ${emailHmac}, wallet_address_enc = ${walletEnc} WHERE id = ${row.id}`;
  }
  console.log(`[fund_claims] done`);
}

async function main() {
  console.log('=== PII Encryption Backfill ===');
  console.log(`Time: ${new Date().toISOString()}`);

  await backfillUsers();
  await backfillGuardians();
  await backfillIdentityInvites();
  await backfillSpaceInvites();
  await backfillNotifications();
  await backfillFundClaims();

  // Verification counts
  const [usersLeft] = await sql`SELECT COUNT(*)::int as c FROM users WHERE email IS NOT NULL AND email_enc IS NULL`;
  const [guardiansLeft] = await sql`SELECT COUNT(*)::int as c FROM guardians WHERE name_enc IS NULL`;
  const [notifLeft] = await sql`SELECT COUNT(*)::int as c FROM notifications WHERE title_enc IS NULL`;
  console.log('\n=== Verification ===');
  console.log(`Users without email_enc: ${usersLeft.c}`);
  console.log(`Guardians without name_enc: ${guardiansLeft.c}`);
  console.log(`Notifications without title_enc: ${notifLeft.c}`);

  await sql.end();
  console.log('\nDone.');
}

main().catch(err => {
  console.error('Migration failed:', err);
  process.exit(1);
});
