#!/usr/bin/env node

/**
 * One-shot admin-password rotation.
 *
 * Closes the operator caveat from the `drop-public-setup` convoy: any
 * deployed env that ran `scripts/setup-neon-db.js` BEFORE that convoy
 * (commit `ff80753`, 2026-05-22) still carries the historical
 * `admin123` bcrypt hash in `users.password_hash` for the admin row.
 * The seed is idempotent (`ON CONFLICT (email) DO NOTHING`), so
 * re-running `npm run setup-db` does NOT rotate the password — it
 * just no-ops on the existing row.
 *
 * This script is the deliberate, audit-trail-preserving alternative
 * to "log into the app and change the password manually": it leaves
 * an `updated_at` timestamp on the row, validates the target row is
 * actually an admin before touching it, and verifies the hash matches
 * post-update.
 *
 * Usage:
 *
 *   POSTGRES_URL=... \
 *   ADMIN_NEW_PASSWORD=$(openssl rand -base64 24) \
 *   node scripts/rotate-admin-password.js
 *
 *   # Or via .env.local with both vars set:
 *   node scripts/rotate-admin-password.js
 *
 * Required env:
 *   - POSTGRES_URL          — Neon connection string (same shape as
 *                             `npm run setup-db` uses).
 *   - ADMIN_NEW_PASSWORD    — new plaintext password. The script
 *                             fail-loud-exits BEFORE opening any DB
 *                             connection if this is unset, empty, or
 *                             shorter than 12 chars.
 *
 * Optional env:
 *   - ADMIN_EMAIL           — defaults to `admin@deckhearth.com`. Use
 *                             this if your env still has the legacy
 *                             `admin@tcgvault.com` row (pre-`pick-a-name`
 *                             convoy, 2026-05-24). Run the email-rename
 *                             migration first if so:
 *                               node scripts/migrations/2026-05-24-rename-admin-email.js
 *
 * Safety guarantees:
 *   - Validates the target row exists AND has `role = 'admin'` before
 *     touching it (refuses to rotate a non-admin row even if you point
 *     ADMIN_EMAIL at it).
 *   - Never echoes the password to stdout / stderr / shell history.
 *     The only stdout output is the rotation status + the row's id /
 *     email / role / updated_at.
 *   - Verifies the new hash matches the supplied plaintext via
 *     `bcrypt.compare` after the UPDATE, before exiting clean. If the
 *     compare fails (extremely unlikely), exits non-zero so the
 *     operator knows to investigate.
 *
 * NOT in scope (intentionally):
 *   - Rotating alice / bob test-user passwords (they live in
 *     `scripts/create-test-users.js` and are dev/test fixtures).
 *   - A "first-login forced rotation" feature in the app (heavier
 *     scope; would be the `force-admin-password-reset-flow` convoy).
 */

import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });

import { neon } from '@neondatabase/serverless';
import bcrypt from 'bcryptjs';

const MIN_PASSWORD_LENGTH = 12;
const BCRYPT_ROUNDS = 12;

async function main() {
  const postgresUrl = process.env.POSTGRES_URL;
  const newPassword = process.env.ADMIN_NEW_PASSWORD;
  const targetEmail = process.env.ADMIN_EMAIL || 'admin@deckhearth.com';

  if (!postgresUrl) {
    console.error('❌ POSTGRES_URL is not set. Set it in .env.local or pass inline.');
    process.exit(1);
  }

  if (!newPassword || newPassword.length === 0) {
    console.error('❌ ADMIN_NEW_PASSWORD is not set. Generate one with:');
    console.error('     openssl rand -base64 24');
    console.error('   Then re-run:');
    console.error('     ADMIN_NEW_PASSWORD=<value> node scripts/rotate-admin-password.js');
    process.exit(1);
  }

  if (newPassword.length < MIN_PASSWORD_LENGTH) {
    console.error(`❌ ADMIN_NEW_PASSWORD must be at least ${MIN_PASSWORD_LENGTH} characters. Got ${newPassword.length}.`);
    process.exit(1);
  }

  const sql = neon(postgresUrl);

  // `@neondatabase/serverless` returns rows directly (NOT wrapped in
  // { rows, rowCount } like `@vercel/postgres`). Same pattern as
  // scripts/migrations/2026-05-24-rename-admin-email.js.
  const before = await sql`
    SELECT id, email, role, updated_at
    FROM users
    WHERE email = ${targetEmail}
  `;

  if (before.length === 0) {
    console.error(`❌ No user found with email '${targetEmail}'.`);
    console.error('   If you ran setup-db before the pick-a-name convoy (2026-05-24),');
    console.error('   the admin row may still be at admin@tcgvault.com. Run the');
    console.error('   email-rename migration first:');
    console.error('     node scripts/migrations/2026-05-24-rename-admin-email.js');
    console.error('   Or pass ADMIN_EMAIL=admin@tcgvault.com to target the legacy row.');
    process.exit(1);
  }

  if (before.length > 1) {
    console.error(`❌ Found ${before.length} rows matching '${targetEmail}'. The users.email column has a UNIQUE constraint so this should be impossible. Investigate manually.`);
    process.exit(1);
  }

  const row = before[0];
  if (row.role !== 'admin') {
    console.error(`❌ Refusing to rotate password: row '${targetEmail}' (id=${row.id}) has role='${row.role}', not 'admin'. This script only rotates admin rows.`);
    process.exit(1);
  }

  console.log(`Found admin row: id=${row.id} email=${row.email} updated_at=${row.updated_at?.toISOString?.() ?? row.updated_at}`);

  const hashedPassword = await bcrypt.hash(newPassword, BCRYPT_ROUNDS);

  await sql`
    UPDATE users
    SET password_hash = ${hashedPassword},
        updated_at = CURRENT_TIMESTAMP
    WHERE id = ${row.id}
      AND email = ${targetEmail}
      AND role = 'admin'
  `;

  // Re-fetch + verify the new hash matches the supplied plaintext.
  const after = await sql`
    SELECT id, email, role, password_hash, updated_at
    FROM users
    WHERE id = ${row.id}
  `;

  if (after.length !== 1) {
    console.error(`❌ Post-update fetch returned ${after.length} rows. Investigate manually.`);
    process.exit(1);
  }

  const matches = await bcrypt.compare(newPassword, after[0].password_hash);
  if (!matches) {
    console.error('❌ Post-update bcrypt.compare returned false. The UPDATE may have failed silently. Investigate manually.');
    process.exit(1);
  }

  console.log(`✅ Rotated password for admin row id=${row.id} email=${row.email}.`);
  console.log(`   New updated_at: ${after[0].updated_at?.toISOString?.() ?? after[0].updated_at}`);
  console.log('   The new password is NOT echoed; store it in your secret manager.');
}

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