import refreshTokenHelper from "../../../utils/refreshTokenHelper";
import errorHandlingHelper from "../../../utils/errorHandlingHelper";
import fetchHelper from "../../../utils/fetchHelper";
import laravelHelper from "../../../utils/laravelHelper";

// POST /api/ffn/stocks/set-all
// Body: { orderSourceId: string, quantity: number (>= 0, integer), note?: string }
//
// VIRTUAL fixed-qty sync. Sets the JTL-FFN available stock level of EVERY (non-BOM)
// product belonging to the selected Order Source's merchant to ONE fixed quantity.
// JTL-FFN's stock API is delta-based, so per product we push (quantity - currentLevel).
//
// Pushed to JTL-FFN ONLY. iDempiere is never read for stock nor written — no
// m_storageonhand / m_product change happens here. The single iDempiere call is the
// read of the Order Source record (for FFN credentials), nothing more.

const buildFfnHeaders = (os: any, ffnToken: string) => {
  const headers: any = { 'content-type': 'application/json', 'accept': 'application/json' }
  if(ffnToken) {
    // JTL FFN expects the proprietary scheme: Authorization: ffn <token>
    headers['Authorization'] = `ffn ${ffnToken}`
    headers['x-application-id'] = 'JPCB0XLOGYOU'
    headers['x-application-version'] = '0.1'
  }
  if(os?.marketplace_key) headers['x-api-key'] = os.marketplace_key
  if(os?.marketplace_secret) headers['x-api-secret'] = os.marketplace_secret
  if(os?.jtl_merchantID) headers['x-merchant-id'] = os.jtl_merchantID
  return headers
}

const normalizeHost = (baseUrl: string) => {
  let host = baseUrl.trim()
  if(!/^https?:\/\//i.test(host)) host = `https://${host}`
  return host.replace(/\/$/, '')
}

// Resolve the path to append to `host` for a fulfiller sub-resource, tolerating a
// marketplace_url that already includes /api/v1/fulfiller(/<resource>) — mirrors the
// other ffn routes (stocks.get.ts, products/index.get.ts, adjustments.post.ts).
const resolveEndpoint = (host: string, resource: string) => {
  const full = `/api/v1/fulfiller/${resource}`
  if(new RegExp(`/api/v1/fulfiller/${resource}/?$`, 'i').test(host)) return ''
  if(/\/api\/v1\/fulfiller\/?$/i.test(host)) return `/${resource}`
  return full
}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const body = await readBody(event)
  const orderSourceId = body?.orderSourceId as string
  const note: string = (body?.note && String(body.note)) || 'Virtual fixed-qty sync via ERP'

  if(!orderSourceId) return { status: 400, message: 'orderSourceId is required' }

  // Validate quantity: whole number >= 0 (0 is allowed — zeroes out FFN stock virtually)
  const quantity = Number(body?.quantity)
  if(!Number.isFinite(quantity) || quantity < 0 || !Number.isInteger(quantity)) {
    return { status: 400, message: 'quantity must be a whole number 0 or greater' }
  }

  // Load + validate Order Source (must be JTL-FFN)
  const os: any = await fetchHelper(event, `models/c_ordersource/${orderSourceId}`, 'GET', token, null)
  if(!os) return { status: 404, message: 'Order Source not found' }
  if(!(os?.Marketplace?.identifier === 'jtl-ffn' || os?.marketplace === '7' || os?.Marketplace?.id === '7')) {
    return { status: 400, message: 'Selected Order Source is not JTL-FFN' }
  }

  const cfg: any = useRuntimeConfig()
  const baseUrl: string = os?.marketplace_url || cfg?.api?.jtlFulfillerUrl || ''
  if(!baseUrl) return { status: 400, message: 'No marketplace_url configured for this Order Source' }
  const ffnToken: string = os?.marketplace_token || cfg?.api?.jtlFulfillerToken || ''

  const headers = buildFfnHeaders(os, ffnToken)
  const host = normalizeHost(baseUrl)
  const productsEndpoint = resolveEndpoint(host, 'products')
  const stocksEndpoint = resolveEndpoint(host, 'stocks')
  const adjEndpoint = resolveEndpoint(host, 'stocks/adjustments')

  // The fulfiller /products endpoint is fulfiller-wide and ignores x-merchant-id; scope
  // to THIS merchant with an OData $filter so we don't touch another merchant's articles.
  const merchantId: string = os?.jtl_merchantID ? String(os.jtl_merchantID).trim() : ''
  const merchantFilter = merchantId ? `merchantId eq '${merchantId.replace(/'/g, "''")}'` : ''

  const PAGE = 500
  const SKIP_CAP = 200000 // runaway guard

  // 1) Page through ALL products of this merchant (need specifications for BOM detection)
  const products: any[] = []
  for(let skip = 0; skip <= SKIP_CAP; skip += PAGE) {
    const params = new URLSearchParams()
    params.set('$top', String(PAGE))
    params.set('$skip', String(skip))
    if(merchantFilter) params.set('$filter', merchantFilter)
    const url = `${host}${productsEndpoint}?${params.toString()}`
    const res: any = await $fetch(url, { method: 'GET', headers })
    const items = res?.items || []
    products.push(...items)
    if(items.length < PAGE) break
  }

  // 2) Page through ALL stocks → map by jfsku (jfsku is globally unique, so cross-merchant
  //    rows are simply never looked up). Carries current level + warehouse + batch details.
  const stocksByJfsku: Record<string, any> = {}
  for(let skip = 0; skip <= SKIP_CAP; skip += PAGE) {
    const params = new URLSearchParams()
    params.set('$top', String(PAGE))
    params.set('$skip', String(skip))
    const url = `${host}${stocksEndpoint}?${params.toString()}`
    const res: any = await $fetch(url, { method: 'GET', headers })
    const items = res?.items || []
    for(const s of items) { if(s?.jfsku) stocksByJfsku[s.jfsku] = s }
    if(items.length < PAGE) break
  }

  // 3) Single fulfiller warehouse — fallback for products that have no stock record yet
  //    (e.g. a merchant's first sync). Only needed when quantity > 0.
  let defaultWarehouseId = ''
  try {
    const wRes: any = await laravelHelper(event, 'fulfiller/warehouses', 'GET')
    const wh = (wRes?.items || wRes?.records || [])[0]
    if(wh?.warehouseId) defaultWarehouseId = wh.warehouseId
  } catch (e) { /* optional — rows with an existing stock record still resolve a warehouse */ }

  // 4) Build delta adjustments (target = quantity for every non-BOM product)
  const payloads: any[] = []
  let skippedBom = 0, skippedNoWh = 0, alreadyAtTarget = 0
  for(const p of products) {
    const jfsku = p?.jfsku || p?.Jfsku || ''
    if(!jfsku) continue
    if(p?.specifications?.isBillOfMaterials) { skippedBom++; continue } // JTL derives BOM stock from components
    const stock = stocksByJfsku[jfsku] || null
    const currentLevel = Number(stock?.stockLevel ?? 0)
    const delta = quantity - currentLevel
    if(delta === 0) { alreadyAtTarget++; continue }
    const wh = (stock?.warehouses && stock.warehouses[0]) ? stock.warehouses[0] : null
    const det = (stock?.stockLevelDetails && stock.stockLevelDetails[0]) ? stock.stockLevelDetails[0] : null
    const warehouseId = wh?.warehouseId || defaultWarehouseId
    if(!warehouseId) { skippedNoWh++; continue }
    const bb = det?.bestBefore
    payloads.push({
      fulfillerStockChangeId: `setall-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
      jfsku,
      warehouseId,
      quantity: delta,
      batch: det?.batch,
      bestBefore: bb ? { year: bb.year, month: bb.month, day: bb.day } : undefined,
      note,
      fulfillerTimestamp: new Date().toISOString()
    })
  }

  // 5) Push each adjustment to JTL-FFN sequentially. Per-item try/catch so one failure
  //    never aborts the batch (and never bubbles up to trigger a full retry/re-push).
  const adjUrl = `${host}${adjEndpoint}`
  const results: any[] = []
  for(const payload of payloads) {
    try {
      const resp = await $fetch(adjUrl, { method: 'POST', headers, body: payload })
      results.push({ ok: true, payload, response: resp })
    } catch (err: any) {
      const e = errorHandlingHelper(err?.data ?? err, err?.data ?? err)
      const status = Number(e?.status || err?.status || err?.response?.status || 500)
      const message = e?.message || err?.data?.message || err?.message || 'Failed to send FFN stock adjustment'
      results.push({ ok: false, payload, error: { status, message } })
    }
  }

  const failed = results.filter(r => !r.ok)
  return {
    status: failed.length ? 207 : 200,
    message: failed.length
      ? `${failed.length} of ${payloads.length} adjustment(s) failed — set rest to ${quantity}`
      : `Set ${payloads.length} product(s) to ${quantity} in JTL-FFN`,
    summary: {
      quantity,
      totalProducts: products.length,
      changed: payloads.length,
      alreadyAtTarget,
      skippedBom,
      skippedNoWh,
      failed: failed.length
    },
    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
    }
  }
})
