/**
 * GET /api/mail/lead-followups?days=30
 * Leads (sales / vendor, AD_User) created in the last `days` whose LATEST e-mail
 * in the mailpiler archive is an incoming one (from the lead) and that got NO
 * reaction from us since then — neither an outgoing mail to that address nor a
 * contact activity (C_ContactActivity) logged after the mail. Only the archive
 * is consulted (one indexed lookup per lead, no IMAP). Nothing is stored.
 * Automatic replies from the lead (out-of-office etc.) are skipped when picking
 * the latest incoming mail. Staff only. Never throws to the client.
 */
import { string } from 'alga-js'
import { gateFromEvent } from '../../utils/chatAccess'
import { archiveAvailable, searchArchive } from '../../utils/mail/mailArchive'
import { isValidEmail } from '../../utils/mail/leadMailbox'
import fetchHelper from '../../utils/fetchHelper'
import refreshTokenHelper from '../../utils/refreshTokenHelper'

const DEFAULT_DAYS = 30
const MAX_DAYS = 365
const MAX_LEADS = 1000
const ARCHIVE_ROWS = 60          // newest mails per address — enough to find the latest inbound + any reply after it
const CONCURRENCY = 5
const AUTO_REPLY = /^(automatische antwort|automatic reply|auto(-|\s)?reply|autoreply|out of office|abwesenheit|abwesenheitsnotiz|ooo:|lesebestätigung|read receipt|zugestellt:|delivery status|unzustellbar|undeliverable)/i

const ts = (v: any): number => {
  if (!v) return 0
  const n = new Date(v).getTime()
  return Number.isNaN(n) ? 0 : n
}

export default defineEventHandler(async (event) => {
  let token = ''
  try { token = (await gateFromEvent(event)).token } catch (err: any) { return { status: err?.statusCode || 403, message: 'Forbidden', rows: [] } }
  if (!archiveAvailable()) return { status: 503, message: 'Mail archive not configured', rows: [] }

  const q = getQuery(event)
  const days = Math.min(MAX_DAYS, Math.max(1, Math.round(Number(q.days)) || DEFAULT_DAYS))
  // Expired leads (AD_User.LeadStatus = E) are skipped by default — nobody waits
  // for an answer on a closed lead; `includeExpired=1` lists them anyway (badged).
  const includeExpired = ['1', 'true', 'yes'].includes(String(q.includeExpired ?? '').toLowerCase())
  const sinceDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
  const since = sinceDate.toISOString().slice(0, 10)
  const started = Date.now()

  const get = async (url: string) => {
    try {
      return await fetchHelper(event, url, 'GET', token, null)
    } catch {
      const fresh = await refreshTokenHelper(event)
      return await fetchHelper(event, url, 'GET', fresh, null)
    }
  }

  try {
    // 1) Leads created in the window.
    const leadFilter = `(isSalesLead eq true OR isVendorLead eq true) AND Created ge '${since}'`
    const leadRes: any = await get(`models/ad_user?$filter=${string.urlEncode(leadFilter)}&$select=Name,EMail,companyname,Phone,Created,LeadStatus&$orderby=${string.urlEncode('Created desc')}&$top=${MAX_LEADS}`)
    const leads: any[] = (leadRes?.records || []).map((u: any) => ({
      id: Number(u.id),
      name: String(u.Name || u.name || ''),
      companyname: String(u.companyname || u.CompanyName || ''),
      phone: String(u.Phone || u.phone || ''),
      email: String(u.EMail || u.eMail || '').trim().toLowerCase(),
      created: u.Created || null,
      leadStatus: String(u.LeadStatus?.id || u.leadStatus?.id || ''),
      expired: String(u.LeadStatus?.id || u.leadStatus?.id || '') === 'E'
    }))
    const expiredSkipped = includeExpired ? 0 : leads.filter((l) => l.expired).length

    // 2) Contact activities of the window, newest first, grouped per lead. An activity
    //    can only be logged after the lead exists, so `Created ge since` is complete.
    const actByUser = new Map<number, { date: number; iso: string; description: string; type: string }[]>()
    try {
      const act: any = await get(`models/c_contactactivity?$filter=${string.urlEncode(`Created ge '${since}'`)}&$select=AD_User_ID,StartDate,Created,Description,ContactActivityType&$orderby=${string.urlEncode('Created desc')}&$top=5000`)
      for (const a of (act?.records || [])) {
        const userId = Number(a?.AD_User_ID?.id)
        if (!userId) continue
        const date = Math.max(ts(a?.StartDate), ts(a?.Created))
        if (!date) continue
        if (!actByUser.has(userId)) actByUser.set(userId, [])
        actByUser.get(userId)!.push({
          date,
          iso: new Date(date).toISOString(),
          description: String(a?.Description || ''),
          type: String(a?.ContactActivityType?.identifier || a?.ContactActivityType || '')
        })
      }
    } catch (e: any) {
      console.warn('[lead-followups] activity lookup failed, continuing without activities:', e?.data ?? e?.message ?? e)
    }

    // 3) Archive lookup per lead address (bounded concurrency).
    const rows: any[] = []
    let withoutEmail = 0
    let errors = 0
    let inboundLeads = 0
    const seenEmails = new Set<string>()
    const work = leads.filter((l) => {
      if (!includeExpired && l.expired) return false
      if (!isValidEmail(l.email)) { withoutEmail++; return false }
      if (seenEmails.has(l.email)) return false        // duplicate lead records of one address: check once
      seenEmails.add(l.email)
      return true
    })

    const checkLead = async (lead: any) => {
      let mails: any[]
      try {
        mails = (await searchArchive(lead.email, ARCHIVE_ROWS)).rows
      } catch (e: any) {
        errors++
        console.warn('[lead-followups] archive lookup failed for lead', lead.id, e?.message || e)
        return
      }
      // newest first — the first non-automatic incoming mail is the one that needs an answer
      const inIndex = mails.findIndex((m) => m.direction === 'in' && !AUTO_REPLY.test(String(m.subject || '').trim()))
      if (inIndex < 0) return
      inboundLeads++
      const latestIn = mails[inIndex]
      const inDate = ts(latestIn.date)
      const repliedByMail = mails.slice(0, inIndex).some((m) => m.direction === 'out' && ts(m.date) > inDate)
      if (repliedByMail) return
      const acts = actByUser.get(lead.id) || []
      if (acts.some((a) => a.date > inDate)) return

      const lastOut = mails.find((m) => m.direction === 'out')
      const lastAct = acts[0] || null
      rows.push({
        ...lead,
        lastInbound: { id: latestIn.uid, date: latestIn.date, subject: String(latestIn.subject || ''), from: latestIn.from?.address || lead.email },
        inboundCount: mails.filter((m) => m.direction === 'in').length,
        lastOwnMail: lastOut ? { date: lastOut.date, subject: String(lastOut.subject || '') } : null,
        lastActivity: lastAct ? { date: lastAct.iso, description: lastAct.description, type: lastAct.type } : null,
        daysWaiting: inDate ? Math.max(0, Math.floor((Date.now() - inDate) / 86_400_000)) : null
      })
    }

    let cursor = 0
    const worker = async () => {
      while (cursor < work.length) {
        const lead = work[cursor++]
        await checkLead(lead)
      }
    }
    await Promise.all(Array.from({ length: Math.min(CONCURRENCY, work.length) }, worker))

    rows.sort((a, b) => ts(b.lastInbound?.date) - ts(a.lastInbound?.date))
    return {
      status: 200,
      days,
      since,
      scanned: leads.length,
      checked: work.length,
      withoutEmail,
      expiredSkipped,
      includeExpired,
      inboundLeads,
      errors,
      rows,
      ms: Date.now() - started
    }
  } catch (err: any) {
    console.warn('[lead-followups] failed:', err?.data ?? err?.message ?? err)
    return { status: Number(err?.statusCode || err?.status) || 502, message: err?.message || 'Lookup failed', rows: [] }
  }
})
