/**
 * Lead qualification fields — custom AD_User columns (Entity Type U, created 2026-10-21):
 *
 *   write key (REST, lowercase-first) → read key   type       notes
 *   leadRating            → LeadRating            Integer    1–5 stars, NULL = unrated
 *   followUpDate          → FollowUpDate          Date       Wiedervorlage (YYYY-MM-DD)
 *   followUpNote          → FollowUpNote          String 255 what to do on that date
 *   monthlyOrders         → MonthlyOrders         Integer    expected orders / month
 *   skuCount              → SkuCount              Integer    number of articles
 *   shopSystem            → ShopSystem            String 255 shop / marketplace systems
 *   expectedMonthlyValue  → ExpectedMonthlyValue  Amount     € / month (quote forecast total)
 *   winProbability        → WinProbability        Integer    0–100 %, auto-bumped by stage
 *   expectedStartDate     → ExpectedStartDate     Date       planned go-live
 *   aiScore               → AiScore               Integer    0–100, KI-Analyse dealScore
 *   avgParcelWeight       → AvgParcelWeight       String 30  e.g. "1-2 kg"
 *   leadStreet / leadZip / leadCity / leadCountry → LeadStreet (255) / LeadZip (20) / LeadCity (120) / LeadCountry (100)
 *                                                  String     address, mirrored to/from quote & contract `customer`
 *   BusinessModel         → BusinessModel         List       AD_Reference "BusinessModel" (ids 10000002…)
 *   LostReason            → LostReason            List       AD_Reference "LostReason"   (ids 10000005…)
 *
 * Lists are written as `{ id: '<AD_Ref_List.Value>' }` and read back as
 * `{ id, identifier }`. The list values are READ FROM THE API (getLeadRefList), never
 * hard-coded — the user maintains them in iDempiere. All writers here are fail-soft:
 * a qualification write must never break a send / confirmation / assessment flow.
 */
import fetchHelper from '../fetchHelper'

export const QUALIFY_READ_KEYS: Record<string, string> = {
  leadRating: 'LeadRating', followUpDate: 'FollowUpDate', followUpNote: 'FollowUpNote',
  monthlyOrders: 'MonthlyOrders', skuCount: 'SkuCount', shopSystem: 'ShopSystem',
  expectedMonthlyValue: 'ExpectedMonthlyValue', winProbability: 'WinProbability',
  expectedStartDate: 'ExpectedStartDate', aiScore: 'AiScore', avgParcelWeight: 'AvgParcelWeight',
  leadStreet: 'LeadStreet', leadZip: 'LeadZip', leadCity: 'LeadCity', leadCountry: 'LeadCountry',
  businessModel: 'BusinessModel', lostReason: 'LostReason'
}

const LIST_FIELDS: Record<string, string> = { businessModel: 'BusinessModel', lostReason: 'LostReason' }

const intField = (min: number, max: number, zeroIsNull = true) => (v: any) => {
  if (v === null || v === undefined || String(v).trim() === '') return null
  const n = Math.round(Number(v))
  if (!Number.isFinite(n) || n < min || n > max) return undefined
  return zeroIsNull && n === 0 ? null : n
}
const dateField = (v: any) => {
  if (v === null || v === undefined || String(v).trim() === '') return null
  const m = String(v).trim().match(/^(\d{4}-\d{2}-\d{2})/)
  if (!m || Number.isNaN(Date.parse(m[1]))) return undefined
  return m[1]
}
const stringField = (max: number) => (v: any) => {
  if (v === null || v === undefined) return null
  const s = String(v).trim()
  return s === '' ? null : s.slice(0, max)
}
const amountField = (v: any) => {
  if (v === null || v === undefined || String(v).trim() === '') return null
  const n = Number(String(v).replace(',', '.'))
  if (!Number.isFinite(n) || n < 0 || n > 1e9) return undefined
  return n === 0 ? null : Math.round(n * 100) / 100
}

/** Scalar normalisers: `undefined` = invalid (skip the key), `null` = clear the column. */
export const QUALIFY_SCALARS: Record<string, (v: any) => number | string | null | undefined> = {
  leadRating: intField(0, 5),
  followUpDate: dateField,
  followUpNote: stringField(255),
  monthlyOrders: intField(0, 100000000),
  skuCount: intField(0, 100000000),
  shopSystem: stringField(255),
  expectedMonthlyValue: amountField,
  winProbability: intField(0, 100, false),
  expectedStartDate: dateField,
  aiScore: intField(0, 100, false),
  avgParcelWeight: stringField(30),
  leadStreet: stringField(255),
  leadZip: stringField(20),
  leadCity: stringField(120),
  leadCountry: stringField(100)
}

// ---- reference lists (AD_Reference by name → AD_Ref_List), cached per process ----------
const listCache = new Map<string, { at: number; items: Array<{ id: string; name: string }> }>()
const LIST_TTL_MS = 10 * 60 * 1000

export const getLeadRefList = async (event: any, token: string, referenceName: string): Promise<Array<{ id: string; name: string }>> => {
  const hit = listCache.get(referenceName)
  if (hit && Date.now() - hit.at < LIST_TTL_MS) return hit.items
  const ref: any = await fetchHelper(event, `models/ad_reference?$filter=Name eq '${referenceName}'&$top=1`, 'GET', token, null)
  const refId = Number(ref?.records?.[0]?.id)
  if (!refId) throw new Error(`AD_Reference "${referenceName}" not found`)
  const res: any = await fetchHelper(event, `models/ad_ref_list?$filter=AD_Reference_ID eq ${refId} and IsActive eq true&$orderby=Value asc&$top=200`, 'GET', token, null)
  const items = (res?.records || []).map((r: any) => ({ id: String(r.Value), name: String(r.Name || r.Value) }))
  listCache.set(referenceName, { at: Date.now(), items })
  return items
}

export const getLeadLists = async (event: any, token: string) => {
  const [businessModel, lostReason] = await Promise.all([getLeadRefList(event, token, 'BusinessModel'), getLeadRefList(event, token, 'LostReason')])
  return { businessModel, lostReason }
}

/** Map a quote/contract `assumptions.businessModel` ('B2C' | 'B2B' | 'B2C+B2B') to the list id. */
const squash = (s: any) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '')
export const businessModelIdFor = (items: Array<{ id: string; name: string }>, value: any): string | null => {
  const key = squash(value)
  if (!key) return null
  const both = key.includes('b2b') && key.includes('b2c')
  const hit = items.find((i) => {
    const n = squash(i.name)
    return both ? (n.includes('b2b') && n.includes('b2c')) : n === key
  })
  return hit ? hit.id : null
}
/** Reverse: list identifier ('B2B + B2C') → quote option id ('B2C+B2B'). */
export const quoteBusinessModelFor = (identifier: any): string => {
  const k = squash(identifier)
  if (k.includes('b2b') && k.includes('b2c')) return 'B2C+B2B'
  if (k === 'b2b') return 'B2B'
  if (k === 'b2c') return 'B2C'
  return ''
}

/**
 * Normalise a client body into an iDempiere PUT patch. Unknown keys and invalid values
 * are dropped; list values are validated against the API list (unknown id → dropped).
 */
export const normalizeQualifyPatch = async (event: any, token: string, body: Record<string, any>): Promise<Record<string, any>> => {
  const patch: Record<string, any> = {}
  for (const [k, v] of Object.entries(body || {})) {
    if (QUALIFY_SCALARS[k]) {
      const norm = QUALIFY_SCALARS[k](v)
      if (norm !== undefined) patch[k] = norm
    } else if (LIST_FIELDS[k]) {
      if (v === null || v === undefined || String(v).trim() === '') { patch[LIST_FIELDS[k]] = null; continue }
      const items = await getLeadRefList(event, token, LIST_FIELDS[k])
      const id = String(v).trim()
      if (items.some((i) => i.id === id)) patch[LIST_FIELDS[k]] = { id }
    }
  }
  return patch
}

// ---- automatic stage values ----------------------------------------------------------
/** Win probability targets per pipeline stage; automatic bumps only ever RAISE the value
 *  (a sales rep's higher manual estimate survives), except `lost` / `new` which reset it. */
export const WIN_PROBABILITY: Record<string, number> = {
  new: 10, working: 25, quoteSent: 50, quoteConfirmed: 70, contractSent: 85, contractSigned: 95, converted: 100, lost: 0
}

const readLead = async (event: any, token: string, leadId: number): Promise<any> => {
  try { return await fetchHelper(event, `models/ad_user/${leadId}`, 'GET', token, null) } catch { return null }
}

/** PUT qualification values on a lead — fail-soft (logs, never throws). Returns true on success. */
export const writeLeadQualification = async (event: any, token: string, leadId: number, patch: Record<string, any>, tag = 'lead-qualify'): Promise<boolean> => {
  if (!(leadId > 0) || !patch || !Object.keys(patch).length) return false
  try {
    await fetchHelper(event, `models/ad_user/${leadId}`, 'PUT', token, patch)
    return true
  } catch (err: any) {
    console.error(`[${tag}] write skipped for lead ${leadId}:`, err?.data?.detail ?? err?.data?.title ?? err?.message ?? err)
    return false
  }
}

/**
 * Raise WinProbability to the stage's target (never lowers, except stage 'lost'/'new').
 * `source` guards partner records (C_BPartner has none of these columns).
 */
export const bumpWinProbability = async (event: any, token: string, source: string, recordId: number, stage: keyof typeof WIN_PROBABILITY, extra: Record<string, any> = {}): Promise<number | null> => {
  if (source !== 'lead' || !(recordId > 0)) return null
  const target = WIN_PROBABILITY[stage]
  if (target === undefined) return null
  let next = target
  if (stage !== 'lost' && stage !== 'new') {
    const lead = await readLead(event, token, recordId)
    const current = Number(lead?.WinProbability ?? lead?.winProbability ?? 0) || 0
    next = Math.max(current, target)
  }
  const ok = await writeLeadQualification(event, token, recordId, { winProbability: next, ...extra }, 'win-probability')
  return ok ? next : null
}

/**
 * Quote sent → copy the sizing assumptions and the forecast total onto the lead so the
 * CRM list carries what was offered: MonthlyOrders, SkuCount, BusinessModel, ShopSystem,
 * ExpectedStartDate, ExpectedMonthlyValue (+ WinProbability ≥ quoteSent).
 * Contract sent → the same minus the forecast total, WinProbability ≥ contractSent.
 */
export const syncLeadFromOffer = async (event: any, token: string, source: string, recordId: number, payload: any, stage: 'quoteSent' | 'contractSent'): Promise<void> => {
  if (source !== 'lead' || !(recordId > 0)) return
  const a = payload?.assumptions || {}
  const patch: Record<string, any> = {}
  const orders = QUALIFY_SCALARS.monthlyOrders(a.ordersPerMonth); if (orders) patch.monthlyOrders = orders
  const skus = QUALIFY_SCALARS.skuCount(a.skuCount); if (skus) patch.skuCount = skus
  const shop = QUALIFY_SCALARS.shopSystem(a.shopSystem); if (shop) patch.shopSystem = shop
  const start = QUALIFY_SCALARS.expectedStartDate(a.plannedStart); if (start) patch.expectedStartDate = start
  // address typed in the quote / contract → lead (only filled parts) — written as its OWN
  // PUT so a missing address column can never block the sizing / probability sync.
  const c = payload?.customer || {}
  const address: Record<string, any> = {}
  const street = QUALIFY_SCALARS.leadStreet(c.street); if (street) address.leadStreet = street
  const zip = QUALIFY_SCALARS.leadZip(c.zip); if (zip) address.leadZip = zip
  const city = QUALIFY_SCALARS.leadCity(c.city); if (city) address.leadCity = city
  const country = QUALIFY_SCALARS.leadCountry(c.country); if (country) address.leadCountry = country
  await writeLeadQualification(event, token, recordId, address, 'lead-address')
  if (stage === 'quoteSent') {
    const total = QUALIFY_SCALARS.expectedMonthlyValue(payload?.forecast?.totalMonthly); if (total) patch.expectedMonthlyValue = total
  }
  try {
    const items = await getLeadRefList(event, token, 'BusinessModel')
    const id = businessModelIdFor(items, a.businessModel)
    if (id) patch.BusinessModel = { id }
  } catch (err: any) { console.error('[lead-qualify] BusinessModel list unavailable:', err?.message ?? err) }
  await bumpWinProbability(event, token, source, recordId, stage, patch)
}
