/**
 * routes/admin.js
 * Superadmin panel: user management, API key management, analytics, settings.
 */

'use strict';

const express = require('express');
const { requireLogin, requireRole, requireSuperadmin2FA } = require('../middleware/auth');
const User    = require('../models/user');
const Audit   = require('../models/auditLog');
const apiKey  = require('../models/apiKey');
const Case    = require('../models/case');
const Finding = require('../models/finding');
const Takedown = require('../models/takedown');
const { listPlans } = require('../config/plans');
const { db } = require('../config/db');

const router = express.Router();

router.use(requireLogin, requireRole('superadmin'));

// ----- 2FA bootstrap (enroll / verify) -----
// These three admin endpoints come BEFORE the 2FA gate so the admin
// can enroll on first login. Once 2FA is enabled, every subsequent
// admin endpoint enforces requireSuperadmin2FA below.
router.post('/2fa/start', requireLogin, requireRole('superadmin'), (req, res) => {
  return require('./twofa').handleStart(req, res);
});
router.post('/2fa/verify', requireLogin, requireRole('superadmin'), (req, res) => {
  return require('./twofa').handleVerify(req, res);
});
router.get('/2fa/status', requireLogin, requireRole('superadmin'), (req, res) => {
  const { db } = require('../config/db');
  const row = db.prepare('SELECT enabled FROM user_2fa WHERE user_id = ?').get(req.user.id);
  res.json({
    enabled: !!(row && row.enabled),
    verified_this_session: !!(req.session.twofa_verified_at && Date.now() - req.session.twofa_verified_at < 3600 * 1000),
  });
});

// ----- HTML page (also BEFORE the 2FA gate so the wizard can render) -----
// Same pattern as /2fa/start|verify|status above — the wizard UI on the
// dashboard itself needs to be reachable before 2FA is enrolled.
// Once 2FA is enabled, this same page also serves the post-enrollment UI;
// the api() calls inside admin.js handle the verified-this-session state.
router.get('/', (req, res) => {
  res.sendFile(require('path').join(__dirname, '..', 'public', 'views', 'admin.html'));
});

// Dedicated user detail page (replaces the modal). BEFORE the 2FA gate
// so the operator can navigate to a user page even mid-2FA flow.
router.get('/user/:id', (req, res) => {
  res.sendFile(require('path').join(__dirname, '..', 'public', 'views', 'admin-user.html'));
});

// Dedicated API key detail page (replaces the modal).
router.get('/key/:id', (req, res) => {
  res.sendFile(require('path').join(__dirname, '..', 'public', 'views', 'admin-key.html'));
});

// From this point on, every admin endpoint requires the admin to have
// 2FA enabled AND verified in this session.
router.use(requireSuperadmin2FA);

const alertBus = require('../services/alertBus');
const platformContacts = require('../services/platformContacts');
const email    = require('../services/email');
const worker   = require('../jobs/takedownWorker');

// ----- HTML page -----
// (intentionally moved ABOVE the requireSuperadmin2FA gate — see note there)

// ----- analytics -----
router.get('/analytics', (req, res) => {
  const userCounts = User.counts();
  const caseStats  = Case.stats();
  const tkStats    = Takedown.statistics();
  const tkReso     = Takedown.resolution();
  const planBreakdown = userCounts.by_plan.map(p => ({
    plan: p.plan, count: p.n,
  }));
  const mrr = planBreakdown.reduce((sum, p) => {
    const plan = require('../config/plans').PLANS[p.plan];
    return sum + (plan ? plan.price_cents * p.count : 0);
  }, 0) / 100;

  const aiCalls = db.prepare(`
    SELECT
      SUM(CASE WHEN created_at > ? THEN cost_estimate ELSE 0 END) AS cost_month,
      SUM(CASE WHEN created_at > ? AND success = 1 THEN 1 ELSE 0 END) AS calls_month
      FROM ai_calls
  `).get(Date.now() - 30 * 86400000, Date.now() - 30 * 86400000);

  const tier1 = db.prepare(`SELECT COUNT(*) AS n FROM findings WHERE tier = 1`).get().n;
  const tier2 = db.prepare(`SELECT COUNT(*) AS n FROM findings WHERE tier = 2`).get().n;
  const tier3 = db.prepare(`SELECT COUNT(*) AS n FROM findings WHERE tier = 3`).get().n;
  const totalScans = tier1 + tier2 + tier3;
  const aiAvoidPct = totalScans ? Math.round(((tier1 + tier2) / totalScans) * 100) : 100;

  const failedPayments = db.prepare(`
    SELECT COUNT(*) AS n FROM users WHERE payment_failed = 1
  `).get().n;

  const newRegs = User.registrationsLast30Days();

  const scansThisMonth = db.prepare(`
    SELECT COUNT(*) AS n FROM scans WHERE created_at > ?
  `).get(Date.now() - 30 * 86400000).n;

  res.json({
    users: {
      total: userCounts.total,
      by_plan: planBreakdown,
      mrr_estimate: mrr,
      new_registrations_30d: newRegs,
    },
    cases: {
      by_status: caseStats,
    },
    scans: {
      total: totalScans,
      this_month: scansThisMonth,
    },
    takedowns: {
      by_status: tkStats,
      total_sent: tkReso.total,
      resolved: tkReso.resolved,
      resolution_rate: tkReso.total ? Math.round((tkReso.resolved / tkReso.total) * 100) : 0,
      last_30_days: Takedown.countsLast30Days(),
      top_domains: Takedown.domainLeaderboard(10),
    },
    ai: {
      cost_this_month_usd: aiCalls.cost_month || 0,
      calls_this_month: aiCalls.calls_month || 0,
      tier1, tier2, tier3,
      ai_avoided_pct: aiAvoidPct,
    },
    billing: { failed_payments: failedPayments },
  });
});

// ----- API keys management -----
router.get('/api-keys', (req, res) => {
  res.json({ keys: apiKey.list().map(apiKey.safeKey), services: apiKey.SERVICES });
});

router.post('/api-keys', (req, res) => {
  const { service, key_value, label, quota_limit } = req.body || {};
  if (!service || !key_value) return res.status(400).json({ error: 'MISSING_FIELDS' });
  try {
    const k = apiKey.create({ service, key_value, label, quota_limit: parseInt(quota_limit, 10) || 1000 });
    Audit.log(req.user, 'admin.api_key.create', `key:${k.id}`, { service, label });
    res.json({ ok: true, key: apiKey.safeKey(k) });
  } catch (e) {
    res.status(400).json({ error: e.message });
  }
});

router.patch('/api-keys/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
  const { label, key_value, is_active, quota_limit } = req.body || {};
  apiKey.update(id, { label, key_value, is_active, quota_limit: quota_limit ? parseInt(quota_limit, 10) : undefined });
  Audit.log(req.user, 'admin.api_key.update', `key:${id}`);
  res.json({ ok: true, key: apiKey.safeKey(apiKey.getById(id)) });
});

router.post('/api-keys/:id/toggle', (req, res) => {
  const id = parseInt(req.params.id, 10);
  const k = apiKey.getById(id);
  if (!k) return res.status(404).json({ error: 'NOT_FOUND' });
  apiKey.toggleKey(id, !k.is_active);
  Audit.log(req.user, 'admin.api_key.toggle', `key:${id}`, { is_active: !k.is_active });
  res.json({ ok: true, key: apiKey.safeKey(apiKey.getById(id)) });
});

router.delete('/api-keys/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
  apiKey.remove(id);
  Audit.log(req.user, 'admin.api_key.delete', `key:${id}`);
  res.json({ ok: true });
});
// ----- learned contact map -----
// The system auto-populates this from real outcomes. These endpoints
// let the operator inspect, override, or disable entries.
const contactsRepo = require('../services/platformContactsRepo');

router.get('/contacts', (req, res) => {
  const { domain, source, min_confidence } = req.query;
  const filter = {};
  if (domain) filter.domain = domain;
  if (source) filter.source = source;
  if (min_confidence !== undefined) filter.minConfidence = parseFloat(min_confidence);
  res.json({ contacts: contactsRepo.list(filter), count: contactsRepo.list(filter).length });
});

router.get('/contacts/:id', (req, res) => {
  const c = contactsRepo.getById(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  res.json({ contact: c });
});

router.post('/contacts', (req, res) => {
  const { domain, contact, form_url, security_url, name, notes } = req.body || {};
  if (!domain || !contact) return res.status(400).json({ error: 'MISSING_FIELDS' });
  const c = contactsRepo.recordAdminOverride({ domain, contact, form_url, security_url, name, notes });
  Audit.log(req.user, 'admin.contact.create', `contact:${c.id}`, { domain, contact, source: 'admin_override' });
  res.json({ ok: true, contact: c });
});

router.patch('/contacts/:id', (req, res) => {
  const c = contactsRepo.adminUpdate(parseInt(req.params.id, 10), req.body || {});
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  Audit.log(req.user, 'admin.contact.update', `contact:${c.id}`, { domain: c.domain, contact: c.contact });
  res.json({ ok: true, contact: c });
});

router.post('/contacts/:id/disable', (req, res) => {
  const c = contactsRepo.adminDisable(parseInt(req.params.id, 10));
  if (!c) return res.status(404).json({ error: 'NOT_FOUND' });
  Audit.log(req.user, 'admin.contact.disable', `contact:${c.id}`, { domain: c.domain, contact: c.contact });
  res.json({ ok: true, contact: c });
});

// ----- user management -----
router.get('/users', (req, res) => {
  const { search, plan, status, limit = '50', offset = '0', include_test } = req.query;
  const list = User.list({
    search, plan, status,
    limit: parseInt(limit, 10),
    offset: parseInt(offset, 10),
    include_test: include_test === '1' ? '1' : '0',
  });
  res.json({ users: list, plans: listPlans() });
});

router.get('/users/:id', (req, res) => {
  const u = User.getById(parseInt(req.params.id, 10));
  if (!u) return res.status(404).json({ error: 'NOT_FOUND' });
  // Include API token metadata (never the hash or plaintext)
  const tokens = db.prepare(`
    SELECT id, label, last4, created_at, last_used_at, revoked_at
      FROM api_tokens WHERE user_id = ? ORDER BY created_at DESC
  `).all(u.id);
  res.json({ user: User.safeUser(u), api_tokens: tokens });
});

router.patch('/users/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
  const { name, plan, plan_override, plan_override_reason, suspended } = req.body || {};
  User.update(id, { name, plan, plan_override, plan_override_reason, suspended });
  Audit.log(req.user, 'admin.user.update', `user:${id}`, { plan, plan_override, suspended });
  res.json({ ok: true });
});

router.post('/users/:id/suspend', (req, res) => {
  const id = parseInt(req.params.id, 10);
  User.update(id, { suspended: 1 });
  Audit.log(req.user, 'admin.user.suspend', `user:${id}`);
  res.json({ ok: true });
});

router.post('/users/:id/unsuspend', (req, res) => {
  const id = parseInt(req.params.id, 10);
  User.update(id, { suspended: 0 });
  Audit.log(req.user, 'admin.user.unsuspend', `user:${id}`);
  res.json({ ok: true });
});

router.post('/users/:id/impersonate', (req, res) => {
  const id = parseInt(req.params.id, 10);
  const u = User.getById(id);
  if (!u) return res.status(404).json({ error: 'NOT_FOUND' });
  // mark session to track impersonation (don't rotate csrf — middleware handles it)
  req.session.impersonator_id = req.user.id;
  req.session.userId = u.id;
  Audit.log(req.user, 'admin.user.impersonate', `user:${id}`);
  res.json({ ok: true });
});

router.post('/users/:id/delete', (req, res) => {
  const id = parseInt(req.params.id, 10);
  if (id === req.user.id) return res.status(400).json({ error: 'CANNOT_DELETE_SELF' });
  User.remove(id);
  Audit.log(req.user, 'admin.user.delete', `user:${id}`);
  res.json({ ok: true });
});

// Revoke all API tokens for a user (in case of compromise)
router.post('/users/:id/revoke-tokens', (req, res) => {
  const id = parseInt(req.params.id, 10);
  db.prepare(`UPDATE api_tokens SET revoked_at = ? WHERE user_id = ? AND revoked_at IS NULL`)
    .run(Date.now(), id);
  Audit.log(req.user, 'admin.user.revoke_tokens', `user:${id}`);
  res.json({ ok: true });
});

// ----- audit log -----
router.get('/audit', (req, res) => {
  const { limit = '100', offset = '0' } = req.query;
  res.json({ log: Audit.list(parseInt(limit, 10), parseInt(offset, 10)) });
});

// ----- settings (basic) -----
router.get('/settings', (req, res) => {
  const rows = db.prepare('SELECT key, value, updated_at FROM settings').all();
  res.json({ settings: rows });
});

router.put('/settings/:key', (req, res) => {
  const { value } = req.body || {};
  db.prepare(`
    INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)
    ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
  `).run(req.params.key, value, Date.now());
  Audit.log(req.user, 'admin.settings.update', req.params.key, { value });
  res.json({ ok: true });
});

// ================= Alerts =================
// These are the operator-only alert surface. NEVER expose them to non-admins.
// (The /admin router already requires superadmin role above.)

router.get('/alerts', (req, res) => {
  const { severity, service, category, acknowledged, limit = '200', offset = '0' } = req.query;
  const list = alertBus.list({
    severity:    severity || null,
    service:     service  || null,
    category:    category || null,
    acknowledged:acknowledged === undefined ? null : acknowledged === 'true' ? 1 : 0,
    limit:       Math.min(parseInt(limit, 10) || 200, 500),
    offset:      parseInt(offset, 10) || 0,
  });
  res.json({ alerts: list, counts: alertBus.counts() });
});

router.get('/alerts/:id', (req, res) => {
  const a = alertBus.getById(parseInt(req.params.id, 10));
  if (!a) return res.status(404).json({ error: 'NOT_FOUND' });
  res.json({ alert: a });
});

router.get('/alerts/test-data-summary', (req, res) => {
  // Used by the admin banner: "X test users + Y test cases + Z test
  // takedowns detected". Lets the operator see the scope before
  // clicking "mark all as test" or "acknowledge all test alerts".
  const { db } = require('../config/db');
  const counts = {
    testUsers:     db.prepare('SELECT COUNT(*) AS n FROM users     WHERE is_test_data=1').get().n,
    realUsers:     db.prepare('SELECT COUNT(*) AS n FROM users     WHERE is_test_data=0').get().n,
    testCases:     db.prepare('SELECT COUNT(*) AS n FROM cases     WHERE is_test_data=1').get().n,
    realCases:     db.prepare('SELECT COUNT(*) AS n FROM cases     WHERE is_test_data=0').get().n,
    testTakedowns: db.prepare('SELECT COUNT(*) AS n FROM takedowns WHERE is_test_data=1').get().n,
    realTakedowns: db.prepare('SELECT COUNT(*) AS n FROM takedowns WHERE is_test_data=0').get().n,
    unackAlertsAboutTest: db.prepare(`
      SELECT COUNT(*) AS n FROM alerts
       WHERE acknowledged = 0
         AND (
           ref_url LIKE 'takedown:%' AND CAST(SUBSTR(ref_url, 10) AS INTEGER) IN (SELECT id FROM takedowns WHERE is_test_data=1)
           OR ref_url LIKE 'case:%' AND CAST(SUBSTR(ref_url, 6) AS INTEGER) IN (SELECT id FROM cases WHERE is_test_data=1)
         )
    `).get().n,
  };
  res.json({ counts });
});

// POST /admin/alerts/bulk-acknowledge
// Body: { scope: 'test_data' | 'all_visible' | 'category:NAME' | 'severity:NAME' }
// Bulk-acknowledge alerts. "test_data" is the killer use-case after
// a deploy: ack all alerts whose ref_url points at a test takedown
// or test case (the noise from local-test takedowns + automated
// fuzz runs that we never want to see again).
router.post('/alerts/bulk-acknowledge', (req, res) => {
  const { scope } = req.body || {};
  if (!scope) return res.status(400).json({ error: 'MISSING_SCOPE' });
  const { db } = require('../config/db');
  let updated = 0;
  if (scope === 'test_data') {
    // Ack all unack alerts whose target is a test takedown or test case.
    const result = db.prepare(`
      UPDATE alerts SET acknowledged = 1, ack_by = ?, ack_at = ?
       WHERE acknowledged = 0
         AND (
           (ref_url LIKE 'takedown:%' AND CAST(SUBSTR(ref_url, 10) AS INTEGER) IN (SELECT id FROM takedowns WHERE is_test_data=1))
           OR (ref_url LIKE 'case:%' AND CAST(SUBSTR(ref_url, 6) AS INTEGER) IN (SELECT id FROM cases WHERE is_test_data=1))
         )
    `).run(req.user.id, Date.now());
    updated = result.changes;
  } else if (scope === 'all_visible') {
    // Ack everything currently unack. Use carefully — there's no
    // undo and you might be hiding a real critical.
    const result = db.prepare(`UPDATE alerts SET acknowledged = 1, ack_by = ?, ack_at = ? WHERE acknowledged = 0`)
      .run(req.user.id, Date.now());
    updated = result.changes;
  } else if (scope.startsWith('category:')) {
    const cat = scope.slice(9);
    const r = db.prepare(`UPDATE alerts SET acknowledged = 1, ack_by = ?, ack_at = ? WHERE acknowledged = 0 AND category = ?`)
      .run(req.user.id, Date.now(), cat);
    updated = r.changes;
  } else if (scope.startsWith('severity:')) {
    const sev = scope.slice(9);
    const r = db.prepare(`UPDATE alerts SET acknowledged = 1, ack_by = ?, ack_at = ? WHERE acknowledged = 0 AND severity = ?`)
      .run(req.user.id, Date.now(), sev);
    updated = r.changes;
  } else {
    return res.status(400).json({ error: 'UNKNOWN_SCOPE', allowed: ['test_data', 'all_visible', 'category:NAME', 'severity:NAME'] });
  }
  try {
    Audit.log(req.user, 'admin.alerts.bulk_ack', null, { scope, updated });
  } catch (_) {}
  res.json({ ok: true, scope, updated });
});

// POST /admin/test-data/flag — toggle is_test_data on a row.
// Body: { table: 'users'|'cases'|'takedowns', id, is_test_data: bool }
// Lets the operator fix misclassifications from the UI without
// having to ssh in and run SQL.
router.post('/test-data/flag', (req, res) => {
  const { table, id, is_test_data } = req.body || {};
  if (!['users', 'cases', 'takedowns'].includes(table)) return res.status(400).json({ error: 'BAD_TABLE' });
  if (!Number.isInteger(id)) return res.status(400).json({ error: 'BAD_ID' });
  const flag = is_test_data ? 1 : 0;
  const { db } = require('../config/db');
  db.prepare(`UPDATE ${table} SET is_test_data = ? WHERE id = ?`).run(flag, id);
  try { Audit.log(req.user, 'admin.test_data.flag', `${table}:${id}`, { is_test_data: flag }); } catch (_) {}
  // If we just flagged a case test, propagate to its takedowns.
  if (table === 'cases' && flag === 1) {
    db.prepare(`UPDATE takedowns SET is_test_data = 1 WHERE case_id = ? AND is_test_data = 0`).run(id);
  }
  // If we just flagged a user test, propagate to their cases + takedowns.
  if (table === 'users' && flag === 1) {
    const userId = id;
    db.prepare(`UPDATE cases SET is_test_data = 1 WHERE user_id = ? AND is_test_data = 0`).run(userId);
    db.prepare(`UPDATE takedowns SET is_test_data = 1 WHERE user_id = ? AND is_test_data = 0`).run(userId);
  }
  res.json({ ok: true, table, id, is_test_data: flag });
});

// ---- Trusted devices (the "remember this device" cookies) ----
// Superadmin can list + revoke devices that have a valid
// trusted_device cookie. Revoke is per-device or all-at-once.
const TrustedDevice = require('../models/trustedDevice');

router.get('/trusted-devices', (req, res) => {
  const devices = TrustedDevice.listForUser(req.user.id);
  // Never return token_hash (sensitive). UI just needs UA + IP + dates.
  res.json({ devices: devices.map(d => ({
    id: d.id,
    user_agent: d.user_agent,
    ip_first_seen: d.ip_first_seen,
    last_used_at: d.last_used_at,
    created_at: d.created_at,
    expires_at: d.expires_at,
    is_current: false,   // set by client from comparing cookies; server can't tell
  })) });
});

router.delete('/trusted-devices/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
  const r = TrustedDevice.remove(id, req.user.id);
  try { Audit.log(req.user, 'admin.trusted_device.revoke', `user:${req.user.id}`, { device_id: id }); } catch (_) {}
  res.json({ ok: true, revoked: r.changes });
});

router.delete('/trusted-devices', (req, res) => {
  const r = TrustedDevice.removeAllForUser(req.user.id);
  try { Audit.log(req.user, 'admin.trusted_device.revoke_all', `user:${req.user.id}`, { revoked: r.changes }); } catch (_) {}
  // Also clear the cookie on this response so the operator's current
  // browser doesn't keep using the (now-stale) token.
  try {
    const trustedDevice = require('../services/trustedDevice');
    trustedDevice.clearCookie(res);
  } catch (_) {}
  res.json({ ok: true, revoked: r.changes });
});

router.post('/alerts/:id/acknowledge', (req, res) => {
  const a = alertBus.getById(parseInt(req.params.id, 10));
  if (!a) return res.status(404).json({ error: 'NOT_FOUND' });
  const updated = alertBus.acknowledge(a.id, req.user.id);
  Audit.log(req.user, 'admin.alert.acknowledge', `alert:${a.id}`, { ref: a.ref, severity: a.severity });
  res.json({ ok: true, alert: updated });
});

router.post('/alerts/test-failure', async (req, res) => {
  // Inject a synthetic failure to exercise the alert pipeline end-to-end
  // without burning real quota. Use for operator training.
  const r = alertBus.raise({
    severity: 'warn',
    category: 'dispatch_fail',
    service: 'resend',
    title: 'Test alert: synthetic dispatch failure',
    detail: {
      injected_by: req.user.email,
      hint: 'If you received an email and see this in /admin/alerts, the pipeline works.',
    },
    ref_url: 'test:injection',
    dedupe_key: 'test_injection',
  });
  res.json({ ok: true, alert: r.alert, deduped: r.deduped });
});

router.get('/alert-prefs', (req, res) => {
  res.json({ prefs: alertBus.getAllPrefs() });
});

router.put('/alert-prefs', (req, res) => {
  const updates = req.body || {};
  for (const [k, v] of Object.entries(updates)) {
    if (typeof v === 'string' || typeof v === 'number') {
      alertBus.setPref(k, String(v));
    }
  }
  Audit.log(req.user, 'admin.alert_prefs.update', null, updates);
  res.json({ ok: true, prefs: alertBus.getAllPrefs() });
});

// ================= Production-pending takedowns =================
//
// Rows created by scripts/ingest-nisali-sites.js (or any future bulk import)
// sit at status='production_pending'. The operator reviews them in
// /admin, then clicks "Send all" (or per-row) to actually fire the emails
// once the real Resend/Scrape.do/MiniMax keys are configured.

router.get('/production-pending', (req, res) => {
  const rows = db.prepare(`
    SELECT t.*, c.victim_name, u.email AS user_email, u.name AS user_name
      FROM takedowns t
      JOIN cases  c ON c.id = t.case_id
      JOIN users  u ON u.id = t.user_id
     WHERE t.status = 'production_pending'
     ORDER BY t.created_at DESC
  `).all();
  res.json({ takedowns: rows, count: rows.length });
});

/**
 * Send ONE production-pending takedown. Returns a structured result so
 * callers (single-send route + send-all bulk route) can format their
 * own response without re-running the side-effects.
 *
 *   { ok: true,  takedown_id, status, next_escalate_at, simulated? }
 *   { ok: false, status: <http>, code, message }
 *
 * The single-send HTTP route maps these to HTTP responses; send-all
 * collects ok/not-ok counts and returns one aggregate response.
 */
async function sendOneProductionTakedown(td, actorUser) {
  const validator = require('validator');
  if (!td) return { ok: false, status: 404, code: 'NOT_FOUND', message: 'Takedown not found' };
  if (td.status !== 'production_pending') {
    return { ok: false, status: 400, code: 'NOT_PENDING', message: `Takedown status is ${td.status}, not production_pending` };
  }
  if (!td.contact_email) return { ok: false, status: 400, code: 'NO_CONTACT', message: 'No contact email on this takedown' };
  if (!validator.isEmail(td.contact_email)) {
    return { ok: false, status: 400, code: 'INVALID_EMAIL', message: `Refusing to send to non-email address: ${td.contact_email}` };
  }
  // Reserved TLDs and well-known test/example domains — we should never
  // send real takedown notices here.
  const lc = td.contact_email.toLowerCase();
  if (/(?:@|\.)(example|test|invalid|localhost|example\.com|example\.org|example\.net)$/i.test(lc)) {
    return { ok: false, status: 400, code: 'TEST_DOMAIN', message: `Refusing to send to a reserved/test domain: ${td.contact_email}` };
  }
  // Test mode: skip the real Resend call. The full audit + escalation
  // chain still gets exercised, but no email is dispatched to a
  // possibly-bogus or unintended recipient. This is a developer tool —
  // production NEVER has NCII_DEEP_DISCOVERY_TEST_MODE=1, and a
  // production boot with it set is refused by assertProductionEnv.
  if (process.env.NCII_DEEP_DISCOVERY_TEST_MODE === '1') {
    Takedown.markAsSent(td.id);
    const next = Date.now() + 30 * 60 * 60 * 1000;
    db.prepare('UPDATE takedowns SET next_escalate_at = ? WHERE id = ?').run(next, td.id);
    Audit.log(actorUser, 'takedown.production_dispatch.test_mode', `takedown:${td.id}`, { to: td.contact_email, domain: td.domain, simulated: true });
    return { ok: true, takedown_id: td.id, status: 'sent', next_escalate_at: next, simulated: true };
  }
  // Dispatch the actual email now (production keys must be configured)
  try {
    await email.sendTakedownEmail({ to: td.contact_email, subject: td.email_subject, body: td.email_body });
    Takedown.markAsSent(td.id);
    // Set the 30h escalation window
    const next = Date.now() + 30 * 60 * 60 * 1000;
    db.prepare('UPDATE takedowns SET next_escalate_at = ? WHERE id = ?').run(next, td.id);
    Audit.log(actorUser, 'takedown.production_dispatch', `takedown:${td.id}`, { to: td.contact_email, domain: td.domain });
    // Fan out to all configured webhooks (Slack/Discord/Teams/etc.)
    try {
      const notifier = require('../services/notifier');
      notifier.notify('takedown.sent', {
        takedown_id: td.id, case_id: td.case_id, domain: td.domain,
        contact: td.contact_email, subject: td.email_subject,
        url: td.url, sent_at: Date.now(),
      });
    } catch (_) { /* best-effort */ }
    alertBus.raise({
      severity: 'info', category: 'other',
      title: `Takedown sent (production): ${td.domain}`,
      detail: { takedown_id: td.id, case_id: td.case_id, to: td.contact_email, next_escalate_at: next },
      ref_url: `takedown:${td.id}`,
      dedupe_key: `prod_send:${td.id}`,
    });
    // Auto-learner: a successful dispatch is a positive signal — the
    // address at least got past Resend. Mark as attempted so future
    // lookups prefer it. A subsequent "responded" / "removed" status
    // (set elsewhere) will upgrade it to observed_success.
    try {
      const contactsRepo = require('../services/platformContactsRepo');
      contactsRepo.recordDiscovery({
        domain: td.domain, contact: td.contact_email,
        source: 'attempted', confidence: 0.5,
        notes: `Sent on ${new Date().toISOString()}, awaiting response.`,
      });
    } catch (_) {}
    return { ok: true, takedown_id: td.id, status: 'sent', next_escalate_at: next };
  } catch (e) {
    Takedown.updateStatus(td.id, 'failed', { error: e.message });
    // Auto-learner: a failure (Resend 4xx, 5xx, hard-bounce) is a
    // negative signal. Downgrade this address in the learned contact
    // map so the system picks an alternative next time.
    try {
      const contactsRepo = require('../services/platformContactsRepo');
      contactsRepo.recordFailure({ domain: td.domain, contact: td.contact_email, reason: e.message });
    } catch (_) {}
    alertBus.raise({
      severity: 'error', category: 'dispatch_fail', service: 'resend',
      title: `Production dispatch failed: ${td.domain}`,
      detail: { takedown_id: td.id, error: e.message },
      ref_url: `takedown:${td.id}`, dedupe_key: `prod_send_fail:${td.id}`,
    });
    return { ok: false, status: 502, code: 'DISPATCH_FAILED', message: e.message };
  }
}

router.post('/production-pending/:id/send', async (req, res) => {
  const td = Takedown.getById(parseInt(req.params.id, 10));
  const result = await sendOneProductionTakedown(td, req.user);
  if (!result.ok) {
    return res.status(result.status).json({ error: result.code, message: result.message, status: td && td.status });
  }
  // Preserve the original response shape for the single-send route so
  // the existing UI keeps working without changes.
  const body = {
    ok: true,
    takedown_id: result.takedown_id,
    status: result.status,
    next_escalate_at: result.next_escalate_at,
  };
  if (result.simulated) {
    body.simulated = true;
    body.message = 'Test mode: no real email sent. Audit + 30h escalation window applied.';
  }
  res.json(body);
});

router.post('/production-pending/send-all', async (req, res) => {
  const rows = db.prepare(`SELECT id FROM takedowns WHERE status = 'production_pending' ORDER BY created_at ASC`).all();
  let sent = 0, failed = 0;
  const errors = [];
  for (const r of rows) {
    try {
      const td = Takedown.getById(r.id);
      const result = await sendOneProductionTakedown(td, req.user);
      if (result.ok) sent++;
      else { failed++; errors.push({ id: r.id, code: result.code, message: result.message }); }
    } catch (e) {
      failed++;
      errors.push({ id: r.id, code: 'UNEXPECTED', message: e.message });
    }
    // Be polite to Resend
    await new Promise(resolve => setTimeout(resolve, 300));
  }
  Audit.log(req.user, 'takedown.production_dispatch_all', null, { sent, failed, total: rows.length });
  res.json({ ok: true, sent, failed, total: rows.length, errors });
});

module.exports = router;
