import { string } from 'alga-js'

/**
 * Shared constants + helpers for the bank reconciliation (Bankabgleich) routes.
 * Verified against prod iDempiere:
 *  - C_DocType 1000008 = "AR Receipt" (ARR), 1000009 = "AP Payment" (APP)
 *  - C_Currency 102 = EUR
 * The bank account is NOT a constant here: reconciliation derives it from the
 * statement being reconciled (Volksbank 1000000, PayPal 1000001, …).
 * NB QuickPaymentModal elsewhere still hardcodes 1000000 — out of scope.
 */
export const DOCTYPE_AR_RECEIPT = 1000008
export const DOCTYPE_AP_PAYMENT = 1000009
export const EUR_CURRENCY_ID = 102
export const AMOUNT_TOLERANCE = 0.005
// Max |difference| offered as "Differenz ausbuchen" (write-off / Skonto)
export const WRITEOFF_MAX = 1.00

/** OData string literal: single quotes doubled. */
export const odataLit = (value: any): string => {
  return "'" + String(value).replace(/'/g, "''") + "'"
}

/** Uppercase + strip separators so document numbers survive remittance formatting. */
export const normalizeForDocNoScan = (text: any): string => {
  return String(text || '').toUpperCase().replace(/[\s\-\/\.\_]/g, '')
}

/**
 * Open amount per invoice: |GrandTotal| − |Σ allocations of COMPLETED payments|.
 * Same algorithm as server/api/invoices/ffn-unpaid-count.get.ts — IsPaid lags
 * behind allocations, so the allocation sum is authoritative.
 * Returns { [invoiceId]: openAmt } for the given invoice records
 * (records need id + GrandTotal).
 */
export const computeOpenAmounts = async (
  event: any,
  token: any,
  invoices: any[]
): Promise<Record<number, number>> => {
  const openAmounts: Record<number, number> = {}
  if (!invoices.length) return openAmounts

  const allocationSums: Record<number, number> = {}
  const ids = invoices.map((inv: any) => inv.id)

  // `C_Invoice_ID in (...)` is supported (proven in ffn-unpaid-count.get.ts);
  // chunk to keep URLs bounded.
  for (let i = 0; i < ids.length; i += 45) {
    const chunk = ids.slice(i, i + 45)
    try {
      const res: any = await event.context.fetch(
        `models/c_allocationline?$filter=${string.urlEncode(`C_Invoice_ID in (${chunk.join(',')})`)}` +
        `&$select=C_Invoice_ID,Amount&$expand=C_Payment_ID($select=DocStatus)&$top=1000`,
        'GET', token, null
      )
      for (const alloc of (res?.records || [])) {
        const invoiceId = alloc.C_Invoice_ID?.id
        const paymentDocStatus = alloc.C_Payment_ID?.DocStatus?.id || alloc.C_Payment_ID?.DocStatus || ''
        if (invoiceId && paymentDocStatus === 'CO') {
          allocationSums[invoiceId] = (allocationSums[invoiceId] || 0) + (alloc.Amount || 0)
        }
      }
    } catch (err) {
      // allocation fetch failure -> treat chunk as unallocated (invoices stay "open",
      // which only ever over-offers candidates, never hides real matches)
    }
  }

  for (const invoice of invoices) {
    const grandTotal = Math.abs(invoice.GrandTotal || 0)
    const paid = Math.abs(allocationSums[invoice.id] || 0)
    openAmounts[invoice.id] = Math.round((grandTotal - paid) * 100) / 100
  }

  return openAmounts
}

/** Map a raw c_bankstatementline record to the API line shape used by all routes. */
export const presentStatementLine = (line: any) => {
  const paymentRef = line.C_Payment_ID
  const chargeRef = line.C_Charge_ID
  const partnerRef = line.C_BPartner_ID

  return {
    id: line.id,
    line: line.Line,
    date: line.StatementLineDate,
    valutaDate: line.ValutaDate || null,
    amount: line.StmtAmt ?? 0,
    trxAmt: line.TrxAmt ?? 0,
    chargeAmt: line.ChargeAmt ?? 0,
    counterpart: line.EftPayee || '',
    iban: line.EftPayeeAccount || '',
    memo: line.EftMemo || line.Description || '',
    reference: line.ReferenceNo || '',
    matched: paymentRef?.id ? 'payment' : (chargeRef?.id ? 'charge' : null),
    payment: paymentRef?.id ? {
      id: paymentRef.id,
      documentNo: paymentRef.DocumentNo || paymentRef.identifier || '',
      payAmt: paymentRef.PayAmt,
      docStatus: paymentRef.DocStatus?.id || paymentRef.DocStatus || '',
      isReceipt: paymentRef.IsReceipt ?? null,
      isReconciled: paymentRef.IsReconciled ?? null
    } : null,
    charge: chargeRef?.id ? {
      id: chargeRef.id,
      name: chargeRef.Name || chargeRef.identifier || ''
    } : null,
    partner: partnerRef?.id ? {
      id: partnerRef.id,
      name: partnerRef.Name || partnerRef.identifier || ''
    } : null
  }
}

export const LINE_SELECT =
  '$select=C_BankStatementLine_ID,Line,StatementLineDate,ValutaDate,StmtAmt,TrxAmt,ChargeAmt,InterestAmt,' +
  'Description,EftPayee,EftPayeeAccount,EftMemo,ReferenceNo,C_Payment_ID,C_Charge_ID,C_BPartner_ID' +
  '&$expand=C_Payment_ID($select=DocumentNo,PayAmt,DocStatus,IsReceipt,IsReconciled),' +
  'C_Charge_ID($select=Name),C_BPartner_ID($select=Name)'

/** LINE_SELECT + the statement header (for cross-statement "all lines" views). */
export const LINE_SELECT_WITH_STATEMENT =
  LINE_SELECT.replace('C_BPartner_ID&$expand=', 'C_BPartner_ID,C_BankStatement_ID&$expand=') +
  ',C_BankStatement_ID($select=DocumentNo,Name,StatementDate,DocStatus)'

/**
 * Fetch c_bankstatementline records of MANY statements in one go:
 * `C_BankStatement_ID in (...)`, chunked (40 ids per request) and paged
 * (`$top`/`$skip`) so neither URL length nor iDempiere's page size caps the
 * result. `selectAndExpand` is a ready `$select=...&$expand=...` string.
 * Hard-capped at `maxRows` lines as a runaway guard (oldest statements first).
 */
export const fetchLinesForStatements = async (
  event: any,
  token: any,
  statementIds: number[],
  selectAndExpand: string,
  maxRows: number = 5000
): Promise<any[]> => {
  const ids = Array.from(new Set(statementIds.map((v) => Number(v)).filter((v) => v > 0)))
  const out: any[] = []
  if (!ids.length) return out

  const PAGE = 500
  for (let i = 0; i < ids.length && out.length < maxRows; i += 40) {
    const chunk = ids.slice(i, i + 40)
    const filter = chunk.length === 1
      ? `C_BankStatement_ID eq ${chunk[0]}`
      : `C_BankStatement_ID in (${chunk.join(',')})`
    let skip = 0
    while (out.length < maxRows) {
      const res: any = await event.context.fetch(
        `models/c_bankstatementline?$filter=${string.urlEncode(filter)}` +
        `&${selectAndExpand}&$orderby=${string.urlEncode('C_BankStatement_ID,Line')}&$top=${PAGE}&$skip=${skip}`,
        'GET', token, null
      )
      const records = res?.records || []
      out.push(...records)
      if (records.length < PAGE) break
      skip += PAGE
    }
  }
  return out.slice(0, maxRows)
}

/** Parse `?ids=1,2,3` (or an array) into a bounded list of positive ints. */
export const parseIdList = (raw: any, max: number = 100): number[] => {
  const parts = Array.isArray(raw) ? raw : String(raw ?? '').split(',')
  const ids = parts.map((v: any) => Number(String(v).trim())).filter((v: number) => Number.isInteger(v) && v > 0)
  return Array.from(new Set(ids)).slice(0, max)
}
