import { downloadLexofficeCsv, type LexofficeCsvRow } from '~/utils/lexofficeExport'

// Shared export orchestration for the marketplace settlement pages (Amazon
// settlement periods / eBay payouts) → lexoffice bank-statement CSV.
//
// Three variants, all driven by the SAME list of ticked periods:
//   - combined  : positions + the closing payout ("Geldtransit") row per period
//                 in ONE file — PayJoe's "Sammelkonto" layout, every period nets
//                 to exactly 0,00 on the clearing account. Recommended default.
//   - payouts   : only the payout rows (one per period). Pure list data.
//   - positions : only the position rows (per order / fee / ad invoice).
// The page supplies the marketplace-specific pieces (how to fetch a period's
// detail rows and how to turn them into CSV rows); everything else — progress,
// warnings, foreign-currency skips, netting check, sorting, file naming — lives
// here once instead of twice.

export interface LexofficeRowsResult {
  rows: LexofficeCsvRow[]
  warning?: string
  // The rows are a deliberate subset of the period (grid filter applied), so the
  // period is not expected to net to 0,00 — skips the combined-export netting check.
  partial?: boolean
}

export interface LexofficeSettlementExportOptions<TItem> {
  // Loads the detail rows + summary of one period. Return { error } to skip it.
  fetchDetail: (item: TItem) => Promise<{ rows?: any[]; summary?: any; error?: string }>
  buildPositionRows: (item: TItem, detailRows: any[], summary: any) => LexofficeRowsResult
  buildPayoutRow: (item: TItem) => LexofficeRowsResult
  // Short human label for warnings ("Zeitraum 01.06.–15.06.2026").
  itemLabel: (item: TItem) => string
  // ISO date used for the file name range (payout date).
  itemDate: (item: TItem) => string | null | undefined
  filePrefix: string // e.g. 'lexoffice-amazon'
  onAlert: (alert: { status: number; message: string }) => void
  emptySelectionMessage: () => string
  // Optional: called AFTER the CSV file was handed to the browser, once per
  // period that contributed at least one row (positions and/or payout line).
  // The pages use it to mark the exported orders as "CSV-exportiert" (C_Order
  // marker) and to record the period in the export log (useSettlementExportLog).
  onExported?: (variant: LexofficeExportVariant, exported: LexofficeExportedPeriod<TItem>[], meta: { fileName: string; rowCount: number }) => void | Promise<void>
}

export type LexofficeExportVariant = 'combined' | 'payouts' | 'positions'

export interface LexofficeExportedPeriod<TItem> {
  item: TItem
  // Detail rows the position lines were built from (empty for payouts-only)
  rows: any[]
  positions: boolean // position lines of this period are in the file
  partial: boolean // …but only a filtered subset of the period
  payout: boolean // the payout (Geldtransit) line is in the file
  // Net of this period's lines in a combined, complete export (0,00 = the
  // clearing account closes); null otherwise
  net: number | null
}

const isoDay = (value: string | null | undefined) => {
  if (!value) return ''
  const d = new Date(value)
  return isNaN(d.getTime()) ? '' : d.toISOString().split('T')[0]
}

const rowTime = (row: LexofficeCsvRow) => {
  const d = row.bookingDate instanceof Date ? row.bookingDate : new Date(row.bookingDate || '')
  return isNaN(d.getTime()) ? Number.MAX_SAFE_INTEGER : d.getTime()
}

const round2 = (n: number) => Math.round((Number(n) || 0) * 100) / 100

export const useLexofficeSettlementExport = <TItem>(options: LexofficeSettlementExportOptions<TItem>) => {
  const exporting = ref(false)
  const progress = ref({ done: 0, total: 0 })

  const buildFileName = (variant: LexofficeExportVariant, items: TItem[]) => {
    const days = items.map(i => isoDay(options.itemDate(i))).filter(Boolean).sort()
    const range = days.length ? `${days[0]}_${days[days.length - 1]}` : new Date().toISOString().split('T')[0]
    const part = variant === 'combined' ? 'sammelkonto' : variant === 'payouts' ? 'auszahlungen' : 'positionen'
    return `${options.filePrefix}-${part}-${range}.csv`
  }

  const finish = (variant: LexofficeExportVariant, items: TItem[], allRows: LexofficeCsvRow[], warnings: string[], partialCount = 0): { fileName: string; rowCount: number } | null => {
    if (!allRows.length) {
      options.onAlert({ status: 400, message: 'Keine exportierbaren Zeilen gefunden.' + (warnings.length ? ' — ' + warnings.join(' | ') : '') })
      return null
    }
    // Stable chronological order (Buchungstag), payout row of a period last
    // among equal dates because it is pushed after the positions.
    const sorted = allRows
      .map((row, idx) => ({ row, idx, t: rowTime(row) }))
      .sort((a, b) => a.t - b.t || a.idx - b.idx)
      .map(x => x.row)
    const fileName = buildFileName(variant, items)
    const result = downloadLexofficeCsv(sorted, fileName)
    const base = variant === 'combined'
      ? (partialCount
          ? `Lexoffice-CSV erstellt: ${result.rowCount} Zeilen für ${items.length} Zeitraum/Zeiträume — gefilterter Export: ${partialCount} Zeitraum/Zeiträume nur mit den sichtbaren Positionen und OHNE Auszahlungszeile${partialCount < items.length ? `, ${items.length - partialCount} vollständig inkl. Auszahlungszeile (Saldo 0,00)` : ''}. In lexoffice auf das Verrechnungskonto importieren; fehlende Auszahlungszeilen bei Bedarf über „Nur Auszahlungszeilen“ exportieren.`
          : `Lexoffice-CSV erstellt: ${result.rowCount} Zeilen für ${items.length} Zeitraum/Zeiträume (Positionen + Auszahlung, je Zeitraum Saldo 0,00). In lexoffice auf das Verrechnungskonto importieren; die Auszahlungszeile als Geldtransit mit dem Bankeingang verknüpfen.`)
      : variant === 'payouts'
        ? `Auszahlungs-CSV erstellt: ${result.rowCount} Zeilen. Jede Zeile in lexoffice als Geldtransit kategorisieren und mit dem passenden Eingang auf dem Bankkonto verknüpfen.`
        : `Positions-CSV erstellt: ${result.rowCount} Zeilen.`
    options.onAlert({
      status: 200,
      message: warnings.length ? `${base} Warnungen: ${warnings.join(' | ')}` : base
    })
    return { fileName, rowCount: result.rowCount }
  }

  // `extraWarnings`: notes the caller already knows before the run starts (e.g.
  // periods skipped because they are not loaded / not ticked) — reported with
  // the result alert like any other warning.
  const run = async (variant: LexofficeExportVariant, items: TItem[], extraWarnings: string[] = []) => {
    if (exporting.value) return
    if (!items.length) {
      options.onAlert({ status: 400, message: [options.emptySelectionMessage(), ...extraWarnings].join(' — ') })
      return
    }
    const needsDetail = variant !== 'payouts'
    exporting.value = true
    progress.value = { done: 0, total: needsDetail ? items.length : 0 }
    const allRows: LexofficeCsvRow[] = []
    const warnings: string[] = [...extraWarnings]
    // Periods that contributed rows to the file (for onExported).
    const exportedDetails: LexofficeExportedPeriod<TItem>[] = []
    let partialCount = 0
    try {
      for (const item of items) {
        const label = options.itemLabel(item)
        const periodRows: LexofficeCsvRow[] = []
        let partial = false
        let detailRows: any[] = []
        let positionCount = 0
        let payoutCount = 0
        let net: number | null = null
        try {
          if (needsDetail) {
            const detail = await options.fetchDetail(item)
            if (detail?.error) {
              warnings.push(`${label}: ${detail.error}`)
              continue
            }
            detailRows = detail?.rows || []
            const pos = options.buildPositionRows(item, detailRows, detail?.summary)
            if (pos.warning) warnings.push(pos.warning)
            partial = !!pos.partial
            if (partial) partialCount++
            periodRows.push(...pos.rows)
            positionCount = pos.rows.length
          }
          if (variant !== 'positions') {
            const pay = options.buildPayoutRow(item)
            // The position builder usually raised the same warning already
            // (foreign currency); only add what is new (e.g. "payout row left out").
            if (pay.warning && !warnings.includes(pay.warning)) warnings.push(pay.warning)
            periodRows.push(...pay.rows)
            payoutCount = pay.rows.length
          }
          if (variant === 'combined' && periodRows.length && !partial) {
            // Every period must net to 0,00 on the clearing account. The position
            // builder already surfaces any reconciliation residue as its own row,
            // so a non-zero here means a genuine data problem worth flagging.
            net = round2(periodRows.reduce((s, r) => s + round2(r.amount), 0))
            if (Math.abs(net) >= 0.01) {
              warnings.push(`${label}: Saldo ${net.toFixed(2).replace('.', ',')} ≠ 0,00 — bitte prüfen`)
            }
          }
          allRows.push(...periodRows)
          if (periodRows.length) {
            exportedDetails.push({
              item,
              rows: positionCount ? detailRows : [],
              positions: positionCount > 0,
              partial: positionCount > 0 && partial,
              payout: payoutCount > 0,
              net: payoutCount > 0 && positionCount > 0 && !partial ? net : null
            })
          }
        } catch (error: any) {
          warnings.push(`${label}: ${error?.data?.message || error?.message || 'Fehler beim Laden'}`)
        } finally {
          if (needsDetail) progress.value.done++
        }
      }
    } finally {
      exporting.value = false
    }
    const downloaded = finish(variant, items, allRows, warnings, partialCount)
    if (downloaded && exportedDetails.length && options.onExported) {
      await options.onExported(variant, exportedDetails, downloaded)
    }
  }

  return {
    exporting,
    progress,
    exportCombined: (items: TItem[], extraWarnings?: string[]) => run('combined', items, extraWarnings),
    exportPayoutsOnly: (items: TItem[], extraWarnings?: string[]) => run('payouts', items, extraWarnings),
    exportPositionsOnly: (items: TItem[], extraWarnings?: string[]) => run('positions', items, extraWarnings)
  }
}
