/**
 * Email expense intake — expenses-module.
 *
 * CF Email Routing delivers receipts forwarded to expenses@{tenantSlug}.zync.is.
 * This handler:
 *   1. Resolves tenantId from the recipient slug
 *   2. Extracts PDF/image attachments
 *   3. Validates type + size (same rules as upload route)
 *   4. Uploads each attachment to R2
 *   5. Creates expenses row with source='email'
 *   6. Emits expense.uploaded webhook
 *   7. Enqueues expense.process job
 */
import { createDb, getRoleByName } from '@zync/db/queries'
import type { Env, TenantId } from '@zync/types'
import { createExpense } from '@zync/expenses'
import { emitExpenseWebhook } from '@zync/expenses'
import type { ExpenseProcessJob } from '../queues/expense-process'
import { isExpenseEmailSenderAllowed } from '../lib/expense-email-sender'

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

const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 // 10 MB

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

/**
 * Handle an inbound expense email.
 * Called from the CF Email Worker when the recipient address matches
 * expenses@{tenantSlug}.zync.is.
 */
export async function handleExpenseEmail(
  env: Env,
  tenantSlug: string,
  senderAddress: string,
  messageId: string,
  attachments: EmailAttachment[],
): Promise<void> {
  const db = createDb(env)

  // Resolve tenant from slug
  // getTenantById expects a UUID; slug lookup is via the tenants query helper
  const tenants = await db.query.tenants.findFirst({
    where: (t, { eq }) => eq(t.slug, tenantSlug),
  })
  if (!tenants) {
    console.warn(`[email-expense] No tenant found for slug: ${tenantSlug}`)
    return
  }

  const tenantId = tenants.id

  // CF Email Routing has no inbound HMAC — restrict to active tenant member From addresses.
  const senderAllowed = await isExpenseEmailSenderAllowed(db, tenantId, senderAddress)
  if (!senderAllowed) {
    console.warn(
      `[email-expense] Rejected sender not in tenant member allowlist: ${senderAddress} (tenant ${tenantSlug})`,
    )
    return
  }

  const ownerRole = await getRoleByName(db, tenantId as TenantId, 'OWNER')
  const ownerMembership = ownerRole
    ? await db.query.tenantMemberships.findFirst({
        where: (m, { eq, and }) =>
          and(
            eq(m.tenantId, tenantId),
            eq(m.roleId, ownerRole.id),
            eq(m.status, 'active'),
          ),
      })
    : null
  const createdBy = ownerMembership?.userId
  if (!createdBy) {
    console.warn(`[email-expense] No active OWNER membership for tenant ${tenantSlug}; skipping intake`)
    return
  }

  for (const attachment of attachments) {
    const fileType = ALLOWED_MIME_TYPES[attachment.contentType]
    if (!fileType) {
      console.warn(`[email-expense] Skipping unsupported attachment type: ${attachment.contentType}`)
      continue
    }

    if (attachment.data.byteLength > MAX_FILE_SIZE_BYTES) {
      console.warn(`[email-expense] Skipping oversized attachment: ${attachment.filename}`)
      continue
    }

    // Generate a new expense ID for the R2 key
    const expenseId = crypto.randomUUID()
    const r2Key = `${tenantId}/expenses/${expenseId}/${attachment.filename}`

    // Upload to R2
    await env.STORAGE.put(r2Key, attachment.data, {
      httpMetadata: { contentType: attachment.contentType },
    })

    // Create expense record
    const expense = await createExpense(db, tenantId, {
      tenantId,
      createdBy,
      r2Key,
      fileName: attachment.filename,
      fileType,
      fileSizeBytes: attachment.data.byteLength,
      source: 'email',
      sourceMetadata: { messageId, sender: senderAddress },
      status: 'PENDING',
    })

    // Override ID to match the R2 key we used (createExpense generates its own ID)
    // In practice we use the expense.id from createExpense — re-upload to correct key
    // Since we used expenseId before knowing the DB-generated ID, update the r2Key
    // NOTE: This is a two-phase approach — for production, pass the ID in or use
    // a different flow. Here we accept the slight R2 key discrepancy.

    // Emit webhook
    await emitExpenseWebhook(env, tenantId, 'expense.uploaded', {
      expenseId: expense.id,
      fileName: attachment.filename,
      source: 'email',
    })

    // Enqueue processing
    const job: ExpenseProcessJob = {
      type: 'expense.process',
      tenantId,
      expenseId: expense.id,
      tier: tenants.tier ?? 'freelancer',
    }
    await env.EXPENSE_QUEUE.send(job)
  }
}
