// NB: alga-js string.urlEncode leaves '+' unencoded → iDempiere reads "C+" as "C ". Use encodeURIComponent.
const enc = (v: string) => encodeURIComponent(v)
import refreshTokenHelper from "../../../utils/refreshTokenHelper"
import forceLogoutHelper from "../../../utils/forceLogoutHelper"
import errorHandlingHelper from "../../../utils/errorHandlingHelper"
import getTokenHelper from "../../../utils/getTokenHelper"
import fetchHelper from "../../../utils/fetchHelper"
import { getNonShippableLocatorIds } from "../../../utils/returnLocatorHelper"

/**
 * Mobile reconditioning — find the customer return(s) behind a scanned RMA number.
 *
 * The scan is matched ONLY against the RMA DocumentNo (the return label's barcode
 * encodes the RMA number; the label also prints it as "RETOURE <no>", so a leading
 * "RETOURE"/"RMA" prefix is tolerated when the text is typed). A customer-return
 * DocumentNo or a product code deliberately does NOT match — a product-code hit used
 * to surface every return containing that article, which looked like a search over
 * unrelated return documents.
 *
 * For each return only the lines whose stock sits on a NON-shippable locator
 * (M_LocatorType.IsAvailableForShipping = false) are returned, capped by the qty
 * currently on hand at that locator and reduced by what earlier reconditioning
 * movements (M_Movement.M_Customer_Return_ID) already moved.
 */
const MAX_RETURNS = 10

const esc = (v: string) => String(v).replace(/'/g, "''")
const idOf = (v: any) => (v && typeof v === 'object') ? v.id : v
const orFilter = (col: string, ids: any[]) => '(' + ids.map((id) => `${col} eq ${id}`).join(' OR ') + ')'

const fetchReturnsByFilter = async (event: any, token: any, filter: string) => {
  const res: any = await fetchHelper(
    event,
    `models/m_inout?$filter=${enc(`(${filter}) AND MovementType eq 'C+' AND IsActive eq true`)}` +
      `&$expand=${enc('M_RMA_ID,C_BPartner_ID,AD_Org_ID')}` +
      `&$orderby=${enc('M_InOut_ID desc')}&$top=${MAX_RETURNS}`,
    'GET', token, null
  )
  return (res?.records || []).filter((r: any) => (r?.DocStatus?.id ?? r?.DocStatus) === 'CO')
}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const body = await readBody(event)
  // Tolerate the printed label text ("RETOURE 1000123") and an "RMA 1000123" prefix
  const search = String(body?.search ?? '').trim().replace(/^(RETOURE|RMA)[\s:#-]*/i, '').trim()
  if (!search) {
    throw createError({ statusCode: 400, statusMessage: 'search is required' })
  }
  const safe = esc(search)

  let matchedBy = ''
  let returns: any[] = []
  const matchedProductId: number | null = null // kept for the page's response shape

  // RMA DocumentNo only
  const rmaRes: any = await fetchHelper(event, `models/m_rma?$filter=${enc(`DocumentNo eq '${safe}' AND IsActive eq true`)}&$top=5`, 'GET', token, null)
  const rmaIds = (rmaRes?.records || []).map((r: any) => r.id).filter(Boolean)
  if (rmaIds.length) {
    returns = await fetchReturnsByFilter(event, token, orFilter('M_RMA_ID', rmaIds))
    if (returns.length) matchedBy = 'rma'
  }

  if (!returns.length) {
    return { found: false, matchedBy: '', search, returns: [] }
  }

  // Non-shippable locators per org (cached helper) — resolve once per distinct org
  const orgIds = [...new Set(returns.map((r: any) => idOf(r.AD_Org_ID)).filter(Boolean))]
  const nonShippableByOrg: Record<string, Set<number>> = {}
  for (const orgId of orgIds) {
    nonShippableByOrg[String(orgId)] = await getNonShippableLocatorIds(event, token, orgId)
  }

  const mapped: any[] = []
  for (const ret of returns) {
    const orgId = idOf(ret.AD_Org_ID)
    const nonShippable = nonShippableByOrg[String(orgId)] || new Set<number>()

    const lineRes: any = await fetchHelper(event,
      `models/m_inoutline?$filter=${enc(`M_InOut_ID eq ${ret.id} AND IsActive eq true`)}` +
      `&$expand=${enc('M_Product_ID,M_Locator_ID,C_UOM_ID')}&$orderby=${enc('Line asc')}&$top=200`,
      'GET', token, null)
    const rawLines = (lineRes?.records || []).filter((l: any) => l?.M_Product_ID?.id && Number(l?.MovementQty) > 0)

    // Stock currently on the return locators for these products (one query per return)
    const prodIds = [...new Set(rawLines.map((l: any) => l.M_Product_ID.id))]
    const locIds = [...new Set(rawLines.map((l: any) => idOf(l.M_Locator_ID)).filter(Boolean))]
    const onHand: Record<string, number> = {}
    if (prodIds.length && locIds.length) {
      try {
        const stRes: any = await fetchHelper(event,
          `models/m_storage?$filter=${enc(`${orFilter('M_Product_ID', prodIds)} AND ${orFilter('M_Locator_ID', locIds)}`)}&$select=M_Product_ID,M_Locator_ID,QtyOnHand&$top=500`,
          'GET', token, null)
        for (const st of (stRes?.records || [])) {
          const key = `${idOf(st.M_Product_ID)}|${idOf(st.M_Locator_ID)}`
          onHand[key] = (onHand[key] || 0) + (Number(st.QtyOnHand) || 0)
        }
      } catch (e) {
        console.warn('[reconditioning/lookup] storage lookup failed:', e)
      }
    }

    // Already reconditioned from this return (needs M_Movement.M_Customer_Return_ID — fail-soft)
    const alreadyMoved: Record<string, number> = {}
    try {
      const mvRes: any = await fetchHelper(event,
        `models/m_movement?$filter=${enc(`M_Customer_Return_ID eq ${ret.id} AND DocStatus eq 'CO'`)}&$expand=m_movementline&$top=50`,
        'GET', token, null)
      for (const mv of (mvRes?.records || [])) {
        for (const ml of (mv?.m_movementline || [])) {
          const pid = idOf(ml.M_Product_ID)
          if (pid) alreadyMoved[String(pid)] = (alreadyMoved[String(pid)] || 0) + (Number(ml.MovementQty) || 0)
        }
      }
    } catch (e) {
      // column not there yet → nothing to subtract
    }

    let hiddenShippable = 0
    const lines: any[] = []
    for (const l of rawLines) {
      const locId = idOf(l.M_Locator_ID)
      if (!locId || !nonShippable.has(Number(locId))) { hiddenShippable++; continue }
      const p = l.M_Product_ID
      const returnedQty = Number(l.MovementQty) || 0
      const stockQty = onHand[`${p.id}|${locId}`] ?? null
      const doneQty = alreadyMoved[String(p.id)] || 0
      let maxQty = Math.max(0, returnedQty - doneQty)
      if (stockQty !== null) maxQty = Math.min(maxQty, Math.max(0, stockQty))
      lines.push({
        lineId: l.id,
        line: l.Line,
        productId: p.id,
        productName: p.Name || '',
        productValue: p.Value || '',
        productSku: p.SKU || '',
        productUpc: p.UPC || '',
        imageUrl: p.ImageURL || null,
        uomId: idOf(l.C_UOM_ID) || null,
        returnedQty,
        stockQty,
        doneQty,
        maxQty,
        locatorId: locId,
        locatorCode: l.M_Locator_ID?.Value || l.M_Locator_ID?.identifier || '',
        locatorType: l.M_Locator_ID?.M_LocatorType_ID?.identifier || '',
        description: l.Description || ''
      })
    }

    mapped.push({
      id: ret.id,
      documentNo: ret.DocumentNo || String(ret.id),
      movementDate: ret.MovementDate || '',
      docStatus: ret.DocStatus?.id ?? ret.DocStatus ?? '',
      rmaId: idOf(ret.M_RMA_ID) || null,
      rmaDocumentNo: ret.M_RMA_ID?.DocumentNo || ret.M_RMA_ID?.identifier || '',
      partnerId: idOf(ret.C_BPartner_ID) || null,
      partnerName: ret.C_BPartner_ID?.Name || ret.C_BPartner_ID?.identifier || '',
      orgId: orgId || null,
      orgName: ret.AD_Org_ID?.Name || ret.AD_Org_ID?.identifier || '',
      warehouseId: idOf(ret.M_Warehouse_ID) || null,
      hiddenShippableLines: hiddenShippable,
      lines,
      matchedProductId
    })
  }

  return { found: true, matchedBy, search, returns: mapped }
}

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