/**
 * CF Email Routing handler — expenses-module + crm-support-center.
 * Parses inbound mail to expenses@{tenantSlug}.zync.is or support@{tenantSlug}.zync.is.
 */
import type { ForwardableEmailMessage } from '@cloudflare/workers-types'
import type { Env, InboundMessage } from '@zync/types'
import { createDb } from '@zync/db/queries'
import { handleExpenseEmail } from './email-expense'

const EXPENSES_RECIPIENT_RE = /^expenses@([a-z0-9-]+)\.zync\.is$/i
const SUPPORT_RECIPIENT_RE = /^support@([a-z0-9-]+)\.zync\.is$/i

const ALLOWED_CONTENT_TYPES: Record<string, 'pdf' | 'jpg' | 'png' | 'heic'> = {
  'application/pdf': 'pdf',
  'image/jpeg': 'jpg',
  'image/jpg': 'jpg',
  'image/png': 'png',
  'image/heic': 'heic',
}

interface ParsedAttachment {
  filename: string
  contentType: string
  data: ArrayBuffer
}

function extractTenantSlug(
  message: ForwardableEmailMessage,
  pattern: RegExp,
): string | null {
  const targets = Array.isArray(message.to) ? message.to : [message.to]
  for (const addr of targets) {
    const match = addr.match(pattern)
    if (match?.[1]) return match[1].toLowerCase()
  }
  return null
}

function parseEmailAddress(raw: string): { email: string; name: string | null } {
  const named = raw.match(/^(.+?)\s*<([^>]+)>$/)
  if (named) {
    return { name: named[1]?.replace(/^"|"$/g, '').trim() || null, email: named[2]!.trim().toLowerCase() }
  }
  return { email: raw.trim().toLowerCase(), name: null }
}

function decodeQuotedPrintable(input: string): string {
  return input
    .replace(/=\r?\n/g, '')
    .replace(/=([0-9A-Fa-f]{2})/g, (_, hex: string) =>
      String.fromCharCode(parseInt(hex, 16)),
    )
}

function stripHtml(html: string): string {
  return html
    .replace(/<style[\s\S]*?<\/style>/gi, '')
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/p>/gi, '\n')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .trim()
}

function extractMimeBody(raw: ArrayBuffer): { text: string; html: string | null } {
  const text = new TextDecoder('latin1').decode(raw)
  const boundaryMatch = text.match(/boundary="?([^"\r\n;]+)"?/i)
  if (!boundaryMatch?.[1]) {
    const htmlMatch = text.match(/Content-Type:\s*text\/html[\s\S]*?\r\n\r\n([\s\S]*)/i)
    if (htmlMatch?.[1]) {
      const html = decodeQuotedPrintable(htmlMatch[1].trim())
      return { text: stripHtml(html), html }
    }
    const plainMatch = text.match(/Content-Type:\s*text\/plain[\s\S]*?\r\n\r\n([\s\S]*)/i)
    if (plainMatch?.[1]) {
      return { text: decodeQuotedPrintable(plainMatch[1].trim()), html: null }
    }
    return { text: text.slice(0, 5000), html: null }
  }

  const boundary = boundaryMatch[1]
  const parts = text.split(`--${boundary}`)
  let plain = ''
  let html: string | null = null

  for (const part of parts) {
    if (!part.trim() || part.startsWith('--')) continue
    const headerBodySplit = part.indexOf('\r\n\r\n')
    if (headerBodySplit < 0) continue
    const headers = part.slice(0, headerBodySplit)
    const bodyRaw = part.slice(headerBodySplit + 4).replace(/\r\n$/, '')
    const contentTypeMatch = headers.match(/Content-Type:\s*([^;\r\n]+)/i)
    const contentType = contentTypeMatch?.[1]?.trim().toLowerCase() ?? ''
    const encodingMatch = headers.match(/Content-Transfer-Encoding:\s*(\S+)/i)
    const encoding = encodingMatch?.[1]?.toLowerCase() ?? '7bit'

    let decoded = bodyRaw
    if (encoding === 'base64') {
      const cleaned = bodyRaw.replace(/\s+/g, '')
      try {
        decoded = atob(cleaned)
      } catch {
        decoded = bodyRaw
      }
    } else if (encoding === 'quoted-printable') {
      decoded = decodeQuotedPrintable(bodyRaw)
    }

    if (contentType === 'text/plain' && !plain) {
      plain = decoded.trim()
    } else if (contentType === 'text/html' && !html) {
      html = decoded.trim()
    }
  }

  if (!plain && html) plain = stripHtml(html)
  return { text: plain, html }
}

/** Minimal MIME multipart attachment extractor (no external deps). */
function extractMimeAttachments(raw: ArrayBuffer): ParsedAttachment[] {
  const text = new TextDecoder('latin1').decode(raw)
  const boundaryMatch = text.match(/boundary="?([^"\r\n;]+)"?/i)
  if (!boundaryMatch?.[1]) return []

  const boundary = boundaryMatch[1]
  const parts = text.split(`--${boundary}`)
  const attachments: ParsedAttachment[] = []

  for (const part of parts) {
    if (!part.trim() || part.startsWith('--')) continue

    const headerBodySplit = part.indexOf('\r\n\r\n')
    if (headerBodySplit < 0) continue
    const headers = part.slice(0, headerBodySplit)
    const bodyRaw = part.slice(headerBodySplit + 4).replace(/\r\n$/, '')

    const contentTypeMatch = headers.match(/Content-Type:\s*([^;\r\n]+)/i)
    const contentType = contentTypeMatch?.[1]?.trim().toLowerCase() ?? ''
    if (!ALLOWED_CONTENT_TYPES[contentType]) continue

    const filenameMatch =
      headers.match(/filename="([^"]+)"/i) ??
      headers.match(/name="([^"]+)"/i)
    const filename = filenameMatch?.[1] ?? `attachment.${ALLOWED_CONTENT_TYPES[contentType]}`

    const encodingMatch = headers.match(/Content-Transfer-Encoding:\s*(\S+)/i)
    const encoding = encodingMatch?.[1]?.toLowerCase() ?? 'base64'

    let data: ArrayBuffer
    if (encoding === 'base64') {
      const cleaned = bodyRaw.replace(/\s+/g, '')
      const binary = atob(cleaned)
      const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0))
      data = bytes.buffer as ArrayBuffer
    } else {
      data = new TextEncoder().encode(bodyRaw).buffer as ArrayBuffer
    }

    attachments.push({ filename, contentType, data })
  }

  return attachments
}

async function resolveTenantIdFromSlug(env: Env, slug: string): Promise<string | null> {
  const db = createDb(env)
  const tenant = await db.query.tenants.findFirst({
    where: (t, { eq }) => eq(t.slug, slug),
  })
  return tenant?.id ?? null
}

/**
 * Handle support@{slug}.zync.is inbound mail — enqueue comms.inbound for ticket creation.
 * Returns true when the message was routed to support (even if tenant lookup failed).
 */
export async function handleInboundSupportEmail(
  message: ForwardableEmailMessage,
  env: Env,
): Promise<boolean> {
  const slug = extractTenantSlug(message, SUPPORT_RECIPIENT_RE)
  if (!slug) return false

  const tenantId = await resolveTenantIdFromSlug(env, slug)
  if (!tenantId) {
    console.warn(`[email-routing] No tenant found for support slug ${slug}`)
    return true
  }

  const raw = await new Response(message.raw).arrayBuffer()
  const { text, html } = extractMimeBody(raw)
  const fromParsed = parseEmailAddress(message.from)
  const messageId =
    message.headers.get('message-id') ??
    message.headers.get('Message-ID') ??
    crypto.randomUUID()
  const inReplyTo =
    message.headers.get('in-reply-to') ?? message.headers.get('In-Reply-To') ?? undefined
  const references =
    message.headers.get('references') ?? message.headers.get('References') ?? undefined
  const subject = message.headers.get('subject') ?? message.headers.get('Subject') ?? ''

  const inboundMessage: InboundMessage = {
    from: fromParsed.email,
    text: html ?? text,
    chatId: fromParsed.email,
    metadata: {
      messageId,
      inReplyTo,
      references,
      subject,
      fromName: fromParsed.name,
      channel: 'email',
    },
  }

  await env.QUEUE.send({
    type: 'comms.inbound',
    source: 'email',
    tenantId,
    message: inboundMessage,
    receivedAt: new Date().toISOString(),
  })

  return true
}

export async function handleInboundExpenseEmail(
  message: ForwardableEmailMessage,
  env: Env,
): Promise<void> {
  const slug = extractTenantSlug(message, EXPENSES_RECIPIENT_RE)
  if (!slug) {
    console.warn('[email-routing] No expenses@ recipient found, discarding')
    return
  }

  const raw = await new Response(message.raw).arrayBuffer()
  const attachments = extractMimeAttachments(raw)
  if (attachments.length === 0) {
    console.warn(`[email-routing] No supported attachments for tenant slug ${slug}`)
    return
  }

  const messageId =
    message.headers.get('message-id') ??
    message.headers.get('Message-ID') ??
    crypto.randomUUID()

  await handleExpenseEmail(env, slug, message.from, messageId, attachments)
}

export async function handleInboundEmail(
  message: ForwardableEmailMessage,
  env: Env,
): Promise<void> {
  const supportHandled = await handleInboundSupportEmail(message, env)
  if (supportHandled) return
  await handleInboundExpenseEmail(message, env)
}
