// Client-side aggregation for the cash-basis GuV / EÜR BI reports
// (pages /bi/profit-loss and /bi/income-surplus, component BI/BankCashReport.vue).
// Input = the raw lines of /api/bi/bank-cash-report; every grouping (month,
// account, counterpart, transfers between own accounts) happens here so the
// filters on the page never need another server round-trip.

export interface BankCashLine {
  id: number
  date: string            // yyyy-mm-dd
  amount: number          // StmtAmt: + income, − expense
  accountId: number | null
  statementNo: string
  statementStatus: string
  counterpart: string
  counterpartAccount: string
  memo: string
  reference: string
  matched: 'payment' | 'charge' | null
  paymentNo: string
  chargeName: string
  partnerName: string
  transfer?: boolean
}

export interface BankCashAccount {
  id: number
  name: string
  accountNo: string
  iban: string
  bankName: string
  isActive: boolean
}

export interface MonthBucket {
  month: number           // 1..12
  income: number
  expense: number         // positive magnitude
  net: number
  count: number
}

export interface CounterpartRow {
  name: string
  total: number           // positive magnitude
  count: number
  months: number[]        // 12 positive magnitudes
}

const round2 = (v: number) => Math.round(v * 100) / 100
const normalizeAccountNo = (v: any) => String(v || '').toUpperCase().replace(/[\s\-\.]/g, '')
const normalizeName = (v: any) => String(v || '').toLowerCase().replace(/[^a-z0-9äöüß]/g, '')

export const emptyMonths = (): MonthBucket[] =>
  Array.from({ length: 12 }, (_, i) => ({ month: i + 1, income: 0, expense: 0, net: 0, count: 0 }))

/**
 * Flag lines that only move money between the company's own bank accounts
 * (PayPal payout → Volksbank, …). Heuristic, no booking data needed. A line is a
 * transfer when its counterpart is ANOTHER own account that itself has statement
 * lines in this data set — then the same money shows up twice (out here, in
 * there). Matching: counterpart IBAN / account no., or the other account's
 * bank / account name inside the counterpart name ("PayPal (Europe) S.a.r.l.").
 * Lines whose counterpart is the account's OWN IBAN (bank fees, interest) are
 * never transfers, and nothing is flagged while only one account has lines.
 */
export const flagTransfers = (lines: BankCashLine[], accounts: BankCashAccount[]): BankCashLine[] => {
  const accountsWithLines = new Set(lines.map(l => Number(l.accountId) || 0))
  const ownNumbers = new Map<string, number>()
  const ownNames: { accountId: number; tokens: string[] }[] = []
  for (const a of accounts) {
    if (!accountsWithLines.has(Number(a.id))) continue
    for (const v of [a.iban, a.accountNo]) {
      const n = normalizeAccountNo(v)
      if (n.length >= 6) ownNumbers.set(n, Number(a.id))
    }
    const tokens = [a.bankName, a.name]
      .map(normalizeName)
      .filter(t => t.length >= 4 && !['bank', 'konto', 'account'].includes(t))
    ownNames.push({ accountId: Number(a.id), tokens })
  }
  if (accountsWithLines.size < 2) return lines.map(l => ({ ...l, transfer: false }))
  return lines.map(l => {
    const own = Number(l.accountId) || 0
    const cpNo = normalizeAccountNo(l.counterpartAccount)
    const byNumber = cpNo.length >= 6 ? ownNumbers.get(cpNo) : undefined
    let transfer = byNumber !== undefined && byNumber !== own
    if (!transfer && byNumber === undefined) {
      const cp = normalizeName(l.counterpart)
      if (cp) transfer = ownNames.some(o => o.accountId !== own && o.tokens.some(t => cp.includes(t)))
    }
    return { ...l, transfer }
  })
}

export interface AggregateOptions {
  accountId?: number | null      // null / undefined = all accounts
  includeTransfers?: boolean
}

export interface BankCashAggregate {
  months: MonthBucket[]
  totals: MonthBucket
  byAccount: Record<number, MonthBucket[]>
  payers: CounterpartRow[]        // income by counterpart, desc
  payees: CounterpartRow[]        // expense by counterpart, desc
  transfers: { count: number; amount: number }
  lines: BankCashLine[]           // the lines that were counted
  lastDate: string
}

export const aggregateBankCash = (lines: BankCashLine[], opts: AggregateOptions = {}): BankCashAggregate => {
  const months = emptyMonths()
  const byAccount: Record<number, MonthBucket[]> = {}
  const payerMap: Record<string, CounterpartRow> = {}
  const payeeMap: Record<string, CounterpartRow> = {}
  const transfers = { count: 0, amount: 0 }
  const counted: BankCashLine[] = []
  let lastDate = ''

  for (const l of lines) {
    if (opts.accountId && Number(l.accountId) !== Number(opts.accountId)) continue
    if (l.transfer) {
      transfers.count++
      transfers.amount = round2(transfers.amount + Math.abs(l.amount))
      if (!opts.includeTransfers) continue
    }
    const m = Number(String(l.date).slice(5, 7))
    if (!(m >= 1 && m <= 12)) continue
    const amt = Number(l.amount) || 0
    if (!amt) continue
    counted.push(l)
    if (l.date > lastDate) lastDate = l.date

    const add = (b: MonthBucket) => {
      if (amt > 0) b.income = round2(b.income + amt)
      else b.expense = round2(b.expense - amt)
      b.net = round2(b.income - b.expense)
      b.count++
    }
    add(months[m - 1])
    const accId = Number(l.accountId) || 0
    if (!byAccount[accId]) byAccount[accId] = emptyMonths()
    add(byAccount[accId][m - 1])

    const name = (l.counterpart || l.partnerName || '').trim() || '—'
    const map = amt > 0 ? payerMap : payeeMap
    if (!map[name]) map[name] = { name, total: 0, count: 0, months: Array(12).fill(0) }
    map[name].total = round2(map[name].total + Math.abs(amt))
    map[name].count++
    map[name].months[m - 1] = round2(map[name].months[m - 1] + Math.abs(amt))
  }

  const totals: MonthBucket = months.reduce((acc, b) => ({
    month: 0,
    income: round2(acc.income + b.income),
    expense: round2(acc.expense + b.expense),
    net: round2(acc.net + b.net),
    count: acc.count + b.count
  }), { month: 0, income: 0, expense: 0, net: 0, count: 0 })

  const sortRows = (map: Record<string, CounterpartRow>) =>
    Object.values(map).sort((a, b) => b.total - a.total || a.name.localeCompare(b.name))

  return { months, totals, byAccount, payers: sortRows(payerMap), payees: sortRows(payeeMap), transfers, lines: counted, lastDate }
}

/** Top-N rows + one "other" remainder row (null when nothing is folded). */
export const topWithRemainder = (rows: CounterpartRow[], n: number, otherLabel: string): { top: CounterpartRow[]; other: CounterpartRow | null } => {
  const top = rows.slice(0, n)
  const rest = rows.slice(n)
  if (!rest.length) return { top, other: null }
  const other: CounterpartRow = { name: otherLabel, total: 0, count: 0, months: Array(12).fill(0) }
  for (const r of rest) {
    other.total = round2(other.total + r.total)
    other.count += r.count
    r.months.forEach((v, i) => { other.months[i] = round2(other.months[i] + v) })
  }
  return { top, other }
}

export const formatEur = (v: number | null | undefined, opts: { blankZero?: boolean } = {}) => {
  const n = Number(v) || 0
  if (opts.blankZero && n === 0) return ''
  return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n)
}

export const useBankCashReport = () => {
  const cache = useState<Record<number, { accounts: BankCashAccount[]; lines: BankCashLine[]; truncated: boolean; generatedAt: string }>>('bank_cash_report_cache', () => ({}))

  const loadYear = async (year: number, force = false) => {
    if (!force && cache.value[year]) return cache.value[year]
    const res: any = await $fetch('/api/bi/bank-cash-report', {
      query: { year },
      headers: useRequestHeaders(['cookie'])
    })
    if (!res || res.lines === undefined) {
      throw new Error(res?.message || `Error ${res?.status || ''}`.trim())
    }
    const accounts: BankCashAccount[] = res.accounts || []
    const entry = {
      accounts,
      lines: flagTransfers(res.lines || [], accounts),
      truncated: !!res.truncated,
      generatedAt: res.generatedAt || ''
    }
    cache.value = { ...cache.value, [year]: entry }
    return entry
  }

  return { loadYear }
}
