import { randomUUID, timingSafeEqual } from 'node:crypto'
import { state } from '../state.js'
import { queueContactSubmissionsWrite } from '../data.js'
import { MAX_CONTACT_SUBMISSIONS } from '../config.js'

const MESSAGE_ID_RE = /^<[\x21-\x7E]+>$/
const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024 // 5 MB total per email

function decodeHtmlBody(raw) {
  if (typeof raw !== 'string' || !raw.trim()) return null
  const s = raw.trim()
  if (s.startsWith('<')) return s
  try {
    const decoded = Buffer.from(s.replace(/\s+/g, ''), 'base64').toString('utf8')
    return decoded.trimStart().startsWith('<') ? decoded : s
  } catch {
    return s
  }
}

function normalizeSubject(subject) {
  return (subject ?? '').toLowerCase().replace(/^(re|fwd?):\s*/i, '').trim()
}

function resolveThreadId(fromEmail, normalizedSubject, inReplyTo) {
  // 1. Exact In-Reply-To match
  if (inReplyTo) {
    const parent = state.contactSubmissions.find(s => s.messageId === inReplyTo)
    if (parent?.threadId) return parent.threadId
  }
  // 2. Same sender + matching subject within 30 days
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000
  const match = state.contactSubmissions.find(s =>
    s.email === fromEmail &&
    normalizedSubject &&
    normalizeSubject(s.message.match(/^Subject:\s*(.+)/m)?.[1] ?? '') === normalizedSubject &&
    new Date(s.submittedAt).getTime() > cutoff,
  )
  if (match?.threadId) return match.threadId
  // 3. New thread
  return randomUUID()
}

function parseAttachments(raw) {
  if (!Array.isArray(raw)) return []
  let totalBytes = 0
  const out = []
  for (const a of raw.slice(0, 10)) {
    if (!a || typeof a.filename !== 'string') continue
    const dataStr = typeof a.content === 'string' ? a.content : ''
    const size = typeof a.size === 'number' ? a.size : Math.floor(dataStr.length * 0.75)
    if (totalBytes + size > MAX_ATTACHMENT_BYTES) continue
    totalBytes += size
    out.push({
      id: randomUUID(),
      filename: a.filename.slice(0, 255),
      contentType: typeof a.contentType === 'string' ? a.contentType : 'application/octet-stream',
      size,
      data: dataStr,
    })
  }
  return out
}

export function register(app) {
  app.post('/api/inbound-email', (req, res) => {
    const secret = process.env.INBOUND_EMAIL_SECRET
    if (!secret) {
      res.status(503).json({ message: 'Inbound email not configured.' }); return
    }

    const provided = req.get('x-webhook-secret') ?? ''
    const a = Buffer.from(provided, 'utf8')
    const b = Buffer.from(secret, 'utf8')
    if (!provided || a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(401).json({ message: 'Unauthorized.' }); return
    }

    const { from, to, subject, body, htmlBody, date, messageId, inReplyTo, attachments: rawAttachments, source } = req.body ?? {}

    if (!from || typeof from !== 'string') {
      res.status(400).json({ message: 'Missing from address.' }); return
    }

    const fromMatch = /^(.*?)\s*<([^>]+)>$/.exec(from.trim())
    const fromEmail = fromMatch ? fromMatch[2].trim() : from.trim()
    const fromName = fromMatch ? fromMatch[1].trim() : from.trim()

    const normalizedMessageId = typeof messageId === 'string' && MESSAGE_ID_RE.test(messageId.trim()) ? messageId.trim() : ''
    const normalizedInReplyTo = typeof inReplyTo === 'string' && MESSAGE_ID_RE.test(inReplyTo.trim()) ? inReplyTo.trim() : ''

    if (normalizedMessageId) {
      const exists = state.contactSubmissions.some(s => s.messageId === normalizedMessageId)
      if (exists) {
        res.json({ ok: true, duplicate: true }); return
      }
    }

    const subjectStr = typeof subject === 'string' ? subject.trim() : ''
    const threadId = resolveThreadId(fromEmail, normalizeSubject(subjectStr), normalizedInReplyTo)

    const submission = {
      id: randomUUID(),
      threadId,
      submittedAt: (typeof date === 'string' || typeof date === 'number') && Number.isFinite(Date.parse(date)) ? new Date(date).toISOString() : new Date().toISOString(),
      name: fromName || fromEmail,
      email: fromEmail,
      message: [subjectStr ? `Subject: ${subjectStr}` : '', body ?? ''].filter(Boolean).join('\n\n'),
      htmlBody: decodeHtmlBody(htmlBody),
      messageType: 'general',
      subscribe: false,
      archived: false,
      starred: false,
      snoozedUntil: null,
      source: 'inbound-email',
      inboundTo: typeof to === 'string' ? to : '',
      messageId: normalizedMessageId,
      attachments: parseAttachments(rawAttachments),
      emailStatus: {
        welcome: { status: 'not-applicable', lastEventAt: null, lastEventType: null, resendEmailId: null, error: null },
        adminNotification: { status: 'not-applicable', lastEventAt: null, lastEventType: null, resendEmailId: null, error: null },
        adminReply: { status: 'pending', lastEventAt: null, lastEventType: null, resendEmailId: null, error: null },
      },
    }

    state.contactSubmissions.unshift(submission)
    state.contactSubmissions = state.contactSubmissions.slice(0, MAX_CONTACT_SUBMISSIONS)
    queueContactSubmissionsWrite()

    console.log(`[inbound-email] received from ${fromEmail} — subject: ${subjectStr || '(none)'} — thread: ${threadId}`)
    res.json({ ok: true })
  })
}
