/**
 * routes/auth.js
 * Registration, login, logout, email verification, password reset.
 */

'use strict';

const express = require('express');
const validator = require('validator');
const User    = require('../models/user');
const Audit   = require('../models/auditLog');
const email   = require('../services/email');
const throttle = require('../services/throttle');
const sec     = require('../config/security');
const { db }   = require('../config/db');
const { requireLogin } = require('../middleware/auth');

const router = express.Router();

// ---------- register ----------
router.post('/register', async (req, res) => {
  try {
    const { name, email: rawEmail, password, role } = req.body || {};
    if (!name || !rawEmail || !password) {
      return res.status(400).json({ error: 'MISSING_FIELDS' });
    }
    if (!validator.isEmail(rawEmail)) {
      return res.status(400).json({ error: 'INVALID_EMAIL' });
    }
    if (User.getByEmail(rawEmail)) {
      return res.status(409).json({ error: 'EMAIL_TAKEN' });
    }
    // Public registration: only `user` role; superadmin/admin via CLI or invite
    const safeRole = (role === 'admin' && process.env.ALLOW_INVITE_ADMIN === '1') ? 'admin' : 'user';
    if (safeRole === 'admin' && process.env.ALLOW_INVITE_ADMIN !== '1') {
      return res.status(403).json({ error: 'ADMIN_INVITE_DISABLED' });
    }
    const user = User.create({ name, email: rawEmail, password, role: safeRole, plan: 'free' });
    Audit.log(user, 'user.register', `user:${user.id}`, { ip: req.ip });
    try {
      const u = User.getById(user.id);
      await email.sendVerification(u, u.verify_token);
    } catch (e) {
      console.warn('[auth] verification email failed:', e.message);
    }
    // Session fixation defense: regenerate session on register too.
    // Stamp the user's password_changed_at on the session so a future
    // password change invalidates this one.
    req.session.regenerate((regenErr) => {
      if (regenErr) {
        console.warn('[auth] session regenerate error:', regenErr.message);
        return res.status(500).json({ error: 'SESSION_REGEN_FAILED' });
      }
      const fresh = User.getById(user.id);
      req.session.userId = fresh.id;
      req.session.password_at = fresh.password_changed_at || Date.now();
      req.session.email_at    = fresh.email_changed_at    || Date.now();
      // Stash IP + User-Agent on the session so /auth/sessions can list them.
      req.session.created_ip = req.ip;
      req.session.created_ua = (req.get('user-agent') || '').slice(0, 256);
      req.session.created_at = Date.now();
      // Geo lookup at register — same rationale as login
      try {
        const geo = require('../services/geo');
        req.session.created_geo = geo.lookup(req.ip);
      } catch (_) {}
      req.session.email_at    = fresh.email_changed_at    || Date.now();
      req.session.save((saveErr) => {
        if (saveErr) console.warn('[auth] session save error:', saveErr.message);
        res.json({ ok: true, user: User.safeUser(fresh), message: 'Account created. Check your email to verify.' });
      });
    });
  } catch (err) {
    res.status(400).json({ error: err.code || 'REGISTER_FAILED', message: err.message });
  }
});

// ---------- verify ----------
router.get('/verify', (req, res) => {
  const token = req.query.token;
  if (!token) return res.status(400).send('Missing token');
  const u = User.getByVerifyToken(token);
  if (!u) return res.status(400).send('Invalid or expired token');
  if (u.verify_expires && u.verify_expires < Date.now()) return res.status(400).send('Token expired');
  User.verifyEmail(u.id);
  Audit.log(u, 'user.verify', `user:${u.id}`);
  res.redirect('/login?verified=1');
});

// ---------- login ----------
router.post('/login', async (req, res) => {
  const { email: rawEmail, password } = req.body || {};
  if (!rawEmail || !password) {
    return res.status(400).json({ error: 'MISSING_CREDENTIALS' });
  }
  // Per-IP throttle (cheap check before any DB lookup)
  const ipCheck = throttle.checkIp(req.ip);
  if (ipCheck.blocked) {
    res.set('Retry-After', String(ipCheck.retryAfter || 60));
    const isCooldown = ipCheck.reason === 'ip_cooldown';
    const mins = Math.max(1, Math.ceil((ipCheck.retryAfter || 60) / 60));
    return res.status(429).json({
      error: isCooldown ? 'IP_COOLDOWN' : 'IP_RATE_LIMITED',
      retry_after: ipCheck.retryAfter,
      message: isCooldown
        ? `Too many attempts from this network. Try again in ${ipCheck.retryAfter} seconds, or wait ${mins} minute${mins > 1 ? 's' : ''} and retry.`
        : `This network has hit the per-hour limit (${sec.IP_LOGIN_MAX_PER_HR} attempts). Try again in about ${mins} minute${mins > 1 ? 's' : ''}.`,
    });
  }

  const u = User.getByEmail(rawEmail);
  if (!u) {
    throttle.recordFailure(req.ip, rawEmail);
    return res.status(401).json({ error: 'INVALID_CREDENTIALS' });
  }
  // Per-account lockout check (with exponential backoff)
  const accountCheck = throttle.checkAccount(u);
  if (accountCheck.locked) {
    res.set('Retry-After', String(Math.ceil((accountCheck.until - Date.now()) / 1000)));
    return res.status(423).json({ error: 'ACCOUNT_LOCKED', until: accountCheck.until });
  }
  if (u.suspended) {
    return res.status(403).json({ error: 'SUSPENDED' });
  }
  if (!User.comparePassword(password, u.password_hash)) {
    throttle.recordFailure(req.ip, rawEmail);
    const lock = throttle.recordAccountFailure(u.id, u.failed_logins);
    Audit.log(u, 'user.login.fail', `user:${u.id}`, { ip: req.ip, lockout: lock ? `${lock.minutes}m` : null });
    if (lock) {
      res.set('Retry-After', String(lock.minutes * 60));
      return res.status(423).json({ error: 'ACCOUNT_LOCKED', until: Date.now() + lock.minutes * 60 * 1000, message: `Too many attempts. Account locked for ${lock.minutes} minutes.` });
    }
    return res.status(401).json({ error: 'INVALID_CREDENTIALS' });
  }
  if (!u.email_verified) {
    return res.status(403).json({ error: 'EMAIL_NOT_VERIFIED', message: 'Please verify your email first.' });
  }
  // SUCCESS — regenerate session ID first (defends against session fixation)
  throttle.recordSuccess(req.ip, rawEmail);
  throttle.recordAccountSuccess(u.id);
  // req.session.regenerate destroys the current session and creates a new
  // one with a new ID. We do this BEFORE setting userId so an attacker
  // who planted a known pre-auth session ID loses it.
  req.session.regenerate((regenErr) => {
    if (regenErr) {
      console.warn('[auth] session regenerate error:', regenErr.message);
      return res.status(500).json({ error: 'SESSION_REGEN_FAILED' });
    }
    // Check if 2FA is enabled for this user
    const twofaRow = db.prepare('SELECT enabled FROM user_2fa WHERE user_id = ?').get(u.id);
    if (twofaRow && twofaRow.enabled) {
      req.session.pending_2fa_user_id  = u.id;
      req.session.pending_2fa_expires = Date.now() + 5 * 60 * 1000;
      req.session.save((err) => {
        if (err) console.warn('[auth] session save error:', err.message);
        Audit.log(u, 'user.login.2fa_required', `user:${u.id}`, { ip: req.ip });
        res.json({ ok: true, requires_2fa: true, message: 'Enter the 6-digit code from your authenticator app.' });
      });
      return;
    }
    req.session.userId = u.id;
    req.session.password_at = u.password_changed_at || Date.now();
    req.session.email_at    = u.email_changed_at    || Date.now();
    // Stash IP + User-Agent on the session so /auth/sessions can list them.
    req.session.created_ip = req.ip;
    req.session.created_ua = (req.get('user-agent') || '').slice(0, 256);
    req.session.created_at = Date.now();
    // Geo lookup at login — captured here so the lookup cost isn't paid
    // every time /auth/sessions is called.
    try {
      const geo = require('../services/geo');
      req.session.created_geo = geo.lookup(req.ip);
    } catch (_) { /* geo is best-effort; never block login */ }
    Audit.log(u, 'user.login', `user:${u.id}`, { ip: req.ip });
    // Track known IP + notify if it's a new device
    try {
      const known = db.prepare('SELECT 1 FROM user_known_ips WHERE user_id = ? AND ip = ?').get(u.id, req.ip);
      db.prepare(`INSERT OR IGNORE INTO user_known_ips (user_id, ip, first_seen, last_seen) VALUES (?, ?, ?, ?)`)
        .run(u.id, req.ip, Date.now(), Date.now());
      db.prepare(`UPDATE user_known_ips SET last_seen = ? WHERE user_id = ? AND ip = ?`)
        .run(Date.now(), u.id, req.ip);
      if (!known && u.role !== 'superadmin') {
        // Resolve a human location from the IP (offline MaxMind DB; no
        // network call so the survivor's IP never leaves the box).
        let where = 'IP ' + req.ip;
        try {
          const geo = require('../services/geo');
          const g = geo.lookup(req.ip);
          if (g) where = (g.city ? g.city + ', ' : '') + (g.country_name || g.country);
        } catch (_) {}
        const ip = email.sendRaw(u.email,
          'New sign-in to your Shield account',
          `Hi ${u.name},\n\nA new sign-in to your Shield account was detected from ${where} (IP ${req.ip}) at ${new Date().toISOString()}.\n\nIf this was you, no action is needed. If you don't recognise this sign-in, change your password immediately and review your active sessions from the dashboard.\n\n— Shield`,
          { tag: 'security-alert' }
        );
        ip.catch(err => console.warn('[auth] new-device email failed:', err.message));
      }
    } catch (e) { console.warn('[auth] post-login tracking failed:', e.message); }
    req.session.save((err) => {
      if (err) console.warn('[auth] session save error:', err.message);
      res.json({ ok: true, user: User.safeUser(u) });
    });
  });
});

// ---------- logout ----------
router.post('/logout', (req, res) => {
  if (req.user) Audit.log(req.user, 'user.logout', `user:${req.user.id}`);
  // Revoke the trusted-device cookie + DB row for THIS device, so
  // a stolen laptop can't keep using the "remember this device"
  // bypass after the operator signs out. (If the cookie is missing
  // or unknown, the clear is a no-op — safe.)
  try {
    const trustedDevice = require('../services/trustedDevice');
    const token = req.cookies && req.cookies[trustedDevice.COOKIE_NAME];
    if (token && /^[a-f0-9]{64}$/i.test(token) && req.user) {
      const TrustedDeviceModel = require('../models/trustedDevice');
      const row = TrustedDeviceModel.getByToken(token);
      if (row && row.user_id === req.user.id) {
        TrustedDeviceModel.remove(row.id, req.user.id);
      }
    }
    trustedDevice.clearCookie(res);
  } catch (_) { /* best-effort */ }
  req.session.destroy(() => res.json({ ok: true }));
});

// ---------- public alert reference lookup (for the "ref" in error messages) ----------
// Anyone with the short ref can see the user-facing status. No admin
// details are ever returned.
router.get('/ref/:ref', (req, res) => {
  const ref = String(req.params.ref || '').toLowerCase();
  const alertBus = require('../services/alertBus');
  const view = alertBus.publicView(ref);
  if (!view) return res.status(404).json({ error: 'NOT_FOUND' });
  res.json({ ref: view });
});

// ---------- password reset request ----------
router.post('/forgot', async (req, res) => {
  const { email: rawEmail } = req.body || {};
  if (!rawEmail) return res.status(400).json({ error: 'MISSING_EMAIL' });
  const u = User.getByEmail(rawEmail);
  if (u) {
    const token = User.issueResetToken(u.id);
    try { await email.sendPasswordReset(u, token); } catch (e) { console.warn(e.message); }
    Audit.log(u, 'user.forgot', `user:${u.id}`);
  }
  // always 200 to avoid email enumeration
  res.json({ ok: true, message: 'If that email is registered, a reset link has been sent.' });
});

router.get('/reset', (req, res) => {
  const token = req.query.token;
  if (!token) return res.status(400).send('Missing token');
  const u = User.getByResetToken(token);
  if (!u || (u.reset_expires && u.reset_expires < Date.now())) {
    return res.status(400).send('Invalid or expired token');
  }
  res.redirect(`/login?reset=${encodeURIComponent(token)}`);
});

router.post('/reset', (req, res) => {
  const { token, password } = req.body || {};
  if (!token || !password) return res.status(400).json({ error: 'MISSING_FIELDS' });
  const u = User.getByResetToken(token);
  if (!u || (u.reset_expires && u.reset_expires < Date.now())) {
    return res.status(400).json({ error: 'INVALID_TOKEN' });
  }
  try {
    User.setPassword(u.id, password);
  } catch (e) {
    return res.status(400).json({ error: 'WEAK_PASSWORD', message: e.message });
  }
  Audit.log(u, 'user.reset', `user:${u.id}`);
  res.json({ ok: true });
});

// ---------- current user ----------
router.get('/me', requireLogin, (req, res) => {
  res.json({ user: req.user });
});

// ---------- active sessions ----------
//
// GET    /auth/sessions         — list this user's active sessions
// DELETE /auth/sessions/:sid    — revoke one session
// POST   /auth/sessions/revoke-others — revoke all but the current session
//
// Sessions are identified by the sid in the SQLite session store
// (data/sessions.sqlite). The current session is the one making the
// request — its sid is in req.sessionID. Other sessions (other
// browsers, other devices) are listed and can be revoked.
const sessionSvc = require('../services/sessions');

router.get('/sessions', requireLogin, (req, res) => {
  const currentSid = req.sessionID;
  const sessions = sessionSvc.listForUser(req.user.id, currentSid);
  // Sort: current session first, then by created_at desc
  sessions.sort((a, b) => {
    if (a.is_current && !b.is_current) return -1;
    if (b.is_current && !a.is_current) return 1;
    return (b.created_at || 0) - (a.created_at || 0);
  });
  res.json({ sessions });
});

router.delete('/sessions/:sid', requireLogin, (req, res) => {
  const currentSid = req.sessionID;
  if (req.params.sid === currentSid) {
    return res.status(400).json({ error: 'CANNOT_REVOKE_CURRENT', message: 'Use /auth/logout to end your current session.' });
  }
  sessionSvc.revoke(req.user.id, req.params.sid, (err) => {
    if (err) {
      if (/NOT_FOUND_OR_NOT_OWNED/.test(err.message)) {
        return res.status(404).json({ error: 'SESSION_NOT_FOUND' });
      }
      return res.status(500).json({ error: 'REVOKE_FAILED' });
    }
    Audit.log(req.user, 'user.session.revoke', `user:${req.user.id}`, { revoked_sid_prefix: req.params.sid.slice(0, 8), ip: req.ip });
    res.json({ ok: true });
  });
});

router.post('/sessions/revoke-others', requireLogin, (req, res) => {
  const currentSid = req.sessionID;
  sessionSvc.revokeOthers(req.user.id, currentSid, (err, result) => {
    if (err) return res.status(500).json({ error: 'REVOKE_FAILED' });
    Audit.log(req.user, 'user.session.revoke_others', `user:${req.user.id}`, { revoked: result.revoked, ip: req.ip });
    // Send "new sign-in from another device" alert only if we revoked
    // any — that means there was at least one other device.
    if (result.revoked > 0) {
      try {
        const email = require('../services/email');
        email.sendRaw(req.user.email,
          'Other Shield sessions were signed out',
          `Hi ${req.user.name},\n\n${result.revoked} other session(s) on your Shield account were just signed out from IP ${req.ip} at ${new Date().toISOString()}.\n\nIf this was you, no action is needed. If you don't recognise this, change your password immediately.\n\n— Shield`,
          { tag: 'security-alert' }
        ).catch(e => console.warn('[auth] revoke-others email failed:', e.message));
      } catch (_) {}
    }
    res.json({ ok: true, revoked: result.revoked });
  });
});

// ---------- change password (while logged in) ----------
//
// Verifies the current password, sets a new one, and bumps
// password_changed_at on the user — which invalidates ALL other
// sessions on the next request (loadUser checks session.password_at
// vs user.password_changed_at).
//
// Optionally requires a 2FA token if the user has 2FA enabled —
// because a stolen session cookie + missing CSRF isn't the only
// threat; a session hijacker could use a stale tab to change the
// password and lock the real user out.
router.post('/change-password', requireLogin, async (req, res) => {
  try {
    const { current_password, new_password, totp: totpToken } = req.body || {};
    if (!current_password || !new_password) {
      return res.status(400).json({ error: 'MISSING_FIELDS' });
    }
    // Re-fetch the user to get the current password_hash (req.user is
    // a safe version that strips it).
    const u = User.getById(req.user.id);
    if (!u) return res.status(401).json({ error: 'UNAUTHENTICATED' });
    if (!User.comparePassword(current_password, u.password_hash)) {
      Audit.log(u, 'user.change_password.fail', `user:${u.id}`, { ip: req.ip, reason: 'wrong_current' });
      return res.status(401).json({ error: 'WRONG_CURRENT_PASSWORD' });
    }
    if (current_password === new_password) {
      return res.status(400).json({ error: 'SAME_PASSWORD', message: 'New password must differ from the current one.' });
    }
    // If 2FA is enabled, require the current code
    const twofaRow = db.prepare('SELECT secret FROM user_2fa WHERE user_id = ? AND enabled = 1').get(u.id);
    if (twofaRow) {
      if (!totpToken) return res.status(403).json({ error: 'TWO_FA_REQUIRED', message: 'Enter your 6-digit code to confirm.' });
      const totp = require('../services/totp');
      if (!totp.verifyToken(twofaRow.secret, totpToken)) {
        Audit.log(u, 'user.change_password.fail', `user:${u.id}`, { ip: req.ip, reason: 'bad_2fa' });
        return res.status(401).json({ error: 'INVALID_2FA_CODE' });
      }
    }
    // Apply the change. setPassword bumps password_changed_at, which
    // invalidates every other session for this user on next request.
    User.setPassword(u.id, new_password);
    // Revoke ALL trusted devices — a password change is a strong
    // signal that the previous trust is no longer valid. Better to
    // make the operator re-2FA on each device than to leave a stolen
    // laptop with a still-valid bypass.
    try {
      const TrustedDeviceModel = require('../models/trustedDevice');
      TrustedDeviceModel.removeAllForUser(u.id);
    } catch (e) { console.warn('[auth] trusted-device revoke after pw change failed:', e.message); }
    Audit.log(u, 'user.change_password', `user:${u.id}`, { ip: req.ip });
    // Send notification email (best-effort).
    try {
      const ip = req.ip;
      await email.sendRaw(u.email,
        'Your Shield password was changed',
        `Hi ${u.name},\n\nYour password was just changed from IP ${ip} at ${new Date().toISOString()}.\n\nIf this was you, no action is needed.\n\nIf you did not do this, your account may be compromised. Reset your password immediately and contact support@takedowns.vesamuni.com.\n\n— Shield`,
        { tag: 'security-alert' }
      );
    } catch (e) { console.warn('[auth] password-change email failed:', e.message); }
    // Invalidate THIS session too — force re-login. Otherwise the old
    // tab stays open and a hijacker who triggered this could keep using it.
    req.session.destroy((err) => {
      if (err) console.warn('[auth] session destroy after password change:', err.message);
      res.json({ ok: true, message: 'Password updated. Other sessions have been signed out. Please sign in again.' });
    });
  } catch (err) {
    if (err.message && /Password/.test(err.message)) {
      return res.status(400).json({ error: 'WEAK_PASSWORD', message: err.message });
    }
    console.error('[auth] change-password error:', err);
    res.status(500).json({ error: 'INTERNAL' });
  }
});

// ---------- change email (while logged in) ----------
//
// Verifies the current password, sets a new email (unverified),
// sends a verification email. The new email is not active until
// verified — the user can still log in with the old one in the
// meantime.
//
// Requires 2FA if enabled (same rationale as change-password).
router.post('/change-email', requireLogin, async (req, res) => {
  try {
    const { new_email, current_password, totp: totpToken } = req.body || {};
    if (!new_email || !current_password) {
      return res.status(400).json({ error: 'MISSING_FIELDS' });
    }
    if (!validator.isEmail(new_email)) {
      return res.status(400).json({ error: 'INVALID_EMAIL' });
    }
    const normalized = new_email.toLowerCase().trim();
    const u = User.getById(req.user.id);
    if (!u) return res.status(401).json({ error: 'UNAUTHENTICATED' });
    if (!User.comparePassword(current_password, u.password_hash)) {
      Audit.log(u, 'user.change_email.fail', `user:${u.id}`, { ip: req.ip, reason: 'wrong_current' });
      return res.status(401).json({ error: 'WRONG_CURRENT_PASSWORD' });
    }
    if (normalized === u.email) {
      return res.status(400).json({ error: 'SAME_EMAIL', message: 'New email must differ from the current one.' });
    }
    if (User.getByEmail(normalized)) {
      return res.status(409).json({ error: 'EMAIL_TAKEN' });
    }
    // If 2FA is enabled, require the current code
    const twofaRow = db.prepare('SELECT secret FROM user_2fa WHERE user_id = ? AND enabled = 1').get(u.id);
    if (twofaRow) {
      if (!totpToken) return res.status(403).json({ error: 'TWO_FA_REQUIRED', message: 'Enter your 6-digit code to confirm.' });
      const totp = require('../services/totp');
      if (!totp.verifyToken(twofaRow.secret, totpToken)) {
        Audit.log(u, 'user.change_email.fail', `user:${u.id}`, { ip: req.ip, reason: 'bad_2fa' });
        return res.status(401).json({ error: 'INVALID_2FA_CODE' });
      }
    }
    // Apply the change. The new email is set with email_verified=0 and
    // a fresh verify_token. The OLD email is also notified.
    const { verify_token } = User.setEmail(u.id, normalized);
    const fresh = User.getById(u.id);
    // Revoke all trusted devices — same rationale as password change.
    // If the email was changed by an attacker, they shouldn't keep
    // using the trusted-device bypass.
    try {
      const TrustedDeviceModel = require('../models/trustedDevice');
      TrustedDeviceModel.removeAllForUser(u.id);
    } catch (e) { console.warn('[auth] trusted-device revoke after email change failed:', e.message); }
    Audit.log(fresh, 'user.change_email', `user:${u.id}`, { ip: req.ip, new_email: normalized });
    // Send verification to the new address
    try { await email.sendVerification(fresh, verify_token); } catch (e) { console.warn('[auth] new-email verify failed:', e.message); }
    // Notify the old address (in case the change is unauthorized)
    try {
      await email.sendRaw(u.email,
        'Your Shield email address was changed',
        `Hi ${u.name},\n\nYour account email was just changed to ${normalized} from IP ${req.ip} at ${new Date().toISOString()}.\n\nIf this was you, please verify the new address by clicking the link in the email we just sent to it.\n\nIf you did not do this, your account may be compromised. Change your password immediately and contact support@takedowns.vesamuni.com.\n\n— Shield`,
        { tag: 'security-alert' }
      );
    } catch (e) { console.warn('[auth] old-email notify failed:', e.message); }
    res.json({ ok: true, message: 'Verification email sent to ' + normalized + '. Your current login continues to work with your old email until you verify the new one.' });
  } catch (err) {
    console.error('[auth] change-email error:', err);
    res.status(500).json({ error: 'INTERNAL' });
  }
});

// ---------- delete account (while logged in) ----------
//
// Hard, irreversible delete. Requires:
//   - current_password (always)
//   - 2FA token if 2FA is enabled
//   - confirmation: body.confirm === 'DELETE'
//
// Wipes:
//   - the user row
//   - all cases (CASCADE), all findings (CASCADE), all takedowns (CASCADE)
//   - all sessions for this user (next request loadUser will fail since
//     user no longer exists; this request also destroys its own session)
//   - all api_keys, all audit_log, all sessions, all usage_log
//
// Sends a confirmation email to the (about-to-be-deleted) address.
router.post('/delete-account', requireLogin, async (req, res) => {
  try {
    const { current_password, totp: totpToken, confirm } = req.body || {};
    if (!current_password) return res.status(400).json({ error: 'MISSING_FIELDS' });
    if (confirm !== 'DELETE') return res.status(400).json({ error: 'CONFIRM_REQUIRED', message: 'Send {confirm: "DELETE"} to confirm account deletion.' });
    const u = User.getById(req.user.id);
    if (!u) return res.status(401).json({ error: 'UNAUTHENTICATED' });
    if (!User.comparePassword(current_password, u.password_hash)) {
      Audit.log(u, 'user.delete_account.fail', `user:${u.id}`, { ip: req.ip, reason: 'wrong_current' });
      return res.status(401).json({ error: 'WRONG_CURRENT_PASSWORD' });
    }
    // 2FA required if enabled
    const twofaRow = db.prepare('SELECT secret FROM user_2fa WHERE user_id = ? AND enabled = 1').get(u.id);
    if (twofaRow) {
      if (!totpToken) return res.status(403).json({ error: 'TWO_FA_REQUIRED', message: 'Enter your 6-digit code to confirm.' });
      const totp = require('../services/totp');
      if (!totp.verifyToken(twofaRow.secret, totpToken)) {
        Audit.log(u, 'user.delete_account.fail', `user:${u.id}`, { ip: req.ip, reason: 'bad_2fa' });
        return res.status(401).json({ error: 'INVALID_2FA_CODE' });
      }
    }
    // Superadmin can only be deleted by another superadmin (and even
    // then, refuse if they're the only one — prevents accidental
    // lockout of the platform).
    if (u.role === 'superadmin') {
      const otherSupers = db.prepare("SELECT COUNT(*) AS n FROM users WHERE role = 'superadmin' AND id != ?").get(u.id);
      if (otherSupers.n === 0) {
        return res.status(409).json({ error: 'LAST_SUPERADMIN', message: 'You are the only superadmin. Create another superadmin before deleting this account.' });
      }
    }
    // Send a final goodbye email (best-effort).
    try {
      await email.sendRaw(u.email,
        'Your Shield account has been deleted',
        `Hi ${u.name},\n\nYour account and all associated data have been permanently deleted as of ${new Date().toISOString()}.\n\nIf you did not request this, your account may be compromised. Contact support@takedowns.vesamuni.com immediately.\n\nThis is an irreversible action.\n\n— Shield`,
        { tag: 'security-alert' }
      );
    } catch (e) { console.warn('[auth] delete-account email failed:', e.message); }
    // Log the deletion BEFORE removing the user (FK constraints).
    Audit.log(u, 'user.delete_account', `user:${u.id}`, { ip: req.ip });
    // Drop the user. CASCADE wipes cases/findings/takedowns/2fa/keys/etc.
    User.remove(u.id);
    // Destroy this session.
    req.session.destroy((err) => {
      if (err) console.warn('[auth] session destroy after delete-account:', err.message);
      res.json({ ok: true, message: 'Account deleted.' });
    });
  } catch (err) {
    console.error('[auth] delete-account error:', err);
    res.status(500).json({ error: 'INTERNAL' });
  }
});

module.exports = router;
