// SendGrid Inbound Parse webhook handler.
// Receives multipart/form-data POST from SendGrid when an email arrives at
// the configured inbound hostname (e.g. bills.stagelaw.com).
// Looks up the sender in vendor_email_mappings; if matched, parses the PDF
// attachment via parse-invoice and creates a draft bill. Otherwise quarantines
// the email in inbound_bill_emails for manual review.

import { createClient } from 'npm:@supabase/supabase-js@2'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

function extractEmail(raw: string | null): { email: string | null; name: string | null } {
  if (!raw) return { email: null, name: null }
  // Match "Name <email@x.com>" or just "email@x.com"
  const m = raw.match(/(?:"?([^"<]*)"?\s*)?<?([^\s<>]+@[^\s<>]+)>?/)
  if (!m) return { email: null, name: null }
  return { email: m[2].trim().toLowerCase(), name: (m[1] || '').trim() || null }
}

async function arrayBufferToBase64(buf: ArrayBuffer): Promise<string> {
  const bytes = new Uint8Array(buf)
  let binary = ''
  const chunk = 0x8000
  for (let i = 0; i < bytes.length; i += chunk) {
    binary += String.fromCharCode(...bytes.subarray(i, i + chunk))
  }
  return btoa(binary)
}

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') return new Response(null, { headers: corsHeaders })
  if (req.method !== 'POST') {
    return new Response('Method not allowed', { status: 405, headers: corsHeaders })
  }

  const supabaseUrl = Deno.env.get('SUPABASE_URL')!
  const serviceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  const supabase = createClient(supabaseUrl, serviceKey)

  let formData: FormData
  try {
    formData = await req.formData()
  } catch (err) {
    console.error('Failed to parse form data', err)
    return new Response('Bad Request', { status: 400, headers: corsHeaders })
  }

  const fromRaw = formData.get('from')?.toString() ?? null
  const toRaw = formData.get('to')?.toString() ?? null
  const subject = formData.get('subject')?.toString() ?? null
  const text = formData.get('text')?.toString() ?? null
  const attachmentCount = parseInt(formData.get('attachments')?.toString() ?? '0', 10)

  const { email: fromEmail, name: fromName } = extractEmail(fromRaw)
  const { email: toEmail } = extractEmail(toRaw)

  console.log('Inbound bill email', { fromEmail, toEmail, subject, attachmentCount })

  // ===== Deduplication =====
  // External mail loops (auto-forwarders, retry policies) can re-deliver the exact
  // same email repeatedly. If we already received a message with the same sender +
  // subject in the last 30 days, treat this delivery as a duplicate and short-circuit.
  if (fromEmail && subject) {
    const sinceIso = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
    const { data: existing, error: dupErr } = await supabase
      .from('inbound_bill_emails')
      .select('id, status, bill_id, created_at')
      .eq('from_email', fromEmail)
      .eq('subject', subject)
      .gte('created_at', sinceIso)
      .order('created_at', { ascending: false })
      .limit(1)
    if (dupErr) {
      console.error('Dedup lookup failed', dupErr)
    } else if (existing && existing.length > 0) {
      console.log('Duplicate inbound email ignored', {
        fromEmail, subject, existingId: existing[0].id, existingStatus: existing[0].status,
      })
      return new Response(
        JSON.stringify({ ok: true, deduplicated: true, existingId: existing[0].id }),
        { headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
      )
    }
  }

  // Find first PDF attachment
  let pdfFile: File | null = null
  for (let i = 1; i <= attachmentCount; i++) {
    const file = formData.get(`attachment${i}`) as File | null
    if (file && (file.type === 'application/pdf' || file.name?.toLowerCase().endsWith('.pdf'))) {
      pdfFile = file
      break
    }
  }

  // Build raw payload snapshot (small fields only)
  const rawPayload: Record<string, unknown> = {
    from: fromRaw, to: toRaw, subject,
    attachments: attachmentCount,
    envelope: formData.get('envelope')?.toString() ?? null,
    spam_score: formData.get('spam_score')?.toString() ?? null,
  }

  // Upload attachment to storage if present
  let storagePath: string | null = null
  let attachmentUrl: string | null = null
  let pdfBase64: string | null = null
  if (pdfFile) {
    const buf = await pdfFile.arrayBuffer()
    pdfBase64 = await arrayBufferToBase64(buf)
    const safeName = (pdfFile.name || 'invoice.pdf').replace(/[^a-zA-Z0-9._-]/g, '_')
    storagePath = `${new Date().toISOString().slice(0, 10)}/${crypto.randomUUID()}-${safeName}`
    const { error: uploadErr } = await supabase.storage
      .from('inbound-bill-attachments')
      .upload(storagePath, buf, { contentType: 'application/pdf', upsert: false })
    if (uploadErr) {
      console.error('Failed to upload attachment', uploadErr)
      storagePath = null
    } else {
      const { data: signed } = await supabase.storage
        .from('inbound-bill-attachments')
        .createSignedUrl(storagePath, 60 * 60 * 24 * 30) // 30 days
      attachmentUrl = signed?.signedUrl ?? null
    }
  }

  // Look up sender → association mapping
  let mappedAssociationId: string | null = null
  let mappedVendorId: string | null = null
  if (fromEmail) {
    const { data: mappings } = await supabase
      .from('vendor_email_mappings')
      .select('association_id, vendor_id')
      .eq('email', fromEmail)
      .limit(1)
    if (mappings && mappings.length > 0) {
      mappedAssociationId = mappings[0].association_id as string
      mappedVendorId = (mappings[0].vendor_id as string) ?? null
    }
  }

  // Always create the inbox row first so nothing is lost
  const { data: inboxRow, error: inboxErr } = await supabase
    .from('inbound_bill_emails')
    .insert({
      from_email: fromEmail,
      from_name: fromName,
      to_email: toEmail,
      subject,
      body_text: text?.slice(0, 10000) ?? null,
      attachment_url: attachmentUrl,
      attachment_filename: pdfFile?.name ?? null,
      attachment_storage_path: storagePath,
      association_id: mappedAssociationId,
      raw_payload: rawPayload,
      status: 'unassigned',
    })
    .select('id')
    .single()

  if (inboxErr || !inboxRow) {
    console.error('Failed to create inbox row', inboxErr)
    return new Response('Internal error', { status: 500, headers: corsHeaders })
  }

  const inboxId = inboxRow.id as string

  // No PDF? Leave as unassigned with note
  if (!pdfBase64) {
    await supabase
      .from('inbound_bill_emails')
      .update({ status: 'unassigned', error_message: 'No PDF attachment found' })
      .eq('id', inboxId)
    return new Response(JSON.stringify({ ok: true, inboxId, reason: 'no_pdf' }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }

  // Unknown sender → leave for manual review (with the PDF saved + parsed for convenience)
  // We still try to parse so staff can preview the extracted data
  let parsed: Record<string, unknown> | null = null
  let parseError: string | null = null
  try {
    const { data, error } = await supabase.functions.invoke('parse-invoice', {
      body: { pdf_base64: pdfBase64, filename: pdfFile?.name ?? 'invoice.pdf' },
    })
    if (error) throw error
    parsed = (data as { data?: Record<string, unknown> })?.data ?? null
  } catch (err) {
    parseError = err instanceof Error ? err.message : String(err)
    console.error('parse-invoice failed', parseError)
  }

  await supabase
    .from('inbound_bill_emails')
    .update({ parsed_data: parsed, error_message: parseError })
    .eq('id', inboxId)

  // If we have a mapping AND we parsed successfully, auto-create a draft bill
  if (mappedAssociationId && parsed) {
    const totalAmount = Number(parsed.total_amount) || 0
    const invoiceNumber = (parsed.invoice_number as string) || null
    const invoiceDate = (parsed.invoice_date as string) || new Date().toISOString().slice(0, 10)
    const dueDate = (parsed.due_date as string) || null
    const vendorName = (parsed.vendor_name as string) || fromName || fromEmail || 'Unknown Vendor'
    const lineItems = Array.isArray((parsed as any).line_items) ? (parsed as any).line_items : []

    // Upload the PDF to the public `invoices` bucket so the URL is permanent
    // (the inbound-bill-attachments bucket is private and signed URLs expire).
    let publicPdfUrl: string | null = null
    try {
      const safeName = (pdfFile?.name || 'invoice.pdf').replace(/[^a-zA-Z0-9._-]/g, '_')
      const invPath = `inbound/${new Date().toISOString().slice(0, 10)}/${crypto.randomUUID()}-${safeName}`
      const buf = Uint8Array.from(atob(pdfBase64), (c) => c.charCodeAt(0))
      const { error: invUploadErr } = await supabase.storage
        .from('invoices')
        .upload(invPath, buf, { contentType: 'application/pdf', upsert: false })
      if (invUploadErr) {
        console.error('Failed to upload PDF to invoices bucket', invUploadErr)
      } else {
        const { data: pub } = supabase.storage.from('invoices').getPublicUrl(invPath)
        publicPdfUrl = pub?.publicUrl ?? null
      }
    } catch (err) {
      console.error('Public PDF upload error', err)
    }

    const billAttachmentUrl = publicPdfUrl ?? attachmentUrl

    // Create a linked invoices row (mirrors AI Bill Parser flow) so Bill Approvals
    // can read raw_pdf_url and display the attachment.
    let sourceInvoiceId: string | null = null
    try {
      const { data: invRow, error: invErr } = await supabase
        .from('invoices')
        .insert({
          association_id: mappedAssociationId,
          vendor_name: vendorName,
          invoice_number: invoiceNumber,
          issue_date: invoiceDate,
          due_date: dueDate,
          amount: totalAmount,
          description: `Inbound from ${vendorName} (${subject ?? 'no subject'})`,
          status: 'pending',
          raw_pdf_url: billAttachmentUrl,
          line_items: lineItems,
        })
        .select('id')
        .single()
      if (invErr) {
        console.error('Failed to create invoice row', invErr)
      } else {
        sourceInvoiceId = invRow?.id ?? null
      }
    } catch (err) {
      console.error('Invoice insert error', err)
    }

    const { data: bill, error: billErr } = await supabase
      .from('bills')
      .insert({
        association_id: mappedAssociationId,
        vendor_id: mappedVendorId,
        amount: totalAmount,
        invoice_number: invoiceNumber,
        bill_date: invoiceDate,
        due_date: dueDate,
        description: `Inbound from ${vendorName} (${subject ?? 'no subject'})`,
        attachment_url: billAttachmentUrl,
        source_invoice_id: sourceInvoiceId,
        status: 'pending',
      })
      .select('id')
      .single()

    if (billErr) {
      console.error('Failed to create bill', billErr)
      await supabase
        .from('inbound_bill_emails')
        .update({ status: 'error', error_message: `Bill creation failed: ${billErr.message}` })
        .eq('id', inboxId)
    } else {
      await supabase
        .from('inbound_bill_emails')
        .update({
          status: 'processed',
          bill_id: bill.id,
          processed_at: new Date().toISOString(),
        })
        .eq('id', inboxId)
      console.log('Created bill from inbound email', { billId: bill.id, inboxId })
    }
  }

  // Notify staff (admins + managers) of the inbound bill
  try {
    const { data: staffRoles } = await supabase
      .from('user_roles')
      .select('user_id')
      .in('role', ['admin', 'manager'])

    const uniqueIds = Array.from(
      new Set((staffRoles ?? []).map((r: any) => r.user_id).filter(Boolean))
    )

    if (uniqueIds.length > 0) {
      const senderLabel = fromName || fromEmail || 'Unknown sender'
      const subjectLabel = subject ? `: ${subject}` : ''
      const title = mappedAssociationId
        ? 'New inbound bill received'
        : 'Inbound bill needs review'
      const message = mappedAssociationId
        ? `Bill from ${senderLabel}${subjectLabel} was received and processed.`
        : `Email from ${senderLabel}${subjectLabel} could not be matched to a vendor and needs manual review.`
      const link = '/dashboard/inbound-bills'

      const notifications = uniqueIds.map((uid) => ({
        user_id: uid,
        type: 'inbound_bill_received',
        title,
        message,
        related_item_id: inboxId,
        related_item_type: 'inbound_bill_email',
        link,
      }))

      const { error: notifErr } = await supabase
        .from('in_app_notifications')
        .insert(notifications)
      if (notifErr) console.error('Failed to insert notifications', notifErr)
    }
  } catch (err) {
    console.error('Notification dispatch failed', err)
  }

  return new Response(JSON.stringify({ ok: true, inboxId }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
})
