/**
 * Finanzplan Compute Engine — Orchestrator
 *
 * Dependency order:
 *   Personalkosten (independent inputs)
 *   Investitionen (independent inputs)
 *   Kunden -> Umsatzerloese -> Materialaufwand
 *   Betriebliche Aufwendungen (needs Personal + Invest)
 *   Sonst. betr. Ertraege (independent)
 *   Liquiditaet (aggregates all above)
 *   GuV (annual summary)
 *
 * Each computation step is delegated to a companion module:
 *   engine-sheets.ts    — pure computation (Personal, Invest, aggregation)
 *   engine-betrieb.ts   — formula-based opex + category sums
 *   engine-liquiditaet.ts — rolling cash balance
 *   engine-guv.ts       — annual P&L + taxes
 */

import { Pool } from 'pg'
import {
  MonthlyValues, MONTHS, FOUNDING_MONTH,
  emptyMonthly, FPBetrieblicheAufwendungen,
  FPLiquiditaet, FPComputeResult,
} from './types'
import { computePersonalkosten, computeInvestitionen, sumField } from './engine-sheets'
import { computeBetrieblicheAufwendungen, computeCloudHosting } from './engine-betrieb'
import { computeLiquiditaet, computeRollingBalance } from './engine-liquiditaet'
import { computeGuV } from './engine-guv'

// Re-export pure calculators for direct use by tests / scripts
export { computePersonalkosten, computeInvestitionen } from './engine-sheets'

// Import types used only inside this file
type FPUmsatzerloese = import('./types').FPUmsatzerloese
type FPMaterialaufwand = import('./types').FPMaterialaufwand
type FPPersonalkosten = import('./types').FPPersonalkosten
type FPInvestitionen = import('./types').FPInvestitionen

// --- Main Engine ---

export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise<FPComputeResult> {
  // 1. Load all editable data from DB
  const [
    personalRows, investRows, betriebRows, liquidRows,
    kundenSummary, umsatzRows, materialRows,
  ] = await Promise.all([
    pool.query('SELECT * FROM fp_personalkosten WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_investitionen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_betriebliche_aufwendungen WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_liquiditaet WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_kunden_summary WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_umsatzerloese WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
    pool.query('SELECT * FROM fp_materialaufwand WHERE scenario_id = $1 ORDER BY sort_order', [scenarioId]),
  ])

  // 2. Compute Personalkosten
  const personal = computePersonalkosten(personalRows.rows as FPPersonalkosten[])
  const totalBrutto = sumField(personal as any, 'values_brutto')
  const totalSozial = sumField(personal as any, 'values_sozial')
  const totalPersonal = sumField(personal as any, 'values_total')
  const headcount = emptyMonthly()
  for (let m = 1; m <= MONTHS; m++) {
    headcount[`m${m}`] = personal.filter(p => (p.values_total[`m${m}`] || 0) > 0).length
  }

  // Write computed values back to DB
  for (const p of personal) {
    await pool.query(
      'UPDATE fp_personalkosten SET values_brutto = $1, values_sozial = $2, values_total = $3 WHERE id = $4',
      [JSON.stringify(p.values_brutto), JSON.stringify(p.values_sozial), JSON.stringify(p.values_total), p.id]
    )
  }

  // 3. Compute Investitionen
  const invest = computeInvestitionen(investRows.rows as FPInvestitionen[])
  const totalInvest = sumField(invest as any, 'values_invest')
  const totalAfa = sumField(invest as any, 'values_afa')

  for (const i of invest) {
    await pool.query(
      'UPDATE fp_investitionen SET values_invest = $1, values_afa = $2 WHERE id = $3',
      [JSON.stringify(i.values_invest), JSON.stringify(i.values_afa), i.id]
    )
  }

  // 4. Umsatzerloese + Materialaufwand
  const { totalRevenue, totalMaterial } = await computeRevenueAndMaterial(
    pool, umsatzRows.rows as FPUmsatzerloese[], materialRows.rows as FPMaterialaufwand[],
  )

  // 5. Bestandskunden (for formula-based costs)
  const totalBestandskunden = await loadBestandskunden(pool, scenarioId)

  // 5b. Cloud-Hosting formula in Materialaufwand
  await computeCloudHosting(pool, materialRows.rows as FPMaterialaufwand[], totalBestandskunden)

  // 6. Betriebliche Aufwendungen
  const betrieb = betriebRows.rows as FPBetrieblicheAufwendungen[]
  const { totalSonstige } = await computeBetrieblicheAufwendungen(pool, betrieb, {
    totalBrutto, totalPersonal, totalAfa, totalRevenue, totalMaterial,
    headcount, totalBestandskunden,
  })

  // 7. Liquiditaet (first pass)
  const liquid = liquidRows.rows as FPLiquiditaet[]
  await computeLiquiditaet(pool, liquid, {
    totalRevenue, totalMaterial, totalPersonal, totalSonstige, totalInvest,
  })

  // 8. GuV — compute annual values + taxes (writes tax rows to Liquiditaet)
  const guv = await computeGuV(pool, scenarioId, liquid, {
    totalRevenue, totalMaterial, totalBrutto, totalSozial,
    totalPersonal, totalAfa, totalSonstige,
  })

  // 9. Second pass: tax rows were written after rolling balance above.
  // Recompute Summe AUSZAHLUNGEN -> UEBERSCHUSS chain -> rolling balance
  // so taxes are included even on the first engine run.
  await computeRollingBalance(pool, liquid)

  // 10. Build result
  const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten') || r.row_label.includes('SUMME Betriebliche'))
  const liquiditaetRow = liquid.find(r => r.row_type === 'kontostand' && r.row_label.includes('LIQUIDIT'))

  return {
    personalkosten: { total_brutto: totalBrutto, total_sozial: totalSozial, total: totalPersonal, positions: personal, headcount },
    investitionen: { total_invest: totalInvest, total_afa: totalAfa, items: invest },
    umsatzerloese: { total: totalRevenue },
    materialaufwand: { total: totalMaterial },
    betriebliche: { total_sonstige: totalSonstige, total_gesamt: gesamtBetrieb?.values || emptyMonthly() },
    liquiditaet: { rows: liquid, endstand: liquiditaetRow?.values || emptyMonthly() },
    guv,
  }
}

// --- Revenue & Material helpers (kept in orchestrator — tightly coupled to DB writes) ---

async function computeRevenueAndMaterial(
  pool: Pool,
  umsatzAllRows: FPUmsatzerloese[],
  materialAllRows: FPMaterialaufwand[],
): Promise<{ totalRevenue: MonthlyValues; totalMaterial: MonthlyValues }> {
  // Umsatzerloese (quantity x price)
  const prices = umsatzAllRows.filter(r => r.section === 'price')
  const quantities = umsatzAllRows.filter(r => r.section === 'quantity')
  const revenueRows = umsatzAllRows.filter(r => r.section === 'revenue')
  const totalRevenue = emptyMonthly()

  const extractTier = (label: string) => { const m = label.match(/\(([^)]+)\)/); return m ? m[1] : label }
  for (const rev of revenueRows) {
    if (rev.row_label === 'GESAMTUMSATZ') continue
    const tier = extractTier(rev.row_label)
    const qty = quantities.find(q => extractTier(q.row_label) === tier) || quantities.find(q => q.row_label === rev.row_label)
    const price = prices.find(p => extractTier(p.row_label) === tier) || prices.find(p => p.row_label === rev.row_label)
    if (qty && price) {
      for (let m = 1; m <= MONTHS; m++) {
        rev.values[`m${m}`] = Math.round((qty.values[`m${m}`] || 0) * (price.values[`m${m}`] || 0))
      }
      await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(rev.values), rev.id])
    }
    for (let m = 1; m <= MONTHS; m++) {
      totalRevenue[`m${m}`] += rev.values[`m${m}`] || 0
    }
  }
  const gesamtUmsatz = revenueRows.find(r => r.row_label === 'GESAMTUMSATZ')
  if (gesamtUmsatz) {
    await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(totalRevenue), gesamtUmsatz.id])
  }

  // Materialaufwand (quantity x unit_cost)
  const matCosts = materialAllRows.filter(r => r.section === 'cost')
  const matUnitCosts = materialAllRows.filter(r => r.section === 'unit_cost')
  const totalMaterial = emptyMonthly()

  for (const cost of matCosts) {
    if (cost.row_label === 'SUMME') continue
    const uc = matUnitCosts.find(u => u.row_label === cost.row_label)
    const qty = quantities.find(q => q.row_label === cost.row_label)
    if (uc && qty) {
      for (let m = 1; m <= MONTHS; m++) {
        cost.values[`m${m}`] = Math.round((qty.values[`m${m}`] || 0) * (uc.values[`m${m}`] || 0))
      }
      await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(cost.values), cost.id])
    }
    for (let m = 1; m <= MONTHS; m++) {
      totalMaterial[`m${m}`] += cost.values[`m${m}`] || 0
    }
  }
  const matSumme = matCosts.find(r => r.row_label === 'SUMME')
  if (matSumme) {
    await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), matSumme.id])
  }

  return { totalRevenue, totalMaterial }
}

async function loadBestandskunden(pool: Pool, scenarioId: string): Promise<MonthlyValues> {
  const kundenRows = await pool.query(
    "SELECT segment_name, row_label, values FROM fp_kunden WHERE scenario_id = $1 AND row_label LIKE 'Bestandskunden%' ORDER BY sort_order",
    [scenarioId]
  )
  const totalBestandskunden = emptyMonthly()
  for (const row of kundenRows.rows) {
    const rl = (row as { row_label?: string }).row_label || ''
    if (rl.includes('Bestandskunden') && !rl.includes('gesamt')) {
      for (let m = 1; m <= MONTHS; m++) {
        totalBestandskunden[`m${m}`] += row.values?.[`m${m}`] || 0
      }
    }
  }
  return totalBestandskunden
}
