import { string } from 'alga-js'
import refreshTokenHelper from '../../utils/refreshTokenHelper'
import errorHandlingHelper from '../../utils/errorHandlingHelper'

// Read-only earnings analysis over cust_fulfillmentfeeline for a custom date
// range. Mirrors the position grouping of fulfillment/generate-orders.post.ts
// (preview mode) but NEVER creates orders and NEVER marks lines as processed.
// Aggregated types: fulfillment, return, spacerentqm3, spacerentflat, parcel,
// subscription, shippingfee (bucketed by product). request stays per-entry on
// the merchant level (ticket description verbatim) and is aggregated by
// product in the grand total. Unknown types are aggregated too so nothing is
// silently dropped from the analysis.
const AGG_TYPE_ORDER = ['fulfillment', 'return', 'spacerentqm3', 'spacerentflat', 'parcel', 'subscription', 'shippingfee', 'request']

const typeSortIndex = (type: string) => {
  const idx = AGG_TYPE_ORDER.indexOf(type)
  return idx === -1 ? AGG_TYPE_ORDER.length : idx
}

const roundToTwo = (num: number): number => {
  return Math.round((num + Number.EPSILON) * 100) / 100
}

// m_product identifiers are "{Value}_{Name}" and Value often ends in a long
// numeric segment (e.g. "-1_1000199_Fulfillment Auftrag Gebühr"). Used as a
// fallback when the role's token can't read the expanded product Name.
const cleanIdentifierName = (identifier: string): string => {
  return identifier.replace(/^.+?_\d{6,}_/, '')
}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const query = getQuery(event)

  const dateFrom = String(query.dateFrom || '')
  const dateTo = String(query.dateTo || '')
  const statusFilter = String(query.status || 'all') // all | unprocessed | processed
  const partnerId = query.partnerId ? parseInt(String(query.partnerId)) : null
  // Types to exclude from ALL aggregations (comma-separated). byType still
  // reports every type in the range so the UI can offer re-enabling them.
  const excludeTypes = new Set(
    String(query.excludeTypes || '').split(',').map(s => s.trim()).filter(Boolean)
  )

  const datePattern = /^\d{4}-\d{2}-\d{2}$/
  if (!datePattern.test(dateFrom) || !datePattern.test(dateTo)) {
    return { status: 400, message: 'dateFrom and dateTo are required (YYYY-MM-DD)' }
  }

  const fromDate = new Date(dateFrom + 'T00:00:00Z')
  const toDate = new Date(dateTo + 'T00:00:00Z')
  if (isNaN(fromDate.getTime()) || isNaN(toDate.getTime()) || fromDate.getTime() > toDate.getTime()) {
    return { status: 400, message: 'Invalid date range' }
  }
  const rangeDays = Math.round((toDate.getTime() - fromDate.getTime()) / 86400000) + 1
  if (rangeDays > 366) {
    return { status: 400, message: 'Date range too large (max 366 days)' }
  }

  // Inclusive dateTo → exclusive upper bound (next day), same date-only literal
  // style the invoice generator uses for shipping_date filters.
  const endExclusive = new Date(toDate.getTime() + 86400000).toISOString().split('T')[0]

  let filter = `shipping_date ge '${dateFrom}' and shipping_date lt '${endExclusive}'`
  if (statusFilter === 'unprocessed') {
    filter += ' and A_Processed eq false'
  } else if (statusFilter === 'processed') {
    filter += ' and A_Processed eq true'
  }
  if (partnerId) {
    filter += ` and C_BPartner_ID eq ${partnerId}`
  }

  // Partner names come from the FK identifier; the product identifier carries
  // the raw search-key prefix (e.g. "-1_1000199_..."), so expand the clean
  // product Name only — keeps the payload small (a full month is ~6k lines).
  const feeLines: any = await event.context.fetch(
    `models/cust_fulfillmentfeeline?$filter=${string.urlEncode(filter)}&$expand=${string.urlEncode('M_Product_ID($select=Name,Value)')}`,
    'GET',
    token,
    null
  )

  const records: any[] = feeLines?.records || []

  const merchants: any = {}
  const totalPositions: any = {}
  const byType: any = {}
  const byDay: any = {}
  const summary: any = {
    totalAmount: 0,
    totalLines: 0,
    merchantCount: 0,
    processedLines: 0,
    unprocessedLines: 0,
    processedAmount: 0,
    unprocessedAmount: 0,
    skippedLines: 0,
    excludedLines: 0,
    excludedAmount: 0
  }

  for (const line of records) {
    const bpartnerId = line.C_BPartner_ID?.id
    const bpartnerName = line.C_BPartner_ID?.identifier || (bpartnerId ? `Partner ${bpartnerId}` : 'Unknown')
    const productId = line.M_Product_ID?.id
    const productName = line.M_Product_ID?.Name
      || (line.M_Product_ID?.identifier ? cleanIdentifierName(line.M_Product_ID.identifier) : '')
      || (productId ? `Product ${productId}` : 'Unknown')
    const type = line.FulfillTypeAccounting || 'unknown'
    const amount = parseFloat(line.LineTotalAmt) || 0
    const qtyInvoiced = parseFloat(line.QtyInvoiced) || 0
    const processed = line.A_Processed === true

    if (!bpartnerId || !productId) {
      summary.skippedLines++
      continue
    }

    // byType always covers every type in the range — the UI needs the full
    // list (with amounts) to render the deselectable type chips.
    if (!byType[type]) {
      byType[type] = { type, amount: 0, lines: 0 }
    }
    byType[type].amount += amount
    byType[type].lines++

    if (excludeTypes.has(type)) {
      summary.excludedLines++
      summary.excludedAmount += amount
      continue
    }

    summary.totalLines++
    summary.totalAmount += amount
    if (processed) {
      summary.processedLines++
      summary.processedAmount += amount
    } else {
      summary.unprocessedLines++
      summary.unprocessedAmount += amount
    }

    const dayKey = String(line.shipping_date || '').slice(0, 10)
    if (dayKey) {
      if (!byDay[dayKey]) {
        byDay[dayKey] = { date: dayKey, total: 0, types: {} }
      }
      byDay[dayKey].total += amount
      byDay[dayKey].types[type] = (byDay[dayKey].types[type] || 0) + amount
    }

    if (!merchants[bpartnerId]) {
      merchants[bpartnerId] = {
        bpartnerId,
        bpartnerName,
        totalAmount: 0,
        linesCount: 0,
        processedLines: 0,
        unprocessedLines: 0,
        processedAmount: 0,
        unprocessedAmount: 0,
        positions: {},
        requestLines: []
      }
    }
    const merchant = merchants[bpartnerId]
    merchant.totalAmount += amount
    merchant.linesCount++
    if (processed) {
      merchant.processedLines++
      merchant.processedAmount += amount
    } else {
      merchant.unprocessedLines++
      merchant.unprocessedAmount += amount
    }

    // Per-merchant positions — same shapes as the generator preview
    if (type === 'request') {
      // Per-entry, ticket Description verbatim (no aggregation)
      merchant.requestLines.push({
        id: line.id,
        productId,
        productName,
        description: line.Description || '',
        qty: parseFloat(line.QtyEntered) || qtyInvoiced || 1,
        price: parseFloat(line.PriceEntered) || 0,
        amount,
        shippingDate: line.shipping_date,
        processed
      })
    } else {
      const posKey = `${type}|${productId}`
      if (!merchant.positions[posKey]) {
        merchant.positions[posKey] = {
          type,
          productId,
          productName,
          totalAmt: 0,
          qtyInvoiced: 0,
          sourceLines: 0,
          inoutIds: new Set()
        }
      }
      const pos = merchant.positions[posKey]
      pos.totalAmt += amount
      pos.qtyInvoiced += qtyInvoiced
      pos.sourceLines++
      if (line.M_InOut_ID?.id) {
        pos.inoutIds.add(line.M_InOut_ID.id)
      }
    }

    // Grand total positions — everything aggregated by type + product
    const totalKey = `${type}|${productId}`
    if (!totalPositions[totalKey]) {
      totalPositions[totalKey] = {
        type,
        productId,
        productName,
        totalAmt: 0,
        qtyInvoiced: 0,
        qtyEntered: 0,
        sourceLines: 0,
        inoutIds: new Set(),
        merchantIds: new Set()
      }
    }
    const totalPos = totalPositions[totalKey]
    totalPos.totalAmt += amount
    totalPos.qtyInvoiced += qtyInvoiced
    totalPos.qtyEntered += parseFloat(line.QtyEntered) || 0
    totalPos.sourceLines++
    if (line.M_InOut_ID?.id) {
      totalPos.inoutIds.add(line.M_InOut_ID.id)
    }
    totalPos.merchantIds.add(bpartnerId)
  }

  // qty semantics per type mirror the generator preview: fulfillment/return
  // count positions (source lines); request uses QtyEntered; everything else
  // sums QtyInvoiced.
  const positionQty = (pos: any) => {
    if (pos.type === 'fulfillment' || pos.type === 'return') return pos.sourceLines
    if (pos.type === 'request') return roundToTwo(pos.qtyEntered ?? pos.qtyInvoiced)
    return roundToTwo(pos.qtyInvoiced)
  }

  const finalizePosition = (pos: any) => {
    const qty = positionQty(pos)
    return {
      type: pos.type,
      productId: pos.productId,
      productName: pos.productName,
      qty,
      unitPrice: roundToTwo(qty > 0 ? pos.totalAmt / qty : pos.totalAmt),
      amount: roundToTwo(pos.totalAmt),
      sourceLines: pos.sourceLines,
      shipmentCount: pos.inoutIds ? pos.inoutIds.size : 0,
      ...(pos.merchantIds ? { merchantCount: pos.merchantIds.size } : {})
    }
  }

  const sortPositions = (a: any, b: any) => {
    const t = typeSortIndex(a.type) - typeSortIndex(b.type)
    if (t !== 0) return t
    return b.amount - a.amount
  }

  const merchantList = Object.values(merchants).map((m: any) => {
    const positions = Object.values(m.positions).map(finalizePosition)
    for (const req of m.requestLines) {
      positions.push({
        type: 'request',
        productId: req.productId,
        productName: req.productName,
        description: req.description,
        qty: req.qty,
        unitPrice: roundToTwo(req.price),
        amount: roundToTwo(req.amount),
        sourceLines: 1,
        shipmentCount: 0,
        shippingDate: req.shippingDate,
        processed: req.processed
      } as any)
    }
    positions.sort(sortPositions)
    return {
      bpartnerId: m.bpartnerId,
      bpartnerName: m.bpartnerName,
      totalAmount: roundToTwo(m.totalAmount),
      linesCount: m.linesCount,
      processedLines: m.processedLines,
      unprocessedLines: m.unprocessedLines,
      processedAmount: roundToTwo(m.processedAmount),
      unprocessedAmount: roundToTwo(m.unprocessedAmount),
      positions
    }
  })
  merchantList.sort((a: any, b: any) => b.totalAmount - a.totalAmount)

  const totalList = Object.values(totalPositions).map(finalizePosition)
  totalList.sort(sortPositions)

  const byTypeList = Object.values(byType).map((tp: any) => ({
    type: tp.type,
    amount: roundToTwo(tp.amount),
    lines: tp.lines,
    excluded: excludeTypes.has(tp.type)
  }))
  byTypeList.sort((a: any, b: any) => typeSortIndex(a.type) - typeSortIndex(b.type))

  summary.merchantCount = merchantList.length
  summary.totalAmount = roundToTwo(summary.totalAmount)
  summary.processedAmount = roundToTwo(summary.processedAmount)
  summary.unprocessedAmount = roundToTwo(summary.unprocessedAmount)
  summary.excludedAmount = roundToTwo(summary.excludedAmount)

  // Per-day trend — every day of the range present (zero-filled) so charts
  // show gaps honestly instead of skipping empty days.
  const byDayList: any[] = []
  for (let ts = fromDate.getTime(); ts <= toDate.getTime(); ts += 86400000) {
    const dayKey = new Date(ts).toISOString().split('T')[0]
    const day = byDay[dayKey]
    const types: any = {}
    if (day) {
      for (const type in day.types) {
        types[type] = roundToTwo(day.types[type])
      }
    }
    byDayList.push({ date: dayKey, total: roundToTwo(day?.total || 0), types })
  }

  return {
    status: 200,
    dateFrom,
    dateTo,
    statusFilter,
    excludedTypes: Array.from(excludeTypes),
    summary,
    byType: byTypeList,
    byDay: byDayList,
    merchants: merchantList,
    totalPositions: totalList
  }
}

export default defineEventHandler(async (event) => {
  let data: any = {}

  try {
    data = await handleFunc(event)
  } catch (err: any) {
    try {
      let authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error: any) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
    }
  }

  return data
})
