import { defineSisHandler } from "../../../../utils/listings/routeHelper"
import { listListings } from "../../../../utils/listings/listingStore"
import { enrichWithProducts } from "../../../../utils/sisMatching"
import { amazonDomainForOrderSource } from "../../../../utils/amazonSpApi"
import { marketplaceKeyOf, marketplaceLabel, supportsListings } from "../../../../utils/listings/adapters"
import { shopifyStoreHandle } from "../../../../utils/listings/adapters/shopify"
import { shopwareUrls } from "../../../../utils/listings/adapters/shopware"
import { string } from 'alga-js'
import { feeModelForListing, vatRateForTaxCategory } from "../../../../utils/listings/feeModel"
import { minGrossPrice, marginAt } from "../../../../../shared/utils/sisPricing"

// GET /api/sales/sms/listings?orderSourceId=1,2,3&includeInactive=1   — one or several order sources
// GET /api/sales/sms/listings?all=1                                    — every order source with a listings adapter
// The persisted listings (CUST_MarketplaceListing) joined with the internal product data
// (price list price, qty on hand, BOM components). iDempiere only — no marketplace call.
// `sources` carries the per-source marketplace / link data the page needs for mixed lists.
const sourceInfo = (os: any) => {
  const marketplace = marketplaceKeyOf(os)
  const shopDomain = String(os?.marketplace_url || os?.Marketplace_URL || '').replace(/^https?:\/\//i, '').replace(/\/.*$/, '')
  return {
    id: Number(os.id), name: os.Name || '', orgId: Number(os?.AD_Org_ID?.id) || 0, orgName: os?.AD_Org_ID?.identifier || '',
    marketplace, marketplaceName: marketplaceLabel(marketplace), supportsListings: supportsListings(os),
    marketplaceDomain: amazonDomainForOrderSource(os),
    shop: marketplace.startsWith('shopify') ? { storeHandle: shopifyStoreHandle(os), domain: shopDomain }
      : marketplace === 'shopware' ? { baseUrl: shopwareUrls(os).baseUrl } : null
  }
}

// Shipping cost of one national parcel ≤ 2 kg for the pricing floor, from the carrier CONTRACT rates
// (CUST_CarrierRate): DHL Paket band containing 2 kg / lowest DPD Classic band.
//   DHL: + the per-parcel add-ons DHL bills on top of the base — "Maut- und CO2-Zuschlag" 0,19 € (contract
//        row), Peakzuschlag 0,19 € Nov–Dec (contract row), Peak-in-Peak 0,50 € (24.11.–7.12.) and the monthly
//        energy surcharge the table only holds as a 0 % placeholder → env SMS_DHL_ENERGY_PCT (11/2026 = 4,75 %).
//   DPD: per the myDPD G300 price list (reviewed 2026-11-04) prices are "zzgl. Maut- und Treibstoffzuschlägen"; the
//        standard surcharges per parcel are Mautzuschlag 0,35 € (SMS_DPD_TOLL), Sicherheitsgebühr 0,15 €
//        (SMS_DPD_SECURITY), Energiezuschlag Straße (variable, dpd.de/energiezuschlag → SMS_DPD_ENERGY_PCT, 11/2026 =
//        31 %) and Emergency Fuel Surcharge (variable → SMS_DPD_EFS_PCT, 11/2026 = 4,08 %). No peak surcharge for
//        parcels (only tyres) → SMS_DPD_PEAK default 0.
// Packaging per parcel: SMS_PACKAGING_COST (0,50 €). Cached per process (10 min).
const SHIPPING_FALLBACK = { dhl: 4.15, dpd: 3.00 }
const pctEnv = (k: string, d: number) => { const n = Number(process.env[k]); return Number.isFinite(n) && n >= 0 ? n : d }
const r2 = (n: number) => Math.round(n * 100) / 100
let shippingCache: { at: number; value: any } | null = null
const loadShippingDefaults = async (f: any) => {
  if (shippingCache && Date.now() - shippingCache.at < 10 * 60 * 1000) return shippingCache.value
  const packaging = Math.max(0, Number(process.env.SMS_PACKAGING_COST ?? 0.5) || 0)
  const now = new Date()
  const month = now.getMonth() + 1, day = now.getDate()
  const peakSeason = month === 11 || month === 12
  const peakInPeak = (month === 11 && day >= 24) || (month === 12 && day <= 7)
  const energy = { dhl: pctEnv('SMS_DHL_ENERGY_PCT', 4.75), dpd: pctEnv('SMS_DPD_ENERGY_PCT', 31), dpdEfs: pctEnv('SMS_DPD_EFS_PCT', 4.08) }
  const dpdExtra = { toll: pctEnv('SMS_DPD_TOLL', 0.35), security: pctEnv('SMS_DPD_SECURITY', 0.15), peak: pctEnv('SMS_DPD_PEAK', 0) }
  type Part = { label: string; amount: number }
  const build = (base: number, baseLabel: string, parts: Part[]) => ({ base, baseLabel, parts, price: r2(base + parts.reduce((a, p) => a + p.amount, 0)), label: `${baseLabel} ${base.toFixed(2)} €` + parts.map(p => ` + ${p.label} ${p.amount.toFixed(2)} €`).join('') })
  const value: any = { packaging, energy, peakSeason, peakInPeak, dhl: null as any, dpd: null as any }
  let dhlBase: any = null, dpdBase: any = null, dhlSur: any[] = [], dpdSur: any[] = []
  try {
    const q = async (service: string, rateType: string) => {
      const res: any = await f(`models/cust_carrierrate?$filter=${string.urlEncode(`ServiceCode eq '${service}' and RateType eq '${rateType}' and IsActive eq true`)}&$orderby=${string.urlEncode('RangeWeightTo asc')}&$top=100&$select=ServiceName,C_Country_ID,RangeWeightFrom,RangeWeightTo,BasePrice,PricePerKG,IsPercentage,Description`)
      return (res?.records || []).filter((r: any) => /germany|deutschland/i.test(String(r?.C_Country_ID?.identifier || '')) || !r?.C_Country_ID)
    }
    const [dhl, dpd, ds, ps] = await Promise.all([q('DHL_PAKET', 'base'), q('DPD_CLASSIC', 'base'), q('DHL_PAKET', 'surcharge'), q('DPD_CLASSIC', 'surcharge')])
    const band = (rows: any[], kg: number) => rows.find(r => Number(r.RangeWeightFrom) <= kg && Number(r.RangeWeightTo) >= kg) || rows[0]
    dhlBase = band(dhl, 2); dpdBase = band(dpd, 2); dhlSur = ds; dpdSur = ps   // dpdSur kept for a later opt-in of contract surcharge rows
  } catch { /* fallbacks below */ }
  const basePrice = (r: any, fallback: number) => (r ? r2(Number(r.BasePrice) + Number(r.PricePerKG || 0) * 2) : fallback)
  const fixedRow = (rows: any[], re: RegExp) => { const r = rows.find(x => re.test(String(x.Description || '')) && !(x.IsPercentage === true || x.IsPercentage === 'Y')); return r ? Number(r.BasePrice) : null }
  // DHL Paket national
  {
    const base = basePrice(dhlBase, SHIPPING_FALLBACK.dhl)
    const parts: Part[] = []
    if (energy.dhl > 0) parts.push({ label: `energy ${energy.dhl} %`, amount: r2(base * energy.dhl / 100) })
    const toll = fixedRow(dhlSur, /maut/i) ?? 0.19
    if (toll) parts.push({ label: 'toll/CO2', amount: toll })
    if (peakSeason) parts.push({ label: 'peak Nov–Dec', amount: fixedRow(dhlSur, /^peakzuschlag/i) ?? 0.19 })
    if (peakInPeak) parts.push({ label: 'peak-in-peak', amount: fixedRow(dhlSur, /peak-in-peak/i) ?? 0.5 })
    value.dhl = build(base, dhlBase ? `${dhlBase.ServiceName || 'DHL Paket'} ${dhlBase.RangeWeightFrom}–${dhlBase.RangeWeightTo} kg` : 'DHL Paket ≤ 2 kg (fallback)', parts)
  }
  // DPD Classic national
  {
    const base = basePrice(dpdBase, SHIPPING_FALLBACK.dpd)
    const parts: Part[] = []
    if (energy.dpd > 0) parts.push({ label: `energy ${energy.dpd} %`, amount: r2(base * energy.dpd / 100) })
    if (energy.dpdEfs > 0) parts.push({ label: `emergency fuel ${energy.dpdEfs} %`, amount: r2(base * energy.dpdEfs / 100) })
    if (dpdExtra.toll > 0) parts.push({ label: 'toll', amount: dpdExtra.toll })
    if (dpdExtra.security > 0) parts.push({ label: 'security fee', amount: dpdExtra.security })
    if (peakSeason && dpdExtra.peak > 0) parts.push({ label: 'peak Nov–Dec', amount: dpdExtra.peak })
    value.dpd = build(base, dpdBase ? `${dpdBase.ServiceName || 'DPD Classic'} ${dpdBase.RangeWeightFrom}–${dpdBase.RangeWeightTo} kg` : 'DPD Classic (fallback)', parts)
  }
  shippingCache = { at: Date.now(), value }
  return value
}

export default defineSisHandler(async ({ event, token, f }) => {
  const q = getQuery(event)
  const all = q.all === '1' || q.all === 'true'
  const ids = String(q.orderSourceId || '').split(',').map(s => Number(s.trim())).filter(n => Number.isFinite(n) && n > 0)
  if (!all && !ids.length) return { status: 400, message: 'orderSourceId (comma separated) or all=1 is required' }

  let sources: any[] = []
  if (all) {
    const res: any = await f(`models/c_ordersource?$filter=${string.urlEncode('isActive eq true')}&$orderby=${string.urlEncode('Name asc')}&$top=500`)
    sources = (res?.records || []).filter((os: any) => supportsListings(os))
  } else {
    for (const id of ids) {
      const os: any = await f(`models/c_ordersource/${id}`)
      if (os?.id) sources.push(os)
    }
    if (!sources.length) return { status: 404, message: 'Order source not found' }
  }

  const infos = Object.fromEntries(sources.map(os => [String(os.id), sourceInfo(os)]))
  const activeOnly = q.includeInactive !== '1'
  const listings: any[] = []
  for (const os of sources) listings.push(...await listListings(f, Number(os.id), { activeOnly, top: 5000 }))

  // product enrichment is org-scoped (return locators) → one call per org
  const config: any = useRuntimeConfig(event)
  const priceListId = config.public?.pricelistid || '101'
  const enrich = new Map<number, any>()
  const byOrg = new Map<number, number[]>()
  for (const l of listings) {
    if (!l.productId) continue
    const orgId = infos[String(l.orderSourceId)]?.orgId || 0
    byOrg.set(orgId, [...(byOrg.get(orgId) || []), l.productId])
  }
  for (const [orgId, productIds] of byOrg) for (const [pid, e] of await enrichWithProducts(event, token, orgId, productIds, priceListId)) enrich.set(pid, e)

  // Purchase cost of one offer unit: BOM → Σ component purchase price × qty per bundle (the bundle's own
  // list price only when a component has no price), plain product → its purchase price.
  const purchaseCostOf = (p: any): { cost: number | null; parts: { value: string; name: string; qty: number; price: number | null }[]; basis: 'bom' | 'product' | 'none'; complete: boolean } => {
    if (!p) return { cost: null, parts: [], basis: 'none', complete: false }
    const comps: any[] = p.bomComponents || []
    if (p.isBom && comps.length) {
      const parts = comps.map(c => ({ value: c.value, name: c.name, qty: Number(c.qtyBom) || 0, price: c.purchasePrice ?? null }))
      const complete = parts.every(x => x.price != null)
      if (complete) return { cost: Math.round(parts.reduce((sum, x) => sum + x.qty * (x.price || 0), 0) * 100) / 100, parts, basis: 'bom', complete: true }
      if (p.purchasePrice != null) return { cost: p.purchasePrice, parts, basis: 'product', complete: false }
      return { cost: null, parts, basis: 'none', complete: false }
    }
    return p.purchasePrice != null ? { cost: p.purchasePrice, parts: [], basis: 'product', complete: true } : { cost: null, parts: [], basis: 'none', complete: false }
  }

  const shipping = await loadShippingDefaults(f)
  const rows = listings.map(l => {
    const p = l.productId ? enrich.get(l.productId) : null
    const src = infos[String(l.orderSourceId)]
    const purchase = purchaseCostOf(p)
    const vatRate = vatRateForTaxCategory(p?.taxCategoryName)
    const feeModel = feeModelForListing(src?.marketplace || 'unknown', l)
    // full cost of one shipped unit = purchase + packaging + carrier; DHL is the default carrier, DPD shown next to it
    const costDhl = purchase.cost != null ? Math.round((purchase.cost + shipping.packaging + shipping.dhl.price) * 100) / 100 : null
    const costDpd = purchase.cost != null ? Math.round((purchase.cost + shipping.packaging + shipping.dpd.price) * 100) / 100 : null
    const minPrice = costDhl != null ? minGrossPrice(costDhl, vatRate, feeModel) : null
    const minPriceDpd = costDpd != null ? minGrossPrice(costDpd, vatRate, feeModel) : null
    const target = l.price ?? l.remotePrice ?? null
    const margin = costDhl != null && target != null ? marginAt(Number(target), costDhl, vatRate, feeModel) : null
    return {
      ...l,
      orderSourceName: src?.name || l.orderSourceName || '',
      marketplace: src?.marketplace || 'unknown',
      productName: p?.productName || '',
      productValue: p?.productValue || '',
      productAsin: p?.asin || null,
      strapiProductDocumentId: p?.strapiProductDocumentId || null,
      isBom: p?.isBom || false,
      // BOM: components carry the stock, not the bundle → the FIRST component's qty is the row's
      // internal qty (all components with qty per bundle + stock go to the tooltip).
      bomComponents: p?.bomComponents || [],
      bomOwnQtyOnHand: p?.isBom ? (p?.internalQtyOnHand ?? null) : null,
      internalPrice: p?.internalPrice ?? null,
      // pricing floor: purchase cost (net) + marketplace fees + VAT → min. gross price, margin at the target price
      purchaseCost: purchase.cost, purchaseParts: purchase.parts, purchaseBasis: purchase.basis, purchaseComplete: purchase.complete,
      purchasePriceOwn: p?.purchasePrice ?? null, purchasePriceListName: p?.purchasePriceListName || null,
      packagingCost: shipping.packaging, shippingDhl: shipping.dhl.price, shippingDpd: shipping.dpd.price,
      vatRate, feeModel, minPrice, minPriceDpd, marginAtTarget: margin,
      internalQtyOnHand: p?.bomComponents?.[0] ? p.bomComponents[0].onHand : (p?.internalQtyOnHand ?? null),
      returnQtyOnHand: p?.bomComponents?.[0] ? p.bomComponents[0].returnOnHand : (p?.returnQtyOnHand ?? 0)
    }
  })
  const counts = {
    total: rows.length,
    matched: rows.filter(r => r.productId).length,
    unmatched: rows.filter(r => !r.productId).length,
    queued: rows.filter(r => r.syncStatus === 'queued' || r.syncStatus === 'sending').length,
    sent: rows.filter(r => r.syncStatus === 'sent').length,
    errors: rows.filter(r => r.syncStatus === 'error').length,
    delisted: rows.filter(r => r.listingStatus === 'delisted').length
  }
  const first = infos[String(sources[0]?.id)]
  return {
    status: 200, all, sources: infos,
    // single-source fields kept for callers that pass one id
    orderSourceId: first?.id ?? null, orgId: first?.orgId ?? null, orderSourceName: first?.name || '',
    marketplace: first?.marketplace, marketplaceName: first?.marketplaceName, supportsListings: first?.supportsListings,
    marketplaceDomain: first?.marketplaceDomain, shop: first?.shop ?? null,
    shipping, rows, counts
  }
})
