import { string } from 'alga-js'
import refreshTokenHelper from "../../../utils/refreshTokenHelper"
import forceLogoutHelper from "../../../utils/forceLogoutHelper"
import errorHandlingHelper from "../../../utils/errorHandlingHelper"
import fetchHelper from "../../../utils/fetchHelper"
import {
  getEbayContext,
  getPayout,
  getTransactionsForPayout,
  decomposeTransaction,
  parseAmount
} from "../../../utils/ebayFinancesApi"

// READ-ONLY: matches a set of eBay order ids to internal c_order records.
// Only GETs orders to show the reference/match indicator — writes nothing.
// Looks up by ebay_order_id first, then ExternalOrderId for any still unmatched.
const matchOrders = async (event: any, token: any, orderIds: string[]) => {
  const map: Record<string, { id: number; documentNo: string }> = {}
  const ids = orderIds.filter(Boolean)
  if (!ids.length) return { map, truncated: false }

  const CAP = 3000
  const truncated = ids.length > CAP
  const work = truncated ? ids.slice(0, CAP) : ids

  const lookup = async (field: string, pending: string[]) => {
    const CHUNK = 45
    for (let i = 0; i < pending.length; i += CHUNK) {
      const chunk = pending.slice(i, i + CHUNK)
      const filter = chunk.map(id => `${field} eq '${String(id).replace(/'/g, "''")}'`).join(' OR ')
      const res: any = await fetchHelper(
        event,
        `models/c_order?$filter=${string.urlEncode(filter)}&$select=${string.urlEncode('C_Order_ID,DocumentNo,ebay_order_id,ExternalOrderId')}&$top=200`,
        'GET',
        token,
        null
      )
      for (const rec of (res?.records || [])) {
        const entry = { id: rec.id, documentNo: rec.DocumentNo || '' }
        // Register under both ids so a hit on either field resolves the order.
        if (rec.ebay_order_id) map[String(rec.ebay_order_id)] = entry
        if (rec.ExternalOrderId) map[String(rec.ExternalOrderId)] = entry
      }
    }
  }

  // Pass 1: ebay_order_id
  await lookup('ebay_order_id', work)
  // Pass 2: ExternalOrderId for whatever is still unmatched
  const stillMissing = work.filter(id => !map[id])
  if (stillMissing.length) await lookup('ExternalOrderId', stillMissing)

  return { map, truncated }
}

type InvoiceRef = { invoiceId: number; documentNo: string; grandTotal: number; docStatus: string }

// For matched orders, find any linked c_invoice so we can show the invoice no/link.
// Prefers a Completed/Closed invoice over a draft when an order has several.
// Mirrors the orders-check endpoint's lookupInvoices.
const lookupInvoices = async (event: any, token: any, internalOrderIds: number[]) => {
  const map: Record<number, InvoiceRef> = {}
  const ids = [...new Set(internalOrderIds.filter(Boolean))]
  if (!ids.length) return map

  const CHUNK = 45
  for (let i = 0; i < ids.length; i += CHUNK) {
    const chunk = ids.slice(i, i + CHUNK)
    // Only live invoices count as "invoice exists" — exclude voided/reversed ones
    // (iDempiere keeps them IsActive=true). Whitelist: DR / CO / CL.
    const filter = chunk
      .map(id => `((C_Order_ID eq ${id}) and (DocStatus eq 'CO' or DocStatus eq 'CL' or DocStatus eq 'DR'))`)
      .join(' OR ')
    const res: any = await fetchHelper(
      event,
      `models/c_invoice?$filter=${string.urlEncode(filter)}&$select=${string.urlEncode('C_Invoice_ID,DocumentNo,GrandTotal,DocStatus,C_Order_ID')}&$top=400`,
      'GET',
      token,
      null
    )
    for (const rec of (res?.records || [])) {
      const oid = rec.C_Order_ID?.id ?? rec.C_Order_ID
      if (oid == null) continue
      const docStatus = rec.DocStatus?.id || rec.DocStatus?.identifier || rec.DocStatus || ''
      const ref: InvoiceRef = {
        invoiceId: rec.id ?? rec.C_Invoice_ID,
        documentNo: rec.DocumentNo || '',
        grandTotal: typeof rec.GrandTotal === 'number' ? rec.GrandTotal : Number(rec.GrandTotal || 0),
        docStatus
      }
      const existing = map[oid]
      const isFinal = (s: string) => {
        const u = String(s || '').toUpperCase()
        return u === 'CO' || u === 'CL' || u.includes('COMPLET') || u.includes('CLOSED')
      }
      if (!existing || (!isFinal(existing.docStatus) && isFinal(docStatus))) {
        map[oid] = ref
      }
    }
  }
  return map
}

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

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

  const orderSource: any = await fetchHelper(event, `models/c_ordersource/${orderSourceId}`, 'GET', token, null)
  if (!orderSource) return { status: 404, message: 'Order source not found' }

  const marketplaceIdentifier = String(orderSource?.Marketplace?.identifier || '').toLowerCase()
  const isEbay = marketplaceIdentifier === 'ebay' || String(orderSource?.Marketplace?.id) === '5' || String(orderSource?.marketplace) === '5'
  if (!isEbay) return { status: 400, message: `Order source "${orderSource?.Name || orderSourceId}" is not an eBay marketplace` }

  if (!orderSource?.marketplace_token) {
    return { status: 400, message: 'eBay account is not connected for this order source (no stored token).' }
  }

  const ctx = await getEbayContext(orderSource)

  // Payout header (authoritative payout amount + date) and its transactions.
  const payout = await getPayout(ctx, payoutId)
  const transactions = await getTransactionsForPayout(ctx, payoutId)

  // Decompose every transaction into signed accounting rows.
  const detailRows = transactions.flatMap(decomposeTransaction)

  // Derive a period from transaction dates (eBay payouts carry no explicit range).
  const dates = transactions.map((t: any) => t?.transactionDate).filter(Boolean).sort()
  const settlement = {
    settlementId: payout?.payoutId || payoutId,
    periodStart: dates[0] || '',
    periodEnd: dates[dates.length - 1] || '',
    depositDate: payout?.payoutDate || '',
    totalAmount: parseAmount(payout?.amount),
    currency: payout?.amount?.currency || 'EUR',
    status: payout?.payoutStatus || '',
    transactionCount: payout?.transactionCount ?? transactions.length
  }

  // READ-ONLY match to internal orders (best-effort; never blocks the view).
  const distinctOrderIds = [...new Set(detailRows.map(r => r.orderId).filter(Boolean))]
  let matchTruncated = false
  let matchError: string | null = null
  let matchedCount = 0
  try {
    const { map, truncated } = await matchOrders(event, token, distinctOrderIds)
    matchTruncated = truncated
    for (const row of detailRows) {
      const hit = row.orderId ? map[row.orderId] : null
      ;(row as any).internalOrderId = hit?.id ?? null
      ;(row as any).internalDocumentNo = hit?.documentNo ?? ''
      ;(row as any).matched = !!hit
    }
    matchedCount = distinctOrderIds.filter(id => map[id]).length

    // Best-effort: resolve a linked invoice for every matched order so the grid
    // can show a Rechnung column (never blocks the view if it fails).
    try {
      const matchedInternalIds = [...new Set(
        detailRows.map((r: any) => r.internalOrderId).filter(Boolean)
      )] as number[]
      const invoiceMap = await lookupInvoices(event, token, matchedInternalIds)
      for (const row of detailRows) {
        const inv = (row as any).internalOrderId ? invoiceMap[(row as any).internalOrderId] : null
        ;(row as any).invoiceExists = !!inv
        ;(row as any).invoiceId = inv?.invoiceId || null
        ;(row as any).invoiceDocumentNo = inv?.documentNo || ''
        ;(row as any).invoiceTotal = inv?.grandTotal ?? null
        ;(row as any).invoiceDocStatus = inv?.docStatus || ''
      }
    } catch {
      for (const row of detailRows) {
        ;(row as any).invoiceExists = false
        ;(row as any).invoiceId = null
        ;(row as any).invoiceDocumentNo = ''
      }
    }
  } catch (e: any) {
    matchError = e?.message || 'Order matching failed'
    for (const row of detailRows) {
      ;(row as any).internalOrderId = null
      ;(row as any).internalDocumentNo = ''
      ;(row as any).matched = false
      ;(row as any).invoiceExists = false
      ;(row as any).invoiceId = null
      ;(row as any).invoiceDocumentNo = ''
    }
  }

  // Category sums + reconciliation (sum of ALL detail amounts should ≈ payout total).
  const categorySums: Record<string, number> = {
    umsaetze: 0, retouren: 0, ebayGebuehren: 0, versandkosten: 0, werbekosten: 0, sonstiges: 0
  }
  let detailSum = 0
  for (const r of detailRows) {
    detailSum += r.amount
    if (categorySums[r.category] !== undefined) categorySums[r.category] += r.amount
  }

  const summary = {
    ...categorySums,
    auszahlung: settlement.totalAmount,
    lineCount: detailRows.length,
    ordersWithId: distinctOrderIds.length,
    matchedOrders: matchedCount,
    unmatchedOrders: distinctOrderIds.length - matchedCount,
    matchTruncated,
    matchError,
    reconciliation: {
      detailSum: Math.round(detailSum * 100) / 100,
      payout: settlement.totalAmount,
      difference: Math.round((settlement.totalAmount - detailSum) * 100) / 100
    }
  }

  return { status: 200, settlement, summary, rows: detailRows }
}

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

  try {
    data = await handleFunc(event)
  } catch (err: any) {
    // Surface eBay API errors (status-carrying) verbatim instead of treating them
    // as an iDempiere auth failure.
    if (err?.status && err?.message) {
      return { status: err.status, message: err.message, ebay: err.ebay }
    }
    try {
      let authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error: any) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
      forceLogoutHelper(event, data)
    }
  }

  return data
})
