/**
 * CUST_CarrierRate — carrier CONTRACT (purchase) prices per service / country /
 * weight range, plus surcharges and services. Carrier-agnostic (DHL today,
 * DPD later). Sell prices live in CUST_ShippingCost_Default / _BPOverride.
 *
 * Read casing is PascalCase (ServiceCode, BasePrice, PricePerKG …), writes use
 * lowercase-first scalars + PascalCase FK objects (see write-casing recipe).
 */
import fetchHelper from '../fetchHelper'
import type { CarrierRate, RateType } from '../../../app/utils/shippingPriceSheet'
import { carrierCodeOf } from '../../../app/utils/shippingPriceSheet'

export const CARRIER_RATE_MODEL = 'models/cust_carrierrate'
export const CARRIER_RATE_TABLE = 'CUST_CarrierRate'

const n = (v: any, d = 0): number => (Number.isFinite(Number(v)) ? Number(v) : d)
const s = (v: any): string => (v === null || v === undefined ? '' : String(v))
const bool = (v: any): boolean => v === true || v === 'Y' || v === 'true'
const dateOnly = (v: any): string => (v ? String(v).slice(0, 10) : '')

export const mapCarrierRate = (r: any, countryCodes?: Map<number, string>): CarrierRate => {
  const countryId = r?.C_Country_ID?.id ?? null
  return {
    id: n(r?.id),
    uid: r?.uid,
    shipperId: n(r?.M_Shipper_ID?.id),
    shipper: s(r?.M_Shipper_ID?.identifier),
    serviceCode: s(r?.ServiceCode ?? r?.serviceCode),
    serviceName: s(r?.ServiceName ?? r?.serviceName),
    rateType: (s(r?.RateType ?? r?.rateType) || 'base') as RateType,
    countryId: countryId ? n(countryId) : null,
    country: s(r?.C_Country_ID?.identifier),
    countryCode: countryId && countryCodes ? (countryCodes.get(n(countryId)) || '') : '',
    zone: s(r?.Zone ?? r?.zone),
    rangeFrom: n(r?.RangeWeightFrom ?? r?.rangeWeightFrom),
    rangeTo: n(r?.RangeWeightTo ?? r?.rangeWeightTo),
    basePrice: n(r?.BasePrice ?? r?.basePrice),
    pricePerKg: n(r?.PricePerKG ?? r?.pricePerKG ?? r?.pricePerKg),
    isPercentage: bool(r?.IsPercentage ?? r?.isPercentage),
    contractNo: s(r?.ContractNo ?? r?.contractNo),
    validFrom: dateOnly(r?.ValidFrom ?? r?.validFrom),
    validTo: dateOnly(r?.ValidTo ?? r?.validTo),
    description: s(r?.Description ?? r?.description),
    sourceRef: s(r?.SourceRef ?? r?.sourceRef),
    isActive: r?.IsActive ?? r?.isActive ?? true
  }
}

export interface CarrierRateInput {
  shipperId?: number
  serviceCode?: string
  serviceName?: string
  rateType?: string
  countryId?: number | null
  zone?: string
  rangeFrom?: number
  rangeTo?: number
  basePrice?: number
  pricePerKg?: number
  isPercentage?: boolean
  contractNo?: string
  validFrom?: string
  validTo?: string
  description?: string
  sourceRef?: string
  isActive?: boolean
}

/** Body for POST (full) or PUT (only the keys present in `b`). */
export const carrierRateBody = (b: CarrierRateInput, full: boolean): any => {
  const body: any = { tableName: CARRIER_RATE_TABLE }
  const put = (cond: boolean, key: string, value: any) => { if (full || cond) body[key] = value }
  if (full) body.AD_Org_ID = { id: 0, tableName: 'AD_Org' }
  if (full || b.shipperId !== undefined) body.M_Shipper_ID = { id: n(b.shipperId), tableName: 'M_Shipper' }
  if (b.countryId) body.C_Country_ID = { id: n(b.countryId), tableName: 'C_Country' }
  put(b.serviceCode !== undefined, 'serviceCode', s(b.serviceCode))
  put(b.serviceName !== undefined, 'serviceName', s(b.serviceName))
  put(b.rateType !== undefined, 'rateType', s(b.rateType) || 'base')
  put(b.zone !== undefined, 'zone', s(b.zone))
  put(b.rangeFrom !== undefined, 'rangeWeightFrom', n(b.rangeFrom))
  put(b.rangeTo !== undefined, 'rangeWeightTo', n(b.rangeTo))
  put(b.basePrice !== undefined, 'basePrice', n(b.basePrice))
  put(b.pricePerKg !== undefined, 'pricePerKG', n(b.pricePerKg))
  put(b.isPercentage !== undefined, 'isPercentage', b.isPercentage === true)
  put(b.contractNo !== undefined, 'contractNo', s(b.contractNo))
  put(b.description !== undefined, 'description', s(b.description).slice(0, 255))
  put(b.sourceRef !== undefined, 'sourceRef', s(b.sourceRef).slice(0, 60))
  put(b.isActive !== undefined, 'isActive', b.isActive !== false)
  // Dates: only send when set (REST rejects null for these on PUT).
  if (b.validFrom) body.validFrom = String(b.validFrom).slice(0, 10)
  if (b.validTo) body.validTo = String(b.validTo).slice(0, 10)
  return body
}

export interface ShipperGroup { shipperId: number; shipperName: string; cfgId: number | null; carrier: string; shipperIds: number[]; primaryShipperId: number }

/** M_Shipper → carrier group via M_ShipperCfg_ID (DHL cfg 1000000 = DHL/DHL Prime/…; DPD cfg 1000002). Cached 10 min. */
let shipperCache: { at: number; byShipper: Map<number, ShipperGroup> } | null = null
export const loadShipperGroups = async (event: any, token: string): Promise<Map<number, ShipperGroup>> => {
  if (shipperCache && Date.now() - shipperCache.at < 600000) return shipperCache.byShipper
  const res: any = await fetchHelper(event, `models/m_shipper?$top=200&$select=${encodeURIComponent('Name,M_ShipperCfg_ID,IsActive')}`, 'GET', token, null)
  const rows = (res?.records || []).map((r: any) => ({ id: n(r?.id), name: s(r?.Name), cfgId: r?.M_ShipperCfg_ID?.id ? n(r.M_ShipperCfg_ID.id) : null, cfgName: s(r?.M_ShipperCfg_ID?.identifier) }))
  const byShipper = new Map<number, ShipperGroup>()
  for (const r of rows) {
    const carrier = carrierCodeOf(r.cfgName, r.name)
    const members = r.cfgId ? rows.filter((x: any) => x.cfgId === r.cfgId) : [r]
    const ids = members.map((m: any) => m.id).sort((a: number, b: number) => a - b)
    byShipper.set(r.id, { shipperId: r.id, shipperName: r.name, cfgId: r.cfgId, carrier, shipperIds: ids, primaryShipperId: ids[0] })
  }
  shipperCache = { at: Date.now(), byShipper }
  return byShipper
}

export interface CarrierRateFilter {
  shipperId?: number
  /** expand shipperId to every shipper of the same M_ShipperCfg_ID (default true) */
  byCarrier?: boolean
  serviceCode?: string
  countryId?: number
  rateType?: string
  validOn?: string
  includeInactive?: boolean
  top?: number
}

const buildFilter = (f: CarrierRateFilter, shipperIds?: number[]): string => {
  const parts: string[] = []
  if (shipperIds && shipperIds.length > 1) parts.push('(' + shipperIds.map((id) => `M_Shipper_ID eq ${n(id)}`).join(' or ') + ')')
  else if (f.shipperId) parts.push(`M_Shipper_ID eq ${n(f.shipperId)}`)
  if (f.serviceCode) parts.push(`ServiceCode eq '${String(f.serviceCode).replace(/'/g, "''")}'`)
  if (f.countryId) parts.push(`C_Country_ID eq ${n(f.countryId)}`)
  if (f.rateType) parts.push(`RateType eq '${String(f.rateType).replace(/'/g, "''")}'`)
  parts.push(f.includeInactive ? '(IsActive eq true or IsActive eq false)' : 'IsActive eq true')
  return parts.join(' and ')
}

/** Load contract rows (raw iDempiere records), paged in 500s; `top` caps the total. */
export const loadCarrierRateRecords = async (event: any, token: string, f: CarrierRateFilter = {}): Promise<any[]> => {
  const pageSize = 500
  const cap = f.top && f.top > 0 ? f.top : 5000
  const out: any[] = []
  let skip = 0
  let shipperIds: number[] | undefined
  if (f.shipperId && f.byCarrier !== false) {
    try { shipperIds = (await loadShipperGroups(event, token)).get(n(f.shipperId))?.shipperIds } catch { /* fall back to the single shipper */ }
  }
  while (out.length < cap) {
    const take = Math.min(pageSize, cap - out.length)
    const url = `${CARRIER_RATE_MODEL}?$filter=${encodeURIComponent(buildFilter(f, shipperIds))}&$orderby=${encodeURIComponent('ServiceCode asc, C_Country_ID asc, RangeWeightFrom asc, cust_carrierrate_id asc')}&$top=${take}&$skip=${skip}`
    const res: any = await fetchHelper(event, url, 'GET', token, null)
    const recs: any[] = Array.isArray(res?.records) ? res.records : []
    out.push(...recs)
    if (recs.length < take) break
    skip += recs.length
  }
  return out
}

/** C_Country id → ISO2 and ISO2 → id (249 rows, cached 10 min per process). */
let countryCache: { at: number; byId: Map<number, string>; byCode: Map<string, number>; names: Map<number, string> } | null = null
export const loadCountryMaps = async (event: any, token: string) => {
  if (countryCache && Date.now() - countryCache.at < 600000) return countryCache
  const res: any = await fetchHelper(event, `models/c_country?$top=400&$select=${encodeURIComponent('Name,CountryCode')}`, 'GET', token, null)
  const byId = new Map<number, string>(), byCode = new Map<string, number>(), names = new Map<number, string>()
  for (const c of (res?.records || [])) {
    const id = n(c?.id), code = s(c?.CountryCode).toUpperCase()
    if (!id) continue
    names.set(id, s(c?.Name))
    if (code) { byId.set(id, code); if (!byCode.has(code)) byCode.set(code, id) }
  }
  countryCache = { at: Date.now(), byId, byCode, names }
  return countryCache
}

export const loadCarrierRates = async (event: any, token: string, f: CarrierRateFilter = {}): Promise<CarrierRate[]> => {
  const [recs, countries] = await Promise.all([loadCarrierRateRecords(event, token, f), loadCountryMaps(event, token)])
  let rows = recs.map((r) => mapCarrierRate(r, countries.byId))
  if (f.validOn) {
    const d = String(f.validOn).slice(0, 10)
    rows = rows.filter((r) => (!r.validFrom || r.validFrom <= d) && (!r.validTo || r.validTo >= d))
  }
  return rows
}
