import { string } from 'alga-js'
import refreshTokenHelper from "../../utils/refreshTokenHelper"
import errorHandlingHelper from "../../utils/errorHandlingHelper"
import fetchHelper from "../../utils/fetchHelper"
import getReturnLocatorIds from "../../utils/returnLocatorHelper"

// Batch variant of /api/local/qty-on-hand. Resolves many products' local qty-on-hand
// in a few chunked iDempiere queries instead of one request per product (the FFN stock
// page would otherwise fire ~900 requests for kfp). Returns a map keyed by jfsku.
// Body: { items: [{ jfsku, merchantSku }] }
// Response: { status, results: { [jfsku]: { productId, qtyOnHand, returnQtyOnHand } } }

const CHUNK = 40

const sumSellableQty = (storages: any[], returnLocatorIds: Set<number>) => {
  let qtyOnHand = 0
  let returnQtyOnHand = 0
  if (!Array.isArray(storages)) return { qtyOnHand, returnQtyOnHand }
  for (const row of storages) {
    const qty = Number(row?.QtyOnHand || 0)
    const locator = row?.M_Locator_ID
    const locatorId = locator?.id ?? locator
    const isReturn =
      locatorId !== undefined && locatorId !== null && returnLocatorIds.has(Number(locatorId))
    if (isReturn) returnQtyOnHand += qty
    else qtyOnHand += qty
  }
  return { qtyOnHand, returnQtyOnHand }
}

const chunk = <T,>(arr: T[], n: number): T[][] => {
  const out: T[][] = []
  for (let i = 0; i < arr.length; i += n) out.push(arr.slice(i, i + n))
  return out
}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const body = await readBody(event)
  const items: any[] = Array.isArray(body?.items) ? body.items : []
  if (items.length === 0) return { status: 400, message: 'items array is required', results: {} }

  const productSelect = string.urlEncode('m_product_id,Name,SKU,jfsku,AD_Org_ID')
  const storageExpand = string.urlEncode('M_Storage($expand=M_Locator_ID)')

  // De-duplicate the lookup keys.
  const jfskus = [...new Set(items.map(i => String(i?.jfsku || '').trim()).filter(Boolean))]
  const skuByMissing = new Map<string, string>() // merchantSku -> intended jfsku (for fallback)
  for (const i of items) {
    const jf = String(i?.jfsku || '').trim()
    const sku = String(i?.merchantSku || '').trim()
    if (jf && sku) skuByMissing.set(sku, jf)
  }

  const results: Record<string, any> = {}
  const foundJfskus = new Set<string>()
  const products: any[] = []

  // Pass 1: match by jfsku in chunks.
  for (const group of chunk(jfskus, CHUNK)) {
    const orParts = group.map(j => `jfsku eq '${j.replace(/'/g, "''")}'`).join(' or ')
    const filter = string.urlEncode(`(${orParts})`)
    const res: any = await fetchHelper(
      event,
      `models/m_product?$select=${productSelect}&$expand=${storageExpand}&$filter=${filter}&$top=${group.length}`,
      'GET', token, null
    )
    for (const p of (res?.records || [])) {
      if (p?.jfsku) foundJfskus.add(String(p.jfsku))
      products.push(p)
    }
  }

  // Pass 2: SKU fallback for any input jfsku not matched above (some products only map by SKU).
  const missingSkus = [...skuByMissing.entries()]
    .filter(([, jf]) => !foundJfskus.has(jf))
    .map(([sku]) => sku)
  const skuToProduct = new Map<string, any>()
  for (const group of chunk([...new Set(missingSkus)], CHUNK)) {
    const orParts = group.map(s => `SKU eq '${s.replace(/'/g, "''")}'`).join(' or ')
    const filter = string.urlEncode(`(${orParts})`)
    const res: any = await fetchHelper(
      event,
      `models/m_product?$select=${productSelect}&$expand=${storageExpand}&$filter=${filter}&$top=${group.length}`,
      'GET', token, null
    )
    for (const p of (res?.records || [])) {
      if (p?.SKU) skuToProduct.set(String(p.SKU), p)
      products.push(p)
    }
  }

  // Resolve the return-locator set once per distinct org (helper is per-org cached).
  const orgIds = [...new Set(products.map(p => p?.AD_Org_ID?.id ?? p?.AD_Org_ID ?? null))]
  const returnLocByOrg: Record<string, Set<number>> = {}
  for (const oid of orgIds) {
    returnLocByOrg[String(oid)] = await getReturnLocatorIds(event, token, oid)
  }

  const computeFor = (p: any) => {
    const oid = p?.AD_Org_ID?.id ?? p?.AD_Org_ID ?? null
    const { qtyOnHand, returnQtyOnHand } = sumSellableQty(p?.M_Storage || [], returnLocByOrg[String(oid)] || new Set())
    return { productId: Number(p?.m_product_id || p?.id), qtyOnHand, returnQtyOnHand }
  }

  // Map jfsku-matched products.
  for (const p of products) {
    if (p?.jfsku) results[String(p.jfsku)] = computeFor(p)
  }
  // Map SKU-fallback products back onto their intended jfsku key.
  for (const [sku, jf] of skuByMissing.entries()) {
    if (foundJfskus.has(jf)) continue
    const p = skuToProduct.get(sku)
    if (p) results[jf] = computeFor(p)
  }

  return { status: 200, results }
}

export default defineEventHandler(async (event) => {
  try {
    return await handleFunc(event)
  } catch (err: any) {
    try {
      const authToken = await refreshTokenHelper(event)
      return await handleFunc(event, authToken)
    } catch (error: any) {
      const data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
      if ([401, 402, 403, 407].includes(Number(data.status))) {
        //@ts-ignore
        setCookie(event, 'user', null)
      }
      return { ...data, results: {} }
    }
  }
})
