import { randomUUID } from 'node:crypto'
import { Resend } from 'resend'
import { requireAdminAuth } from '../auth.js'
import { escapeHtml, splitName } from '../helpers.js'
import {
  MAX_CONTACT_SUBMISSIONS,
  MAX_QUESTIONS,
  USE_RESEND_AUTOMATION_WELCOME,
  DEFAULT_SEO,
  ADMIN_REPLY_FROM,
  ADMIN_REPLY_FROM_OPTIONS,
} from '../config.js'
import { state } from '../state.js'
import {
  queueContactSubmissionsWrite,
  queueQuestionsWrite,
  queueDraftQuestionsWrite,
  queueReplyTemplatesWrite,
  queueReplyHistoryWrite,
  queueEmailSettingsWrite,
  normalizeContactEmailStatus,
  normalizeMessageType,
  sanitizeReplyTemplates,
  sanitizeReplyHistory,
  appendAuditEntry,
  queueWebhooksWrite,
  fireWebhooks,
} from '../data.js'
import {
  noteContactEmailCooldown,
  extractTagValue,
  mapResendEventToStatus,
  extractResendMessageId,
} from '../study-helpers.js'
import {
  getResendFromAddress,
  getResendReplyToAddress,
  getResendInboxAddress,
  getAddressDomain,
  buildContactWelcomeEmailTemplate,
  buildContactAdminNotificationTemplate,
  buildAdminReplyTemplate,
  sendResendEmailWithRetry,
  syncContactToResend,
} from '../email.js'

// RFC 5322 msg-id: "<" printable-ASCII-no-whitespace ">"
const MESSAGE_ID_RE = /^<[\x21-\x7E]+>$/

function upsertContactEmailStatus(submissionId, stream, patch) {
  if (!submissionId || typeof submissionId !== 'string') return
  if (!stream || typeof stream !== 'string') return
  const at = typeof patch?.lastEventAt === 'string' ? patch.lastEventAt : new Date().toISOString()
  let updated = false

  state.contactSubmissions = state.contactSubmissions.map(submission => {
    if (submission.id !== submissionId) return submission
    const next = normalizeContactEmailStatus(submission.emailStatus, submission.subscribe === true)
    const current = next[stream] ?? { status: 'pending', lastEventAt: null, lastEventType: null, resendEmailId: null, error: null }
    next[stream] = { ...current, ...patch, lastEventAt: at }
    updated = true
    return { ...submission, emailStatus: next }
  })

  if (updated) queueContactSubmissionsWrite()
}

function registerResendMessageForSubmission(submissionId, stream, sendResult) {
  const resendMessageId = extractResendMessageId(sendResult)
  if (!resendMessageId || !submissionId || !stream) return
  state.resendEmailSubmissionIndex.set(resendMessageId, { submissionId, stream })
  // Trim the index when it grows large; oldest entries are least likely to receive webhooks
  if (state.resendEmailSubmissionIndex.size > 2000) {
    const firstKey = state.resendEmailSubmissionIndex.keys().next().value
    state.resendEmailSubmissionIndex.delete(firstKey)
  }
  upsertContactEmailStatus(submissionId, stream, { resendEmailId: resendMessageId })
}

function shouldSendWelcomeEmail({ subscribe }) {
  return subscribe === true
}

const contactHits = new Map()

function contactRateLimit(req, res, next) {
  const ip = req.ip ?? 'unknown'
  const now = Date.now()
  const windowMs = 10 * 60 * 1000
  const entry = contactHits.get(ip) ?? { count: 0, start: now }
  if (now - entry.start > windowMs) { entry.count = 0; entry.start = now }
  entry.count += 1
  contactHits.set(ip, entry)
  // Prune stale entries to prevent unbounded growth
  if (contactHits.size > 5000) {
    const cutoff = now - windowMs
    for (const [k, v] of contactHits) { if (v.start < cutoff) contactHits.delete(k) }
  }
  if (entry.count > 5) {
    res.status(429).json({ message: 'Too many messages. Please wait a few minutes.' })
    return
  }
  next()
}

export function register(app) {
  app.post('/api/contact', contactRateLimit, async (req, res) => {
    try {
      const { firstName, lastName, email, message, messageType, subscribe, notifyOnAnswer, _honey } = req.body ?? {}

      if (_honey) { res.json({ ok: true }); return }

      if (!firstName || typeof firstName !== 'string' || firstName.trim().length < 1 || firstName.trim().length > 100) {
        res.status(400).json({ message: 'First name is required.' }); return
      }
      if (lastName !== undefined && lastName !== null && (typeof lastName !== 'string' || lastName.trim().length > 100)) {
        res.status(400).json({ message: 'Last name is too long.' }); return
      }
      if (!email || typeof email !== 'string' || !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(email.trim())) {
        res.status(400).json({ message: 'A valid email address is required.' }); return
      }
      if (!message || typeof message !== 'string' || message.trim().length < 5 || message.trim().length > 3000) {
        res.status(400).json({ message: 'Message must be between 5 and 3000 characters.' }); return
      }

      if (!process.env.RESEND_API_KEY) {
        console.error('[contact] RESEND_API_KEY env var not set')
        res.status(503).json({ message: 'The contact form is not yet configured on the server.' }); return
      }

      const trimmedName = [firstName.trim(), typeof lastName === 'string' ? lastName.trim() : ''].filter(Boolean).join(' ')
      const trimmedEmail = email.trim()
      const trimmedMessage = message.trim()
      const normalizedMessageType = normalizeMessageType(messageType)
      const cooldown = noteContactEmailCooldown(trimmedEmail)
      if (!cooldown.ok) {
        const retryAfterSeconds = Math.max(1, Math.ceil(cooldown.retryAfterMs / 1000))
        res.status(429).json({ message: `Please wait ${retryAfterSeconds}s before sending another message from this email.` }); return
      }

      const submittedAt = new Date().toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })
      const shouldSendWelcome = shouldSendWelcomeEmail({ subscribe })

      const wantsWelcome = subscribe === true
      const submission = {
        id: randomUUID(),
        submittedAt: new Date().toISOString(),
        name: trimmedName,
        email: trimmedEmail,
        message: trimmedMessage,
        messageType: normalizedMessageType,
        subscribe: wantsWelcome,
        archived: false,
        emailStatus: normalizeContactEmailStatus(null, wantsWelcome),
      }
      state.contactSubmissions.unshift(submission)
      state.contactSubmissions = state.contactSubmissions.slice(0, MAX_CONTACT_SUBMISSIONS)
      queueContactSubmissionsWrite()
      fireWebhooks('contact.new', { name: trimmedName, email: trimmedEmail, message: trimmedMessage, messageType: normalizedMessageType, submittedAt: submission.submittedAt })

      if (normalizedMessageType === 'question') {
        const question = {
          id: randomUUID(),
          submittedAt: new Date().toISOString(),
          firstName: splitName(trimmedName).firstName,
          email: trimmedEmail,
          question: trimmedMessage,
          answer: '',
          answeredAt: null,
          isApproved: false,
          approvedAt: null,
          notifyOnAnswer: notifyOnAnswer === true,
        }
        state.questions.unshift(question)
        state.questions = state.questions.slice(0, MAX_QUESTIONS)
        if (state.draftQuestions !== null) {
          state.draftQuestions.unshift(question)
          state.draftQuestions = state.draftQuestions.slice(0, MAX_QUESTIONS)
          queueDraftQuestionsWrite()
        }
        queueQuestionsWrite()
      }

      const resend = new Resend(process.env.RESEND_API_KEY)
      const adminInbox = getResendInboxAddress()
      const replyToAddress = getResendReplyToAddress()
      const fromAddress = getResendFromAddress()
      const safeMessageTypeTag = normalizedMessageType.replace(/[^a-z0-9_-]/gi, '-').toLowerCase()
      const adminTemplate = buildContactAdminNotificationTemplate({ normalizedMessageType, trimmedName, trimmedEmail, submittedAt, trimmedMessage })
      let welcomeSent = false

      if (subscribe === true) {
        await syncContactToResend(trimmedName, trimmedEmail)
      }

      if (shouldSendWelcome && !USE_RESEND_AUTOMATION_WELCOME) {
        const greetingName = splitName(trimmedName).firstName?.trim() ?? ''
        let publishedSiteContent = state.cachedSiteContent
        if (!publishedSiteContent) {
          try {
            const { loadSiteContentFile } = await import('../data.js')
            const { DATA_FILE } = await import('../config.js')
            const published = await loadSiteContentFile(DATA_FILE)
            publishedSiteContent = published?.siteContent ?? null
          } catch { publishedSiteContent = null }
        }

        const emailConfig = publishedSiteContent ?? {}
        const welcomeBaseUrl = typeof emailConfig?.seo?.canonicalUrl === 'string' && emailConfig.seo.canonicalUrl.trim()
          ? emailConfig.seo.canonicalUrl.trim()
          : DEFAULT_SEO.canonicalUrl
        const welcomeSubject = process.env.RESEND_WELCOME_SUBJECT ?? emailConfig.welcomeEmailSubject ?? 'Welcome to Verse by Verse with Nate'
        const welcomeGreetingPrefix = emailConfig.welcomeEmailGreetingPrefix?.trim() || "Glad you're here"
        const { buildAbsoluteUrl } = await import('../helpers.js')
        const welcomeTemplate = buildContactWelcomeEmailTemplate({
          greetingName,
          welcomeGreetingPrefix,
          welcomeIntro: emailConfig.welcomeEmailIntro?.trim() || 'Thanks for subscribing to Verse by Verse with Nate - a Bible teaching podcast where we slow down, dig into the text, and pull out the nuggets God has for us word by word.',
          welcomeCurrentSeries: emailConfig.welcomeEmailCurrentSeries?.trim() || "Right now we're working through the book of Titus - a short letter packed with practical wisdom about grace, godliness, and what the Christian life looks like when it's rooted in sound doctrine.",
          welcomeStartHereTitle: emailConfig.welcomeEmailStartHereTitle?.trim() || 'Episode 1 - Introduction to Titus',
          welcomeStartHereSummary: emailConfig.welcomeEmailStartHereSummary?.trim() || 'Who wrote it, who received it, and why it still matters.',
          welcomeExpect1: emailConfig.welcomeEmailWhatToExpect1?.trim() || 'Verse-by-verse teaching - we go slow and let the text speak for itself.',
          welcomeExpect2: emailConfig.welcomeEmailWhatToExpect2?.trim() || 'Greek word studies - the kind that open up meaning without being a lecture.',
          welcomeExpect3: emailConfig.welcomeEmailWhatToExpect3?.trim() || 'New episodes + study notes delivered right to your inbox.',
          welcomeScripture: emailConfig.welcomeEmailScripture?.trim() || 'For the grace of God has appeared, bringing salvation to all people.',
          welcomeScriptureRef: emailConfig.welcomeEmailScriptureRef?.trim() || 'Titus 2:11 - BSB',
          welcomeSignoff: emailConfig.welcomeEmailSignoff?.trim() || 'Grace and peace,\nNate',
          welcomeSpotifyUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_SPOTIFY_URL ?? emailConfig.welcomeEmailSpotifyUrl ?? '/spotify'),
          welcomeAppleUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_APPLE_URL ?? emailConfig.welcomeEmailAppleUrl ?? '/apple'),
          welcomeAmazonUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_AMAZON_URL ?? emailConfig.welcomeEmailAmazonUrl ?? '/amazon'),
          welcomeWebsiteUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_WEBSITE_URL ?? emailConfig.welcomeEmailWebsiteUrl ?? '/'),
          welcomeEpisodeUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_EPISODE_URL ?? emailConfig.welcomeEmailStartHereUrl ?? '/start-here'),
          welcomeImageUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_IMAGE_URL ?? emailConfig.welcomeEmailImageUrl ?? '/images/podcast-art.jpeg'),
          welcomeSpotifyBtnLabel: emailConfig.welcomeEmailSpotifyBtnLabel?.trim() || 'Listen on Spotify',
          welcomeAppleBtnLabel: emailConfig.welcomeEmailAppleBtnLabel?.trim() || 'Apple Podcasts',
          welcomeStartHereLinkLabel: emailConfig.welcomeEmailStartHereLinkLabel?.trim() || 'Open Start Here page',
        })

        try {
          const welcomeSendResult = await sendResendEmailWithRetry({
            resend,
            context: 'contact-welcome',
            payload: {
              from: fromAddress,
              to: [trimmedEmail],
              replyTo: replyToAddress,
              subject: welcomeSubject,
              tags: [
                { name: 'flow', value: 'contact-welcome' },
                { name: 'message_type', value: safeMessageTypeTag },
                { name: 'submission_id', value: submission.id },
              ],
              headers: {
                'List-Unsubscribe': `<mailto:${replyToAddress}?subject=Unsubscribe>`,
                'X-Contact-Submission-Id': submission.id,
              },
              text: welcomeTemplate.text,
              html: welcomeTemplate.html,
            },
          })
          registerResendMessageForSubmission(submission.id, 'welcome', welcomeSendResult)
          upsertContactEmailStatus(submission.id, 'welcome', { status: 'sent', lastEventType: 'email.sent', error: null })
          welcomeSent = true
        } catch (welcomeErr) {
          upsertContactEmailStatus(submission.id, 'welcome', {
            status: 'failed',
            lastEventType: 'email.failed',
            error: String(welcomeErr?.message ?? welcomeErr ?? 'unknown error').slice(0, 600),
          })
          console.error('[contact] welcome email failed:', welcomeErr)
          // Submission is already saved — don't 500 the user; fall through to admin notification.
        }
      } else if (shouldSendWelcome && USE_RESEND_AUTOMATION_WELCOME) {
        upsertContactEmailStatus(submission.id, 'welcome', { status: 'automation-enabled', lastEventType: 'email.automation.enabled', error: null })
      }

      try {
        const adminSendResult = await sendResendEmailWithRetry({
          resend,
          context: 'contact-admin-notification',
          payload: {
            from: fromAddress,
            to: [adminInbox],
            replyTo: trimmedEmail,
            subject: adminTemplate.subject,
            tags: [
              { name: 'flow', value: 'contact-admin' },
              { name: 'message_type', value: safeMessageTypeTag },
              { name: 'submission_id', value: submission.id },
            ],
            headers: { 'X-Contact-Submission-Id': submission.id },
            text: adminTemplate.text,
            html: adminTemplate.html,
          },
        })
        registerResendMessageForSubmission(submission.id, 'adminNotification', adminSendResult)
        upsertContactEmailStatus(submission.id, 'adminNotification', { status: 'sent', lastEventType: 'email.sent', error: null })
      } catch (adminSendErr) {
        upsertContactEmailStatus(submission.id, 'adminNotification', {
          status: 'failed',
          lastEventType: 'email.failed',
          error: String(adminSendErr?.message ?? adminSendErr ?? 'unknown error').slice(0, 600),
        })
        console.error('[contact] admin notification email failed:', adminSendErr)
        // Submission is already saved — don't 500 the user.
      }

      res.json({ ok: true, welcomeSent, welcomeHandledByAutomation: shouldSendWelcome && USE_RESEND_AUTOMATION_WELCOME })
    } catch (err) {
      console.error('[contact] send error:', err)
      res.status(500).json({ message: 'Failed to send your message. Please try again or email us directly.' })
    }
  })

  app.post('/api/resend/webhook', (req, res) => {
    const expectedToken = typeof process.env.RESEND_WEBHOOK_TOKEN === 'string' ? process.env.RESEND_WEBHOOK_TOKEN.trim() : ''
    if (!expectedToken) {
      res.status(503).json({ message: 'Webhook token is not configured.' }); return
    }

    const providedToken = (req.get('x-webhook-token') || '').trim()
      || (req.get('x-resend-webhook-token') || '').trim()
      || String(req.query?.token || '').trim()
      || (req.get('authorization') || '').replace(/^Bearer\s+/i, '').trim()

    if (!providedToken || providedToken !== expectedToken) {
      res.status(401).json({ message: 'Unauthorized webhook.' }); return
    }

    const body = req.body && typeof req.body === 'object' ? req.body : {}
    const eventType = typeof body.type === 'string' ? body.type.trim() : ''
    const data = body.data && typeof body.data === 'object' ? body.data : {}
    const tags = Array.isArray(data.tags) ? data.tags : []

    const resendMessageId = (
      typeof data.email_id === 'string' && data.email_id.trim()
        ? data.email_id.trim()
        : (typeof data.emailId === 'string' && data.emailId.trim()
          ? data.emailId.trim()
          : (typeof data.id === 'string' && data.id.trim() ? data.id.trim() : ''))
    )

    const indexed = resendMessageId ? state.resendEmailSubmissionIndex.get(resendMessageId) : null
    const taggedSubmissionId = extractTagValue(tags, 'submission_id')
    const submissionId = indexed?.submissionId || taggedSubmissionId

    const flow = extractTagValue(tags, 'flow')
    const stream = indexed?.stream
      || (flow === 'contact-welcome' ? 'welcome' : '')
      || (flow === 'contact-admin' ? 'adminNotification' : '')
      || (flow === 'admin-reply' ? 'adminReply' : '')

    if (!submissionId || !stream) {
      res.json({ ok: true, ignored: true }); return
    }

    upsertContactEmailStatus(submissionId, stream, {
      status: mapResendEventToStatus(eventType),
      lastEventType: eventType || 'webhook.event',
      resendEmailId: resendMessageId || null,
      error: typeof data?.message === 'string' ? data.message.slice(0, 600) : null,
    })

    res.json({ ok: true })
  })

  app.get('/api/admin-contact-email-health', requireAdminAuth, (_req, res) => {
    const fromAddress = getResendFromAddress()
    const replyToAddress = getResendReplyToAddress()
    const fromDomain = getAddressDomain(fromAddress)
    const replyDomain = getAddressDomain(replyToAddress)
    const warnings = []

    if (!process.env.RESEND_API_KEY) warnings.push('RESEND_API_KEY is missing.')
    if (!fromDomain) warnings.push('RESEND_FROM is missing or invalid.')
    if (fromDomain.endsWith('resend.dev')) warnings.push('RESEND_FROM uses resend.dev. Prefer a verified custom domain.')
    if (fromDomain && replyDomain && fromDomain !== replyDomain) warnings.push('Sender and reply-to domains are different.')
    if (!process.env.RESEND_WEBHOOK_TOKEN) warnings.push('RESEND_WEBHOOK_TOKEN is not configured.')
    warnings.push('Verify SPF, DKIM, and DMARC for the sender domain.')

    const recent = state.contactSubmissions.slice(0, 300)
    const failed = recent.filter(item => {
      const status = normalizeContactEmailStatus(item.emailStatus, item.subscribe === true)
      return ['failed', 'bounced', 'complained'].includes(status.welcome.status)
        || ['failed', 'bounced', 'complained'].includes(status.adminNotification.status)
        || ['failed', 'bounced', 'complained'].includes(status.adminReply.status)
    }).length

    res.json({
      resendApiConfigured: Boolean(process.env.RESEND_API_KEY),
      webhookConfigured: Boolean(process.env.RESEND_WEBHOOK_TOKEN),
      fromAddress,
      replyToAddress,
      fromDomain,
      replyDomain,
      warnings,
      recentSubmissionFailures: failed,
      trackedSubmissions: recent.length,
    })
  })

  app.get('/api/admin-contact-submissions', requireAdminAuth, (_req, res) => {
    res.json({ submissions: state.contactSubmissions.slice(0, 300) })
  })

  app.post('/api/admin-contact-submissions/add', requireAdminAuth, (req, res) => {
    const name = typeof req.body?.name === 'string' ? req.body.name.trim() : ''
    const email = typeof req.body?.email === 'string' ? req.body.email.trim() : ''
    const notes = typeof req.body?.notes === 'string' ? req.body.notes.trim().slice(0, 2000) : ''

    if (!name && !email) {
      res.status(400).json({ message: 'Name or email is required.' }); return
    }
    if (email && !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(email)) {
      res.status(400).json({ message: 'Invalid email address.' }); return
    }

    const submission = {
      id: randomUUID(),
      submittedAt: new Date().toISOString(),
      name: name.slice(0, 200),
      email,
      message: '',
      messageType: 'general',
      subscribe: false,
      archived: false,
      source: 'manual',
      notes,
      emailStatus: normalizeContactEmailStatus(null, false),
    }

    state.contactSubmissions.unshift(submission)
    state.contactSubmissions = state.contactSubmissions.slice(0, MAX_CONTACT_SUBMISSIONS)
    queueContactSubmissionsWrite()
    res.json({ ok: true, submission })
  })

  app.patch('/api/admin-contact-submissions/:id', requireAdminAuth, (req, res) => {
    const { id } = req.params
    if (typeof id !== 'string' || !id.trim()) {
      res.status(400).json({ message: 'Invalid submission id.' }); return
    }

    const patch = {}
    if (typeof req.body?.archived === 'boolean') patch.archived = req.body.archived
    if (typeof req.body?.starred === 'boolean') patch.starred = req.body.starred
    if (typeof req.body?.name === 'string') patch.name = req.body.name.trim().slice(0, 200)
    if (typeof req.body?.notes === 'string') patch.notes = req.body.notes.trim().slice(0, 2000)
    if (Array.isArray(req.body?.tags)) {
      patch.tags = [...new Set(req.body.tags.filter(t => typeof t === 'string' && t.trim()).map(t => t.trim().slice(0, 50)))].slice(0, 20)
    }
    if ('snoozedUntil' in (req.body ?? {})) {
      const v = req.body.snoozedUntil
      patch.snoozedUntil = v === null ? null : (typeof v === 'string' && !isNaN(Date.parse(v)) ? v : undefined)
      if (patch.snoozedUntil === undefined) delete patch.snoozedUntil
    }

    let found = false
    state.contactSubmissions = state.contactSubmissions.map(item => {
      if (item.id !== id) return item
      found = true
      return { ...item, ...patch }
    })

    if (!found) {
      res.status(404).json({ message: 'Submission not found.' }); return
    }

    queueContactSubmissionsWrite()
    res.json({ ok: true })
  })

  app.delete('/api/admin-contact-submissions/:id', requireAdminAuth, (req, res) => {
    const { id } = req.params
    if (typeof id !== 'string' || !id.trim()) {
      res.status(400).json({ message: 'Invalid submission id.' }); return
    }

    const startLength = state.contactSubmissions.length
    state.contactSubmissions = state.contactSubmissions.filter(item => item.id !== id)
    if (state.contactSubmissions.length === startLength) {
      res.status(404).json({ message: 'Submission not found.' }); return
    }

    queueContactSubmissionsWrite()
    appendAuditEntry('submission-deleted', `id: ${id}`)
    res.json({ ok: true })
  })

  app.post('/api/admin-contact-submissions/bulk', requireAdminAuth, (req, res) => {
    const { ids, action } = req.body ?? {}
    if (!Array.isArray(ids) || !['archive', 'unarchive', 'delete', 'star', 'unstar'].includes(action)) {
      res.status(400).json({ message: 'Invalid bulk action.' }); return
    }
    const idSet = new Set(ids.filter(id => typeof id === 'string'))
    if (idSet.size === 0) { res.json({ ok: true, affected: 0 }); return }

    let affected = 0
    if (action === 'delete') {
      const before = state.contactSubmissions.length
      state.contactSubmissions = state.contactSubmissions.filter(s => !idSet.has(s.id))
      affected = before - state.contactSubmissions.length
    } else {
      const patch = action === 'archive' ? { archived: true }
        : action === 'unarchive' ? { archived: false }
        : action === 'star' ? { starred: true }
        : { starred: false }
      state.contactSubmissions = state.contactSubmissions.map(s => {
        if (!idSet.has(s.id)) return s
        affected++
        return { ...s, ...patch }
      })
    }
    queueContactSubmissionsWrite()
    res.json({ ok: true, affected })
  })

  app.post('/api/admin-contacts/merge', requireAdminAuth, (req, res) => {
    const keepEmail = typeof req.body?.keepEmail === 'string' ? req.body.keepEmail.trim().toLowerCase() : ''
    const mergeEmail = typeof req.body?.mergeEmail === 'string' ? req.body.mergeEmail.trim().toLowerCase() : ''
    if (!keepEmail || !mergeEmail || keepEmail === mergeEmail) {
      res.status(400).json({ message: 'keepEmail and mergeEmail must be different non-empty addresses.' }); return
    }
    let affected = 0
    state.contactSubmissions = state.contactSubmissions.map(s => {
      if ((s.email ?? '').trim().toLowerCase() !== mergeEmail) return s
      affected++
      return { ...s, email: keepEmail }
    })
    queueContactSubmissionsWrite()
    appendAuditEntry('contacts-merged', `${mergeEmail} → ${keepEmail} (${affected} submission${affected !== 1 ? 's' : ''})`)
    res.json({ ok: true, affected })
  })

  app.post('/api/admin-contacts/import', requireAdminAuth, (req, res) => {
    const rows = req.body?.rows
    if (!Array.isArray(rows) || rows.length === 0) {
      res.status(400).json({ message: 'rows must be a non-empty array.' }); return
    }
    const EMAIL_RE = /^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/
    let created = 0
    let skipped = 0
    const toAdd = []
    for (const row of rows.slice(0, 1000)) {
      const name = typeof row.name === 'string' ? row.name.trim().slice(0, 200) : ''
      const email = typeof row.email === 'string' ? row.email.trim().toLowerCase().slice(0, 320) : ''
      const notes = typeof row.notes === 'string' ? row.notes.trim().slice(0, 2000) : ''
      const tags = Array.isArray(row.tags)
        ? row.tags.filter(t => typeof t === 'string' && t.trim()).map(t => t.trim().slice(0, 50)).slice(0, 20)
        : (typeof row.tags === 'string' ? row.tags.split(';').map(t => t.trim()).filter(Boolean).slice(0, 20) : [])
      if (!name && !email) { skipped++; continue }
      if (email && !EMAIL_RE.test(email)) { skipped++; continue }
      toAdd.push({ name, email, notes, tags })
    }
    for (const row of toAdd) {
      const submission = {
        id: randomUUID(),
        submittedAt: new Date().toISOString(),
        name: row.name,
        email: row.email,
        message: '',
        messageType: 'general',
        subscribe: false,
        archived: false,
        source: 'manual',
        notes: row.notes,
        tags: row.tags,
        emailStatus: normalizeContactEmailStatus(null, false),
      }
      state.contactSubmissions.unshift(submission)
      created++
    }
    state.contactSubmissions = state.contactSubmissions.slice(0, MAX_CONTACT_SUBMISSIONS)
    if (created > 0) queueContactSubmissionsWrite()
    if (created > 0) appendAuditEntry('contacts-imported', `${created} contacts imported, ${skipped} skipped`)
    res.json({ ok: true, created, skipped })
  })

  app.get('/api/admin-contact-submissions/:id/attachments/:attachmentId', requireAdminAuth, (req, res) => {
    const { id, attachmentId } = req.params
    const submission = state.contactSubmissions.find(s => s.id === id)
    if (!submission) { res.status(404).send('Not found.'); return }
    const attachment = (submission.attachments ?? []).find(a => a.id === attachmentId)
    if (!attachment) { res.status(404).send('Attachment not found.'); return }
    const safe = attachment.filename.replace(/[^\w.\-]/g, '_')
    res.setHeader('Content-Disposition', `attachment; filename="${safe}"`)
    res.setHeader('Content-Type', attachment.contentType || 'application/octet-stream')
    res.send(Buffer.from(attachment.data, 'base64'))
  })

  app.get('/api/admin-reply-config', requireAdminAuth, (_req, res) => {
    res.json({
      fromEmail: getResendReplyToAddress(),
      fromIdentity: getResendFromAddress() || ADMIN_REPLY_FROM,
      resendApiConfigured: Boolean(process.env.RESEND_API_KEY),
      canSendReplies: Boolean(process.env.RESEND_API_KEY),
      note: process.env.RESEND_API_KEY
        ? 'App is configured to attempt sends through Resend. Delivery still depends on Resend sender/domain verification.'
        : 'RESEND_API_KEY is missing, so admin replies cannot be sent yet.',
    })
  })

  app.get('/api/admin-contact-reply-templates', requireAdminAuth, (_req, res) => {
    res.json({ templates: state.replyTemplates })
  })

  app.put('/api/admin-contact-reply-templates', requireAdminAuth, (req, res) => {
    const nextTemplates = sanitizeReplyTemplates(req.body?.templates)
    state.replyTemplates = nextTemplates
    queueReplyTemplatesWrite()
    res.json({ ok: true, templates: state.replyTemplates })
  })

  app.get('/api/admin-contact-reply-history', requireAdminAuth, (_req, res) => {
    res.json({ items: state.replyHistory.slice(0, 100) })
  })

  app.post('/api/admin-contact-submissions/:id/reply', requireAdminAuth, async (req, res) => {
    try {
      if (!process.env.RESEND_API_KEY) {
        res.status(503).json({ message: 'RESEND_API_KEY is not configured on the server.' }); return
      }

      const { id } = req.params
      const subject = typeof req.body?.subject === 'string' ? req.body.subject.trim() : ''
      const message = typeof req.body?.message === 'string' ? req.body.message.trim() : ''
      const requestedFrom = typeof req.body?.fromAddress === 'string' ? req.body.fromAddress.trim() : ''
      const scheduledAt = typeof req.body?.scheduledAt === 'string' && !isNaN(Date.parse(req.body.scheduledAt)) && new Date(req.body.scheduledAt) > new Date() ? req.body.scheduledAt : null

      if (!id || typeof id !== 'string') {
        res.status(400).json({ message: 'Invalid submission id.' }); return
      }
      if (!subject || subject.length > 180) {
        res.status(400).json({ message: 'Subject is required and must be 180 characters or fewer.' }); return
      }
      if (!message || message.length > 6000) {
        res.status(400).json({ message: 'Message is required and must be 6000 characters or fewer.' }); return
      }

      const submission = state.contactSubmissions.find(entry => entry.id === id)
      if (!submission) {
        res.status(404).json({ message: 'Submission not found.' }); return
      }
      if (!submission.email || !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(submission.email)) {
        res.status(400).json({ message: 'Submission does not have a valid email address.' }); return
      }

      const recipientName = splitName(submission.name).firstName || submission.name || 'friend'
      const signature = state.emailSettings?.signature ?? 'Grace and peace,\nVerse by Verse with Nate'
      const html = buildAdminReplyTemplate({ recipientName, message, signature })
      const replyToAddress = getResendReplyToAddress()
      const defaultFrom = getResendFromAddress() || ADMIN_REPLY_FROM
      const fromAddress = ADMIN_REPLY_FROM_OPTIONS.includes(requestedFrom) ? requestedFrom : defaultFrom
      const text = `Hi ${recipientName},\n\n${message}\n\n${signature}\n${replyToAddress}`
      const resend = new Resend(process.env.RESEND_API_KEY)

      const sendResult = await sendResendEmailWithRetry({
        resend,
        context: 'admin-contact-reply',
        payload: {
          from: fromAddress,
          to: [submission.email],
          subject,
          replyTo: replyToAddress,
          ...(scheduledAt ? { scheduledAt } : {}),
          tags: [
            { name: 'flow', value: 'admin-reply' },
            { name: 'message_type', value: submission.messageType ?? 'general' },
            { name: 'submission_id', value: submission.id },
          ],
          headers: {
            'X-Contact-Submission-Id': submission.id,
            ...(typeof submission.messageId === 'string' && MESSAGE_ID_RE.test(submission.messageId) ? {
              'In-Reply-To': submission.messageId,
              'References': submission.messageId,
            } : {}),
          },
          text,
          html,
        },
      })
      registerResendMessageForSubmission(submission.id, 'adminReply', sendResult)
      upsertContactEmailStatus(submission.id, 'adminReply', { status: 'sent', lastEventType: 'email.sent', error: null })

      state.replyHistory.unshift({
        id: randomUUID(),
        submissionId: submission.id,
        toEmail: submission.email,
        toName: submission.name,
        fromEmail: replyToAddress,
        subject,
        preview: message.slice(0, 500),
        sentAt: new Date().toISOString(),
        scheduledAt: scheduledAt ?? null,
      })
      state.replyHistory = state.replyHistory.slice(0, 500)
      queueReplyHistoryWrite()
      appendAuditEntry('reply-sent', `To: ${submission.email} | Subject: ${subject}${scheduledAt ? ` | Scheduled: ${scheduledAt}` : ''}`)
      fireWebhooks('reply.sent', { toEmail: submission.email, toName: submission.name, subject, sentAt: new Date().toISOString() })

      res.json({ ok: true, scheduled: Boolean(scheduledAt), scheduledAt: scheduledAt ?? null })
    } catch (err) {
      if (typeof req.params?.id === 'string' && req.params.id.trim()) {
        upsertContactEmailStatus(req.params.id.trim(), 'adminReply', {
          status: 'failed',
          lastEventType: 'email.failed',
          error: String(err?.message ?? err ?? 'unknown error').slice(0, 600),
        })
      }
      console.error('[admin-reply] send error:', err)
      res.status(500).json({ message: 'Failed to send reply email.' })
    }
  })

  app.post('/api/admin-email/compose', requireAdminAuth, async (req, res) => {
    try {
      if (!process.env.RESEND_API_KEY) {
        res.status(503).json({ message: 'RESEND_API_KEY is not configured on the server.' }); return
      }
      const to = typeof req.body?.to === 'string' ? req.body.to.trim() : ''
      const toName = typeof req.body?.toName === 'string' ? req.body.toName.trim() : ''
      const subject = typeof req.body?.subject === 'string' ? req.body.subject.trim() : ''
      const message = typeof req.body?.message === 'string' ? req.body.message.trim() : ''
      const requestedFrom = typeof req.body?.fromAddress === 'string' ? req.body.fromAddress.trim() : ''
      const scheduledAt = typeof req.body?.scheduledAt === 'string' && !isNaN(Date.parse(req.body.scheduledAt)) && new Date(req.body.scheduledAt) > new Date() ? req.body.scheduledAt : null

      if (!to || !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(to)) {
        res.status(400).json({ message: 'A valid recipient email address is required.' }); return
      }
      if (!subject || subject.length > 180) {
        res.status(400).json({ message: 'Subject is required and must be 180 characters or fewer.' }); return
      }
      if (!message || message.length > 6000) {
        res.status(400).json({ message: 'Message is required and must be 6000 characters or fewer.' }); return
      }

      const recipientName = toName ? splitName(toName).firstName || toName : 'friend'
      const signature = state.emailSettings?.signature ?? 'Grace and peace,\nVerse by Verse with Nate'
      const html = buildAdminReplyTemplate({ recipientName, message, signature })
      const defaultFrom = getResendFromAddress() || ADMIN_REPLY_FROM
      const fromAddress = ADMIN_REPLY_FROM_OPTIONS.includes(requestedFrom) ? requestedFrom : defaultFrom
      const replyToAddress = getResendReplyToAddress()
      const text = `Hi ${recipientName},\n\n${message}\n\n${signature}`
      const resend = new Resend(process.env.RESEND_API_KEY)

      await sendResendEmailWithRetry({
        resend,
        context: 'admin-compose',
        payload: {
          from: fromAddress,
          to: [to],
          subject,
          replyTo: replyToAddress,
          ...(scheduledAt ? { scheduledAt } : {}),
          tags: [{ name: 'flow', value: 'admin-reply' }],
          text,
          html,
        },
      })

      state.replyHistory.unshift({
        id: randomUUID(),
        submissionId: '',
        toEmail: to,
        toName: toName || to,
        fromEmail: replyToAddress,
        subject,
        preview: message.slice(0, 500),
        sentAt: new Date().toISOString(),
        scheduledAt: scheduledAt ?? null,
      })
      state.replyHistory = state.replyHistory.slice(0, 500)
      queueReplyHistoryWrite()
      appendAuditEntry('email-sent', `To: ${to} | Subject: ${subject}${scheduledAt ? ` | Scheduled: ${scheduledAt}` : ''}`)

      res.json({ ok: true, scheduled: Boolean(scheduledAt), scheduledAt: scheduledAt ?? null })
    } catch (err) {
      console.error('[admin-compose] send error:', err)
      res.status(500).json({ message: 'Failed to send email.' })
    }
  })

  app.get('/api/admin-email-settings', requireAdminAuth, (_req, res) => {
    res.json(state.emailSettings ?? { signature: 'Grace and peace,\nVerse by Verse with Nate' })
  })

  app.put('/api/admin-email-settings', requireAdminAuth, (req, res) => {
    const signature = typeof req.body?.signature === 'string'
      ? req.body.signature.slice(0, 1000)
      : (state.emailSettings?.signature ?? 'Grace and peace,\nVerse by Verse with Nate')
    state.emailSettings = { ...state.emailSettings, signature }
    queueEmailSettingsWrite()
    res.json({ ok: true, settings: state.emailSettings })
  })

  app.get('/api/admin-subscribers', requireAdminAuth, (_req, res) => {
    const seen = new Set()
    const subscribers = state.contactSubmissions
      .filter(entry => entry.subscribe === true && entry.email && !seen.has(entry.email) && seen.add(entry.email))
      .map(entry => ({
        name: entry.name,
        email: entry.email,
        subscribedAt: entry.submittedAt,
        source: entry.message?.startsWith('Requested') ? 'download' : 'contact-form',
      }))
      .sort((a, b) => new Date(b.subscribedAt).getTime() - new Date(a.subscribedAt).getTime())
    res.json({ subscribers, total: subscribers.length })
  })

  app.post('/api/admin-subscribers/export', requireAdminAuth, (_req, res) => {
    const seen = new Set()
    const rows = [['Name', 'Email', 'Subscribed At', 'Source']]
    state.contactSubmissions
      .filter(entry => entry.subscribe === true && entry.email && !seen.has(entry.email) && seen.add(entry.email))
      .sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime())
      .forEach(entry => {
        const source = entry.message?.startsWith('Requested') ? 'download' : 'contact-form'
        rows.push([entry.name, entry.email, entry.submittedAt, source])
      })
    const csv = rows.map(row => row.map(cell => `"${String(cell ?? '').replace(/"/g, '""')}"`).join(',')).join('\n')
    res.setHeader('Content-Type', 'text/csv')
    res.setHeader('Content-Disposition', `attachment; filename="subscribers-${new Date().toISOString().slice(0, 10)}.csv"`)
    res.send(csv)
  })

  app.get('/api/admin-audit-log', requireAdminAuth, (_req, res) => {
    res.json({ entries: state.auditLog.slice(0, 200) })
  })

  app.post('/api/admin-broadcasts/send', requireAdminAuth, async (req, res) => {
    if (!process.env.RESEND_API_KEY) { res.status(503).json({ message: 'RESEND_API_KEY is not configured.' }); return }
    const audienceId = process.env.RESEND_AUDIENCE_ID
    if (!audienceId) { res.status(503).json({ message: 'RESEND_AUDIENCE_ID is not configured.' }); return }

    const subject = typeof req.body?.subject === 'string' ? req.body.subject.trim() : ''
    const text = typeof req.body?.text === 'string' ? req.body.text.trim() : ''
    const fromLabel = typeof req.body?.from === 'string' ? req.body.from.trim() : getResendFromAddress()
    const previewText = typeof req.body?.previewText === 'string' ? req.body.previewText.trim() : ''

    if (!subject) { res.status(400).json({ message: 'Subject is required.' }); return }
    if (!text) { res.status(400).json({ message: 'Message body is required.' }); return }

    const paragraphs = text.split(/\n{2,}/).map(p => p.replace(/\n/g, '<br>')).map(p => `<p>${p}</p>`).join('')
    const html = `<!DOCTYPE html><html><body style="font-family:Georgia,serif;max-width:600px;margin:0 auto;padding:24px;color:#1a1a1a;">${paragraphs}</body></html>`

    try {
      const resend = new Resend(process.env.RESEND_API_KEY)
      const { data: created, error: createErr } = await resend.broadcasts.create({
        audienceId,
        from: fromLabel,
        subject,
        ...(previewText ? { previewText } : {}),
        html,
        text,
      })
      if (createErr || !created?.id) {
        res.status(502).json({ message: createErr?.message || 'Broadcast creation failed.' }); return
      }
      const { error: sendErr } = await resend.broadcasts.send(created.id)
      if (sendErr) { res.status(502).json({ message: sendErr.message || 'Broadcast send failed.' }); return }

      appendAuditEntry('broadcast-sent', `Subject: ${subject} | AudienceId: ${audienceId}`)
      res.json({ ok: true, broadcastId: created.id })
    } catch (err) {
      res.status(502).json({ message: String(err?.message ?? 'Broadcast failed.') })
    }
  })

  app.post('/api/admin-contacts/trigger-drip', requireAdminAuth, async (req, res) => {
    if (!process.env.RESEND_API_KEY) { res.status(503).json({ message: 'RESEND_API_KEY is not configured.' }); return }
    const automationId = process.env.RESEND_AUTOMATION_WELCOME || ''
    if (!automationId) { res.status(503).json({ message: 'No RESEND_AUTOMATION_* configured.' }); return }

    const email = typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : ''
    const tag = typeof req.body?.tag === 'string' ? req.body.tag.trim().slice(0, 50) : ''
    if (!email) { res.status(400).json({ message: 'email is required.' }); return }

    try {
      const resend = new Resend(process.env.RESEND_API_KEY)
      // Add or update the contact in the Resend audience so the automation can fire
      const audienceId = process.env.RESEND_AUDIENCE_ID || ''
      if (audienceId) {
        await resend.contacts.create({ audienceId, email, unsubscribed: false }).catch(() => {})
      }
      appendAuditEntry('drip-triggered', `email: ${email}${tag ? ` | tag: ${tag}` : ''}`)
      res.json({ ok: true, note: 'Contact synced to Resend audience; automation will fire based on your Resend settings.' })
    } catch (err) {
      res.status(502).json({ message: String(err?.message ?? 'Drip trigger failed.') })
    }
  })

  app.post('/api/admin-contact-submissions/:id/draft-reply', requireAdminAuth, async (req, res) => {
    const apiKey = process.env.ANTHROPIC_API_KEY
    if (!apiKey) { res.status(503).json({ message: 'ANTHROPIC_API_KEY is not configured on the server.' }); return }

    const { id } = req.params
    if (!id || typeof id !== 'string') { res.status(400).json({ message: 'Invalid submission id.' }); return }

    const submission = state.contactSubmissions.find(s => s.id === id.trim())
    if (!submission) { res.status(404).json({ message: 'Submission not found.' }); return }

    const senderName = submission.name?.trim() || 'this listener'
    const messageText = submission.message?.trim() || '(no message body)'

    try {
      const apiRes = await fetch('https://api.anthropic.com/v1/messages', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': apiKey,
          'anthropic-version': '2023-06-01',
        },
        body: JSON.stringify({
          model: 'claude-haiku-4-5-20251001',
          max_tokens: 400,
          messages: [{
            role: 'user',
            content: `Draft a warm, personal reply to this message from a podcast listener. Write only the reply body — no greeting line (handled separately), no sign-off (handled by a signature). Keep it under 150 words. Be genuine and specific to their message.\n\nFrom: ${senderName}\nMessage:\n${messageText.slice(0, 1500)}`,
          }],
        }),
      })

      if (!apiRes.ok) {
        const err = await apiRes.json().catch(() => ({}))
        res.status(502).json({ message: err?.error?.message || 'AI draft request failed.' }); return
      }

      const data = await apiRes.json()
      const draft = String(data?.content?.[0]?.text ?? '').trim()
      res.json({ ok: true, draft })
    } catch {
      res.status(502).json({ message: 'AI draft request failed.' })
    }
  })

  // ── Webhook CRUD ───────────────────────────────────────────────────────────

  app.get('/api/admin-webhooks', requireAdminAuth, (_req, res) => {
    res.json({ webhooks: state.webhooks })
  })

  app.post('/api/admin-webhooks', requireAdminAuth, (req, res) => {
    const url = typeof req.body?.url === 'string' ? req.body.url.trim() : ''
    const events = Array.isArray(req.body?.events) ? req.body.events.filter(e => typeof e === 'string' && e.trim()) : ['*']
    const label = typeof req.body?.label === 'string' ? req.body.label.trim().slice(0, 100) : ''
    if (!url || !/^https?:\/\/./.test(url)) {
      res.status(400).json({ message: 'A valid http/https URL is required.' }); return
    }
    const webhook = { id: randomUUID(), url: url.slice(0, 500), label, events, createdAt: new Date().toISOString() }
    state.webhooks.push(webhook)
    state.webhooks = state.webhooks.slice(0, 50)
    queueWebhooksWrite()
    appendAuditEntry('webhook-added', url)
    res.json({ ok: true, webhook })
  })

  app.delete('/api/admin-webhooks/:id', requireAdminAuth, (req, res) => {
    const { id } = req.params
    const before = state.webhooks.length
    state.webhooks = state.webhooks.filter(w => w.id !== id)
    if (state.webhooks.length === before) { res.status(404).json({ message: 'Webhook not found.' }); return }
    queueWebhooksWrite()
    appendAuditEntry('webhook-removed', `id: ${id}`)
    res.json({ ok: true })
  })

  app.post('/api/admin-webhooks/:id/test', requireAdminAuth, async (req, res) => {
    const wh = state.webhooks.find(w => w.id === req.params.id)
    if (!wh) { res.status(404).json({ message: 'Webhook not found.' }); return }
    try {
      const r = await fetch(wh.url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'User-Agent': 'Siteforge-Webhook/1.0' },
        body: JSON.stringify({ event: 'test', data: { message: 'Test event from Siteforge.' }, firedAt: new Date().toISOString() }),
        signal: AbortSignal.timeout(8000),
      })
      appendAuditEntry('webhook-test', `${wh.url} → ${r.status}`)
      res.json({ ok: true, status: r.status })
    } catch (err) {
      res.status(502).json({ message: String(err?.message ?? 'Test failed.') })
    }
  })
}
