/**
 * GET /api/accounting/incoming-invoices/match-vendor?id=&vat=&iban=&name=&email=
 *
 * Reuse-first vendor matching for the review card. Returns
 *   - remembered: the c_bpartner bound to a learned profile of this document
 *                 (fingerprint / VAT id / IBAN / layout tokens — see vendorDefaults.ts),
 *   - candidates: existing partners that look like the document's seller, best first,
 *   - vendors:    ALL active vendors of the org (+ org *) so the review's ComboBox is a
 *                 real searchable selection even when nothing matched.
 *
 * Candidates are scored in Node against the vendor list instead of an OData
 * `contains(Name,'<extracted seller name>')`: the extracted name is often a whole
 * letterhead line ("Christina Biendl Buchhaltungsservice | klar. zuverlässig. …") that
 * never is a substring of the partner's name. Signals: TaxID equality, fuzzy name
 * similarity, the partner's name tokens inside the letterhead words, e-mail domain.
 * A last pass looks for NON-vendor partners by the seller's most distinctive word
 * (flagged isVendor:false — confirm then sets IsVendor).
 */
import { string } from 'alga-js'
import refreshTokenHelper from '../../../utils/refreshTokenHelper'
import errorHandlingHelper from '../../../utils/errorHandlingHelper'
import fetchHelper from '../../../utils/fetchHelper'
import { buildInboxCtx } from '../../../utils/inbox/scope'
import { getInboxDocument } from '../../../utils/inbox/inboxDb'
import { normVat } from '../../../utils/inbox/vendorProfile'
import { resolveVendorDefaults } from '../../../utils/inbox/vendorDefaults'
import { nameSimilarity, nameTokens, foldText } from '../../../utils/bankReconciliationSuggestions'

const CANDIDATE_MIN_SCORE = 0.5
const FREE_MAIL = new Set(['gmail.com', 'googlemail.com', 'gmx.de', 'gmx.net', 'web.de', 't-online.de', 'outlook.com', 'outlook.de', 'hotmail.com', 'hotmail.de', 'yahoo.com', 'yahoo.de', 'icloud.com', 'freenet.de', 'posteo.de', 'mailbox.org'])

const isTrue = (v: any) => v === true || v === 'Y'
const slim = (r: any) => ({
  id: r.id,
  name: r.Name,
  taxId: r.TaxID || r.taxID || '',
  isVendor: isTrue(r.IsVendor ?? r.isVendor),
  email: r.EMail || r.eMail || ''
})

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const organizationId = Number(getCookie(event, 'logship_organization_id')) || 0
  const q = getQuery(event)
  const docId = Number(q.id) || 0
  const ctx = buildInboxCtx(event, token)

  const out: any = { status: 200, remembered: null, candidates: [], vendors: [] }
  if (!organizationId) return out

  // the stored document carries the layout tokens + fingerprint; the query values are
  // the fallback (and override the stored ones when the user remapped a field)
  let doc: any = null
  if (docId) { try { doc = await getInboxDocument(ctx, docId) } catch {} }
  const ex = doc?.extracted || {}
  const vat = normVat(String(q.vat || ex?.seller?.vatId || ''))
  const name = String(q.name || ex?.seller?.name || '').trim()
  const email = String(q.email || '').trim().toLowerCase()
  const layoutTokens: string[] = ex?.__layout?.tokens || []

  // 1. remembered partner (learned profile of this document)
  try {
    const lookupDoc = doc || { vendorFingerprint: '', extracted: { seller: { name, vatId: vat }, paymentIban: String(q.iban || '') } }
    const learned = await resolveVendorDefaults(ctx, { doc: lookupDoc, withHistory: false })
    if (learned.rememberedBpartnerId) {
      const r: any = await fetchHelper(event, `models/c_bpartner/${learned.rememberedBpartnerId}`, 'GET', token, null)
      if (r?.id && r.IsActive !== false) out.remembered = { ...slim(r), score: 1, reason: 'learned' }
    }
  } catch {}

  // 2. all active vendors of the org (+ org * = shared vendors)
  const orgScope = `(AD_Org_ID eq ${organizationId} OR AD_Org_ID eq 0)`
  try {
    const filter = `IsActive eq true AND IsVendor eq true AND ${orgScope}`
    const res: any = await fetchHelper(event,
      `models/c_bpartner?$filter=${string.urlEncode(filter)}&$orderby=${string.urlEncode('Name asc')}&$top=500`, 'GET', token, null)
    out.vendors = (res?.records || []).map(slim)
  } catch { /* fail-soft: the picker then only shows the candidates */ }

  // The BUYER (our own organization) is printed on every invoice — its linked partner
  // (ad_org.C_BPartner_ID) and any partner named like the org must never be suggested.
  let ownPartnerId = 0
  let ownTokens: string[] = []
  try {
    const org: any = await fetchHelper(event, `models/ad_org/${organizationId}`, 'GET', token, null)
    ownPartnerId = Number(org?.C_BPartner_ID?.id ?? org?.C_BPartner_ID) || 0
    ownTokens = nameTokens(org?.Name)
  } catch {}
  const isOwn = (v: any) => {
    if (ownPartnerId && Number(v.id) === ownPartnerId) return true
    const vt = nameTokens(v.name)
    return ownTokens.length > 0 && vt.length > 0 && vt.every(x => ownTokens.includes(x))
  }

  // 3. score the vendors against the document
  const letterhead = new Set<string>([
    ...nameTokens(name),
    ...layoutTokens.filter(t => t.startsWith('w:')).map(t => foldText(t.slice(2)).replace(/[^a-z0-9]/g, '')).filter(Boolean)
  ])
  const domains = new Set<string>(layoutTokens.filter(t => t.startsWith('dom:')).map(t => t.slice(4)))
  if (email.includes('@')) domains.add(email.split('@')[1])

  const score = (v: any): { score: number; reason: string } => {
    if (isOwn(v)) return { score: 0, reason: '' }
    let best = 0, reason = ''
    const bump = (s: number, r: string) => { if (s > best) { best = s; reason = r } }
    if (vat && normVat(v.taxId) === vat) bump(1, 'vat')
    if (name) bump(nameSimilarity(v.name, name) * 0.95, 'name')
    const vt = nameTokens(v.name)
    if (vt.length && letterhead.size) {
      const hits = vt.filter(x => letterhead.has(x)).length
      // every name token on the letterhead — a single short token ("post") is too weak
      if (hits === vt.length && (vt.length >= 2 || vt[0].length >= 6)) bump(0.85, 'letterhead')
      else if (vt.length >= 3 && hits >= vt.length - 1) bump(0.6, 'letterhead')
    }
    const dom = String(v.email || '').toLowerCase().split('@')[1]
    if (dom && !FREE_MAIL.has(dom) && domains.has(dom)) bump(0.8, 'email')
    return { score: Math.round(best * 100) / 100, reason }
  }

  const scored = out.vendors
    .map((v: any) => ({ ...v, ...score(v) }))
    .filter((v: any) => v.score >= CANDIDATE_MIN_SCORE)
    .sort((a: any, b: any) => b.score - a.score)
    .slice(0, 8)
  out.candidates = scored

  // 4. nothing convincing among the vendors → partners that are not flagged vendor yet
  if (!scored.some((c: any) => c.score >= 0.7) && !out.remembered) {
    const word = nameTokens(name).filter(w => w.length >= 5).sort((a, b) => b.length - a.length)[0]
    if (word) {
      try {
        const filter = `IsActive eq true AND IsVendor eq false AND ${orgScope} AND contains(tolower(Name),'${word.replace(/'/g, '')}')`
        const res: any = await fetchHelper(event, `models/c_bpartner?$filter=${string.urlEncode(filter)}&$top=10`, 'GET', token, null)
        const extra = (res?.records || []).map(slim)
          .map((v: any) => ({ ...v, ...score(v) }))
          .filter((v: any) => v.score >= CANDIDATE_MIN_SCORE)
        out.candidates = [...out.candidates, ...extra].sort((a: any, b: any) => b.score - a.score).slice(0, 8)
      } catch {}
    }
  }

  return out
}

export default defineEventHandler(async (event) => {
  let data: any = {}
  try {
    data = await handleFunc(event)
  } catch (err: any) {
    try {
      const authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error: any) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
    }
  }
  return data
})
