// =============================================================================
// Buchhaltung (Accounting) Service
// =============================================================================

import pool from '../config/database';
import logger from '../utils/logger';
import fs from 'fs';
import { permissionService } from './permission.service';
import notificationService from './notification.service';

// ---------------------------------------------------------------------------
// Kategorien (Categories)
// ---------------------------------------------------------------------------

async function getKategorien(haushaltsjahr_id: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_kategorien WHERE haushaltsjahr_id = $1 ORDER BY sortierung, bezeichnung`,
      [haushaltsjahr_id]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getKategorien failed', { error });
    throw new Error('Kategorien konnten nicht geladen werden');
  }
}

async function createKategorie(data: { haushaltsjahr_id: number; bezeichnung: string; sortierung?: number }) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_kategorien (haushaltsjahr_id, bezeichnung, sortierung)
       VALUES ($1, $2, $3)
       RETURNING *`,
      [data.haushaltsjahr_id, data.bezeichnung, data.sortierung ?? 0]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createKategorie failed', { error });
    throw new Error('Kategorie konnte nicht erstellt werden');
  }
}

async function updateKategorie(id: number, data: { bezeichnung?: string; sortierung?: number }) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
    if (data.sortierung !== undefined)  { fields.push(`sortierung = $${idx++}`);  values.push(data.sortierung); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_kategorien SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updateKategorie failed', { error, id });
    throw new Error('Kategorie konnte nicht aktualisiert werden');
  }
}

async function deleteKategorie(id: number) {
  try {
    await pool.query(`UPDATE buchhaltung_konten SET kategorie_id = NULL WHERE kategorie_id = $1`, [id]);
    await pool.query(`DELETE FROM buchhaltung_kategorien WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deleteKategorie failed', { error, id });
    throw new Error('Kategorie konnte nicht gelöscht werden');
  }
}

// ---------------------------------------------------------------------------
// Haushaltsjahre (Fiscal Years)
// ---------------------------------------------------------------------------

async function getAllHaushaltsjahre() {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_haushaltsjahre ORDER BY jahr DESC`
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAllHaushaltsjahre failed', { error });
    throw new Error('Haushaltsjahre konnten nicht geladen werden');
  }
}

async function getHaushaltsjahrById(id: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_haushaltsjahre WHERE id = $1`,
      [id]
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.getHaushaltsjahrById failed', { error, id });
    throw new Error('Haushaltsjahr konnte nicht geladen werden');
  }
}

async function getCurrentHaushaltsjahr() {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_haushaltsjahre
       WHERE abgeschlossen = FALSE
       ORDER BY jahr DESC
       LIMIT 1`
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.getCurrentHaushaltsjahr failed', { error });
    throw new Error('Aktuelles Haushaltsjahr konnte nicht geladen werden');
  }
}

async function createHaushaltsjahr(
  data: { jahr: number; bezeichnung: string; beginn: string; ende: string },
  userId: string
) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_haushaltsjahre (jahr, bezeichnung, beginn, ende, erstellt_von)
       VALUES ($1, $2, $3, $4, $5)
       RETURNING *`,
      [data.jahr, data.bezeichnung, data.beginn, data.ende, userId]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createHaushaltsjahr failed', { error });
    throw new Error('Haushaltsjahr konnte nicht erstellt werden');
  }
}

async function updateHaushaltsjahr(
  id: number,
  data: { bezeichnung?: string; beginn?: string; ende?: string }
) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
    if (data.beginn !== undefined)      { fields.push(`beginn = $${idx++}`);       values.push(data.beginn); }
    if (data.ende !== undefined)        { fields.push(`ende = $${idx++}`);         values.push(data.ende); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_haushaltsjahre SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updateHaushaltsjahr failed', { error, id });
    throw new Error('Haushaltsjahr konnte nicht aktualisiert werden');
  }
}

async function closeHaushaltsjahr(id: number) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    // Check no open entwurf transactions
    const check = await client.query(
      `SELECT COUNT(*) FROM buchhaltung_transaktionen WHERE haushaltsjahr_id = $1 AND status = 'entwurf'`,
      [id]
    );
    if (parseInt(check.rows[0].count, 10) > 0) {
      throw new Error('Es gibt noch offene Entwürfe in diesem Haushaltsjahr');
    }
    const result = await client.query(
      `UPDATE buchhaltung_haushaltsjahre SET abgeschlossen = TRUE WHERE id = $1 RETURNING *`,
      [id]
    );
    await client.query('COMMIT');
    return result.rows[0] || null;
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.closeHaushaltsjahr failed', { error, id });
    throw error;
  } finally {
    client.release();
  }
}

// ---------------------------------------------------------------------------
// Konto-Typen (Account Types — static lookup)
// ---------------------------------------------------------------------------

async function getAllKontoTypen() {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_konto_typen ORDER BY sort_order`
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAllKontoTypen failed', { error });
    throw new Error('Kontotypen konnten nicht geladen werden');
  }
}

async function createKontoTyp(data: { bezeichnung: string; art: string; sort_order?: number }) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_konto_typen (bezeichnung, art, sort_order) VALUES ($1, $2, $3) RETURNING *`,
      [data.bezeichnung, data.art, data.sort_order ?? 0]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createKontoTyp failed', { error });
    throw new Error('Kontotyp konnte nicht erstellt werden');
  }
}

async function updateKontoTyp(id: number, data: { bezeichnung?: string; art?: string; sort_order?: number }) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
    if (data.art !== undefined)         { fields.push(`art = $${idx++}`);         values.push(data.art); }
    if (data.sort_order !== undefined)  { fields.push(`sort_order = $${idx++}`);  values.push(data.sort_order); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_konto_typen SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updateKontoTyp failed', { error, id });
    throw new Error('Kontotyp konnte nicht aktualisiert werden');
  }
}

async function deleteKontoTyp(id: number) {
  try {
    const check = await pool.query(
      `SELECT COUNT(*) FROM buchhaltung_bankkonten WHERE konto_typ_id = $1`,
      [id]
    );
    if (parseInt(check.rows[0].count) > 0) {
      const err = new Error('Kontotyp wird noch verwendet');
      (err as any).statusCode = 409;
      throw err;
    }
    await pool.query(`DELETE FROM buchhaltung_konto_typen WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deleteKontoTyp failed', { error, id });
    throw error;
  }
}

// ---------------------------------------------------------------------------
// Bankkonten (Bank Accounts)
// ---------------------------------------------------------------------------

async function getAllBankkonten() {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_bankkonten WHERE aktiv = TRUE ORDER BY ist_standard DESC, bezeichnung`
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAllBankkonten failed', { error });
    throw new Error('Bankkonten konnten nicht geladen werden');
  }
}

async function getBankkontoById(id: number) {
  try {
    const result = await pool.query(`SELECT * FROM buchhaltung_bankkonten WHERE id = $1`, [id]);
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.getBankkontoById failed', { error, id });
    throw new Error('Bankkonto konnte nicht geladen werden');
  }
}

async function createBankkonto(
  data: { bezeichnung: string; iban?: string; bic?: string; institut?: string; ist_standard?: boolean },
  userId: string
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    if (data.ist_standard) {
      await client.query(`UPDATE buchhaltung_bankkonten SET ist_standard = FALSE`);
    }
    const result = await client.query(
      `INSERT INTO buchhaltung_bankkonten (bezeichnung, iban, bic, institut, ist_standard, erstellt_von)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [data.bezeichnung, data.iban || null, data.bic || null, data.institut || null, data.ist_standard || false, userId]
    );
    await client.query('COMMIT');
    return result.rows[0];
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.createBankkonto failed', { error });
    throw new Error('Bankkonto konnte nicht erstellt werden');
  } finally {
    client.release();
  }
}

async function updateBankkonto(
  id: number,
  data: { bezeichnung?: string; iban?: string; bic?: string; institut?: string; ist_standard?: boolean }
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    if (data.ist_standard) {
      await client.query(`UPDATE buchhaltung_bankkonten SET ist_standard = FALSE WHERE id != $1`, [id]);
    }
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined)  { fields.push(`bezeichnung = $${idx++}`);   values.push(data.bezeichnung); }
    if (data.iban !== undefined)         { fields.push(`iban = $${idx++}`);           values.push(data.iban || null); }
    if (data.bic !== undefined)          { fields.push(`bic = $${idx++}`);            values.push(data.bic || null); }
    if (data.institut !== undefined)     { fields.push(`institut = $${idx++}`);       values.push(data.institut || null); }
    if (data.ist_standard !== undefined) { fields.push(`ist_standard = $${idx++}`);   values.push(data.ist_standard); }
    if (fields.length > 0) {
      values.push(id);
      await client.query(
        `UPDATE buchhaltung_bankkonten SET ${fields.join(', ')} WHERE id = $${idx}`,
        values
      );
    }
    const result = await client.query(`SELECT * FROM buchhaltung_bankkonten WHERE id = $1`, [id]);
    await client.query('COMMIT');
    return result.rows[0] || null;
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.updateBankkonto failed', { error, id });
    throw new Error('Bankkonto konnte nicht aktualisiert werden');
  } finally {
    client.release();
  }
}

async function deactivateBankkonto(id: number) {
  try {
    const result = await pool.query(
      `UPDATE buchhaltung_bankkonten SET aktiv = FALSE WHERE id = $1 RETURNING *`,
      [id]
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.deactivateBankkonto failed', { error, id });
    throw new Error('Bankkonto konnte nicht deaktiviert werden');
  }
}

async function getBankkontoStatement(
  bankkontoId: number,
  filters?: { von?: string; bis?: string }
) {
  try {
    // 1. Get the bank account
    const kontoResult = await pool.query(
      `SELECT * FROM buchhaltung_bankkonten WHERE id = $1`,
      [bankkontoId]
    );
    if (!kontoResult.rows[0]) return null;

    // 2. Fetch all transactions involving this bank account (both directions for transfers)
    const conditions: string[] = [
      `(t.bankkonto_id = $1 OR t.transfer_ziel_bankkonto_id = $1)`
    ];
    const values: unknown[] = [bankkontoId];
    let idx = 2;

    if (filters?.von) { conditions.push(`t.datum >= $${idx++}`); values.push(filters.von); }
    if (filters?.bis) { conditions.push(`t.datum <= $${idx++}`); values.push(filters.bis); }

    // Only include booked/approved transactions for the statement
    conditions.push(`t.status IN ('gebucht', 'freigegeben')`);

    const where = `WHERE ${conditions.join(' AND ')}`;
    const txResult = await pool.query(
      `SELECT t.*,
              hy.jahr as haushaltsjahr_jahr,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung,
              bk2.bezeichnung AS transfer_ziel_bezeichnung
       FROM buchhaltung_transaktionen t
       JOIN buchhaltung_haushaltsjahre hy ON t.haushaltsjahr_id = hy.id
       LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
       LEFT JOIN buchhaltung_bankkonten bk2 ON t.transfer_ziel_bankkonto_id = bk2.id
       ${where}
       ORDER BY t.datum ASC, t.id ASC`,
      values
    );

    // 3. Compute running balance
    let laufenderSaldo = 0;
    let gesamteinnahmen = 0;
    let gesamtausgaben = 0;

    const rows = txResult.rows.map(row => {
      const betrag = parseFloat(row.betrag);
      let aenderung = 0;

      if (row.typ === 'transfer') {
        if (row.bankkonto_id === bankkontoId) {
          // Money leaving this account
          aenderung = -betrag;
          gesamtausgaben += betrag;
        } else {
          // Money arriving at this account (transfer_ziel_bankkonto_id matches)
          aenderung = betrag;
          gesamteinnahmen += betrag;
        }
      } else if (row.typ === 'einnahme') {
        aenderung = betrag;
        gesamteinnahmen += betrag;
      } else if (row.typ === 'ausgabe') {
        aenderung = -betrag;
        gesamtausgaben += betrag;
      }

      laufenderSaldo += aenderung;

      return {
        ...row,
        aenderung,
        laufender_saldo: Math.round(laufenderSaldo * 100) / 100,
      };
    });

    return {
      konto: kontoResult.rows[0],
      rows,
      summary: {
        gesamteinnahmen: Math.round(gesamteinnahmen * 100) / 100,
        gesamtausgaben: Math.round(gesamtausgaben * 100) / 100,
        saldo: Math.round(laufenderSaldo * 100) / 100,
      },
    };
  } catch (error) {
    logger.error('BuchhaltungService.getBankkontoStatement failed', { error, bankkontoId });
    throw new Error('Kontoauszug konnte nicht geladen werden');
  }
}

// ---------------------------------------------------------------------------
// Konten (Budget Accounts)
// ---------------------------------------------------------------------------

async function getAllKonten(haushaltsjahrId: number) {
  try {
    const result = await pool.query(
      `SELECT k.*, kt.bezeichnung as konto_typ_bezeichnung, kt.art as konto_typ_art,
              k.parent_id, pk.bezeichnung AS parent_bezeichnung
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_konten pk ON pk.id = k.parent_id
       WHERE k.haushaltsjahr_id = $1 AND k.aktiv = TRUE
       ORDER BY k.kontonummer`,
      [haushaltsjahrId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAllKonten failed', { error, haushaltsjahrId });
    throw new Error('Konten konnten nicht geladen werden');
  }
}

async function getKontoById(id: number) {
  try {
    const result = await pool.query(
      `SELECT k.*, kt.bezeichnung as konto_typ_bezeichnung, kt.art as konto_typ_art,
              pk.bezeichnung AS parent_bezeichnung
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_konten pk ON pk.id = k.parent_id
       WHERE k.id = $1`,
      [id]
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.getKontoById failed', { error, id });
    throw new Error('Konto konnte nicht geladen werden');
  }
}

async function getKontenTree(haushaltsjahrId: number) {
  try {
    const result = await pool.query(
      `SELECT k.*,
              kt.bezeichnung as konto_typ_bezeichnung, kt.art as konto_typ_art,
              pk.bezeichnung AS parent_bezeichnung,
              k.kategorie_id, kat.bezeichnung AS kategorie_bezeichnung,
              COALESCE(SUM(CASE WHEN t.typ='ausgabe' AND t.ausgaben_typ='gwg' AND t.status='gebucht' THEN t.betrag ELSE 0 END), 0) AS spent_gwg,
              COALESCE(SUM(CASE WHEN t.typ='ausgabe' AND t.ausgaben_typ='anlagen' AND t.status='gebucht' THEN t.betrag ELSE 0 END), 0) AS spent_anlagen,
              COALESCE(SUM(CASE WHEN t.typ='ausgabe' AND t.ausgaben_typ='instandhaltung' AND t.status='gebucht' THEN t.betrag ELSE 0 END), 0) AS spent_instandhaltung,
              COALESCE(SUM(CASE WHEN t.typ='einnahme' AND t.status='gebucht' THEN t.betrag ELSE 0 END), 0) AS einnahmen_betrag
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_konten pk ON pk.id = k.parent_id
       LEFT JOIN buchhaltung_kategorien kat ON kat.id = k.kategorie_id
       LEFT JOIN buchhaltung_transaktionen t ON t.konto_id = k.id AND t.haushaltsjahr_id = $1
       WHERE k.haushaltsjahr_id = $1 AND k.aktiv = TRUE
       GROUP BY k.id, kt.bezeichnung, kt.art, pk.bezeichnung, kat.bezeichnung
       ORDER BY k.kontonummer`,
      [haushaltsjahrId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getKontenTree failed', { error, haushaltsjahrId });
    throw new Error('Kontenbaum konnte nicht geladen werden');
  }
}

async function getKontoDetail(kontoId: number) {
  try {
    const kontoResult = await pool.query(
      `SELECT k.*, kt.bezeichnung as konto_typ_bezeichnung, kt.art as konto_typ_art,
              pk.bezeichnung AS parent_bezeichnung
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_konten pk ON pk.id = k.parent_id
       WHERE k.id = $1`,
      [kontoId]
    );
    if (!kontoResult.rows[0]) return null;

    const childrenResult = await pool.query(
      `SELECT * FROM buchhaltung_konten WHERE parent_id = $1 AND aktiv = TRUE ORDER BY kontonummer`,
      [kontoId]
    );

    const transaktionenResult = await pool.query(
      `SELECT t.*,
              hy.jahr as haushaltsjahr_jahr,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung,
              bk2.bezeichnung AS transfer_ziel_bezeichnung
       FROM buchhaltung_transaktionen t
       JOIN buchhaltung_haushaltsjahre hy ON t.haushaltsjahr_id = hy.id
       LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
       LEFT JOIN buchhaltung_bankkonten bk2 ON t.transfer_ziel_bankkonto_id = bk2.id
       WHERE t.konto_id = $1
       ORDER BY t.datum DESC, t.id DESC`,
      [kontoId]
    );

    return {
      konto: kontoResult.rows[0],
      children: childrenResult.rows,
      transaktionen: transaktionenResult.rows,
    };
  } catch (error) {
    logger.error('BuchhaltungService.getKontoDetail failed', { error, kontoId });
    throw new Error('Kontodetails konnten nicht geladen werden');
  }
}

async function getPendingCount(haushaltsjahrId?: number): Promise<number> {
  try {
    let query = `SELECT COUNT(*) FROM buchhaltung_transaktionen WHERE status = 'entwurf'`;
    const params: unknown[] = [];
    if (haushaltsjahrId) {
      query += ` AND haushaltsjahr_id = $1`;
      params.push(haushaltsjahrId);
    }
    const result = await pool.query(query, params);
    return parseInt(result.rows[0].count, 10);
  } catch (error) {
    logger.error('BuchhaltungService.getPendingCount failed', { error });
    throw new Error('Anzahl offener Entwürfe konnte nicht geladen werden');
  }
}

async function validateSubPotBudget(
  parentId: number,
  budgetGwg: number,
  budgetAnlagen: number,
  budgetInstandhaltung: number,
  excludeKontoId?: number
) {
  const parentResult = await pool.query(
    `SELECT budget_gwg, budget_anlagen, budget_instandhaltung FROM buchhaltung_konten WHERE id = $1`,
    [parentId]
  );
  if (!parentResult.rows[0]) return; // parent not found, skip validation

  const parent = parentResult.rows[0];
  const excludeClause = excludeKontoId ? ` AND id != $2` : '';
  const siblingParams: unknown[] = [parentId];
  if (excludeKontoId) siblingParams.push(excludeKontoId);

  const siblingResult = await pool.query(
    `SELECT COALESCE(SUM(budget_gwg), 0) AS sum_gwg,
            COALESCE(SUM(budget_anlagen), 0) AS sum_anlagen,
            COALESCE(SUM(budget_instandhaltung), 0) AS sum_instandhaltung
     FROM buchhaltung_konten WHERE parent_id = $1${excludeClause}`,
    siblingParams
  );
  const sibling = siblingResult.rows[0];

  const checks: { label: string; sum: number; parentBudget: number }[] = [
    { label: 'GWG', sum: parseFloat(sibling.sum_gwg) + budgetGwg, parentBudget: parseFloat(parent.budget_gwg) },
    { label: 'Anlagen', sum: parseFloat(sibling.sum_anlagen) + budgetAnlagen, parentBudget: parseFloat(parent.budget_anlagen) },
    { label: 'Instandhaltung', sum: parseFloat(sibling.sum_instandhaltung) + budgetInstandhaltung, parentBudget: parseFloat(parent.budget_instandhaltung) },
  ];

  for (const c of checks) {
    if (c.parentBudget > 0 && c.sum > c.parentBudget) {
      throw Object.assign(
        new Error(`Budget ${c.label}: Summe der Untertöpfe (${c.sum.toFixed(2)} €) übersteigt das übergeordnete Budget (${c.parentBudget.toFixed(2)} €)`),
        { statusCode: 400 }
      );
    }
  }
}

async function createKonto(
  data: { haushaltsjahr_id: number; konto_typ_id?: number; kontonummer: string; bezeichnung: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; kategorie_id?: number | null; budget_typ?: string; budget_gesamt?: number },
  userId: string
) {
  try {
    if (data.parent_id && (data.budget_gwg || data.budget_anlagen || data.budget_instandhaltung)) {
      await validateSubPotBudget(data.parent_id, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0);
    }

    // Child konten inherit parent's budget_typ
    let budgetTyp = data.budget_typ || 'detailliert';
    if (data.parent_id) {
      const parentRow = await pool.query(`SELECT budget_typ FROM buchhaltung_konten WHERE id = $1`, [data.parent_id]);
      if (parentRow.rows[0]) {
        budgetTyp = parentRow.rows[0].budget_typ;
      }
    }

    const result = await pool.query(
      `INSERT INTO buchhaltung_konten (haushaltsjahr_id, konto_typ_id, kontonummer, bezeichnung, parent_id, budget_gwg, budget_anlagen, budget_instandhaltung, notizen, erstellt_von, kategorie_id, budget_typ, budget_gesamt)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
       RETURNING *`,
      [data.haushaltsjahr_id, data.konto_typ_id || null, data.kontonummer, data.bezeichnung, data.parent_id || null, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0, data.notizen || null, userId, data.kategorie_id ?? null, budgetTyp, data.budget_gesamt || 0]
    );
    return result.rows[0];
  } catch (error: any) {
    logger.error('BuchhaltungService.createKonto failed', { error });
    if (error.statusCode) throw error;
    if (error.code === '23505' && error.constraint === 'buchhaltung_konten_haushaltsjahr_id_kontonummer_key') {
      throw Object.assign(new Error(`Kontonummer "${data.kontonummer}" existiert bereits in diesem Haushaltsjahr`), { statusCode: 409 });
    }
    throw new Error('Konto konnte nicht erstellt werden');
  }
}

async function cascadeBudgetTypToChildren(parentId: number, newType: string): Promise<void> {
  const children = await pool.query(
    'SELECT id, budget_typ, budget_gwg, budget_anlagen, budget_instandhaltung, budget_gesamt FROM buchhaltung_konten WHERE parent_id = $1',
    [parentId]
  );

  for (const child of children.rows) {
    const oldType = child.budget_typ || 'detailliert';
    if (oldType === newType) continue;

    let gwg = 0, anlagen = 0, instandhaltung = 0, gesamt = 0;

    if (oldType === 'detailliert' && newType === 'einfach') {
      // Sum detail fields into gesamt
      gesamt = (parseFloat(child.budget_gwg) || 0) + (parseFloat(child.budget_anlagen) || 0) + (parseFloat(child.budget_instandhaltung) || 0);
    }
    // einfach → detailliert: zero everything (user redistributes manually)

    await pool.query(
      `UPDATE buchhaltung_konten
       SET budget_typ = $1, budget_gwg = $2, budget_anlagen = $3, budget_instandhaltung = $4, budget_gesamt = $5
       WHERE id = $6`,
      [newType, gwg, anlagen, instandhaltung, gesamt, child.id]
    );

    // Recurse into grandchildren
    await cascadeBudgetTypToChildren(child.id, newType);
  }
}

async function updateKonto(
  id: number,
  data: { konto_typ_id?: number; kontonummer?: string; bezeichnung?: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; kategorie_id?: number | null; budget_typ?: string; budget_gesamt?: number }
) {
  try {
    // Budget validation for sub-pots
    const hasBudgetChange = data.budget_gwg !== undefined || data.budget_anlagen !== undefined || data.budget_instandhaltung !== undefined;
    const hasParentChange = data.parent_id !== undefined;
    if (hasBudgetChange || hasParentChange) {
      const current = await pool.query(`SELECT parent_id, budget_gwg, budget_anlagen, budget_instandhaltung FROM buchhaltung_konten WHERE id = $1`, [id]);
      if (current.rows[0]) {
        const parentId = data.parent_id !== undefined ? data.parent_id : current.rows[0].parent_id;
        if (parentId) {
          const bGwg = data.budget_gwg !== undefined ? data.budget_gwg : parseFloat(current.rows[0].budget_gwg);
          const bAnl = data.budget_anlagen !== undefined ? data.budget_anlagen : parseFloat(current.rows[0].budget_anlagen);
          const bIns = data.budget_instandhaltung !== undefined ? data.budget_instandhaltung : parseFloat(current.rows[0].budget_instandhaltung);
          await validateSubPotBudget(parentId, bGwg, bAnl, bIns, id);
        }
      }
    }

    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.konto_typ_id !== undefined)          { fields.push(`konto_typ_id = $${idx++}`);          values.push(data.konto_typ_id || null); }
    if (data.kontonummer !== undefined)            { fields.push(`kontonummer = $${idx++}`);            values.push(data.kontonummer); }
    if (data.bezeichnung !== undefined)            { fields.push(`bezeichnung = $${idx++}`);            values.push(data.bezeichnung); }
    if (data.parent_id !== undefined)              { fields.push(`parent_id = $${idx++}`);              values.push(data.parent_id || null); }
    if (data.budget_gwg !== undefined)             { fields.push(`budget_gwg = $${idx++}`);             values.push(data.budget_gwg); }
    if (data.budget_anlagen !== undefined)         { fields.push(`budget_anlagen = $${idx++}`);         values.push(data.budget_anlagen); }
    if (data.budget_instandhaltung !== undefined)  { fields.push(`budget_instandhaltung = $${idx++}`);  values.push(data.budget_instandhaltung); }
    if (data.notizen !== undefined)                { fields.push(`notizen = $${idx++}`);                values.push(data.notizen || null); }
    if ('kategorie_id' in data)                     { fields.push(`kategorie_id = $${idx++}`);            values.push(data.kategorie_id ?? null); }
    if (data.budget_typ !== undefined)              { fields.push(`budget_typ = $${idx++}`);              values.push(data.budget_typ); }
    if (data.budget_gesamt !== undefined)           { fields.push(`budget_gesamt = $${idx++}`);           values.push(data.budget_gesamt); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');

    // Child konten must inherit parent's budget_typ
    if (data.budget_typ !== undefined) {
      const currentRow = await pool.query(`SELECT parent_id FROM buchhaltung_konten WHERE id = $1`, [id]);
      const parentId = data.parent_id !== undefined ? data.parent_id : currentRow.rows[0]?.parent_id;
      if (parentId) {
        const parentRow = await pool.query(`SELECT budget_typ FROM buchhaltung_konten WHERE id = $1`, [parentId]);
        if (parentRow.rows[0]) {
          // Override budget_typ with parent's value
          const btIdx = fields.findIndex(f => f.startsWith('budget_typ'));
          if (btIdx !== -1) {
            values[btIdx] = parentRow.rows[0].budget_typ;
          }
        }
      }
    }

    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_konten SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );

    // Cascade budget_typ change to all children recursively
    if (data.budget_typ !== undefined && result.rows[0]) {
      const newBudgetTyp = result.rows[0].budget_typ;
      // Fetch what the old type was before the update (the RETURNING row has the new value)
      // We know a change happened if the user sent budget_typ, so cascade unconditionally
      await cascadeBudgetTypToChildren(id, newBudgetTyp);
    }

    return result.rows[0] || null;
  } catch (error: any) {
    logger.error('BuchhaltungService.updateKonto failed', { error, id });
    if (error.statusCode) throw error;
    throw new Error('Konto konnte nicht aktualisiert werden');
  }
}

async function deleteKonto(id: number) {
  try {
    await pool.query(`DELETE FROM buchhaltung_konten WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deleteKonto failed', { error, id });
    throw new Error('Konto konnte nicht gelöscht werden');
  }
}

async function getBudgetUtilisation(id: number) {
  try {
    const result = await pool.query(
      `SELECT k.*,
              kt.bezeichnung as konto_typ_bezeichnung,
              kt.art as konto_typ_art,
              COALESCE(SUM(CASE WHEN t.typ = 'ausgabe' AND t.status IN ('gebucht','freigegeben') THEN t.betrag ELSE 0 END), 0) as gebucht_betrag,
              COALESCE(SUM(CASE WHEN t.typ = 'ausgabe' AND t.status = 'entwurf' THEN t.betrag ELSE 0 END), 0) as ausstehend_betrag
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_transaktionen t ON t.konto_id = k.id
       WHERE k.id = $1
       GROUP BY k.id, kt.bezeichnung, kt.art`,
      [id]
    );
    if (!result.rows[0]) return null;
    const row = result.rows[0];
    const gebucht = parseFloat(row.gebucht_betrag);
    const ausstehend = parseFloat(row.ausstehend_betrag);
    const budget = parseFloat(row.budget_gwg) + parseFloat(row.budget_anlagen) + parseFloat(row.budget_instandhaltung);
    return {
      ...row,
      gebucht_betrag: gebucht,
      ausstehend_betrag: ausstehend,
      verfuegbar_betrag: budget - gebucht - ausstehend,
      auslastung_prozent: budget > 0 ? Math.round((gebucht / budget) * 100) : 0,
    };
  } catch (error) {
    logger.error('BuchhaltungService.getBudgetUtilisation failed', { error, id });
    throw new Error('Budgetauslastung konnte nicht geladen werden');
  }
}

// ---------------------------------------------------------------------------
// Transaktionen (Transactions)
// ---------------------------------------------------------------------------

async function listTransaktionen(filters: {
  haushaltsjahr_id?: number;
  konto_id?: number;
  status?: string;
  typ?: string;
  datum_von?: string;
  datum_bis?: string;
  search?: string;
}) {
  try {
    const conditions: string[] = [];
    const values: unknown[] = [];
    let idx = 1;

    if (filters.haushaltsjahr_id) { conditions.push(`t.haushaltsjahr_id = $${idx++}`); values.push(filters.haushaltsjahr_id); }
    if (filters.konto_id)         { conditions.push(`t.konto_id = $${idx++}`);         values.push(filters.konto_id); }
    if (filters.status)           { conditions.push(`t.status = $${idx++}`);           values.push(filters.status); }
    if (filters.typ)              { conditions.push(`t.typ = $${idx++}`);              values.push(filters.typ); }
    if (filters.datum_von)        { conditions.push(`t.datum >= $${idx++}`);           values.push(filters.datum_von); }
    if (filters.datum_bis)        { conditions.push(`t.datum <= $${idx++}`);           values.push(filters.datum_bis); }
    if (filters.search) {
      conditions.push(`(t.beschreibung ILIKE $${idx} OR t.empfaenger_auftraggeber ILIKE $${idx} OR t.beleg_nr ILIKE $${idx})`);
      values.push(`%${filters.search}%`);
      idx++;
    }

    const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
    const result = await pool.query(
      `SELECT t.*,
              hy.jahr as haushaltsjahr_jahr,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung,
              bk2.bezeichnung AS transfer_ziel_bezeichnung
       FROM buchhaltung_transaktionen t
       JOIN buchhaltung_haushaltsjahre hy ON t.haushaltsjahr_id = hy.id
       LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
       LEFT JOIN buchhaltung_bankkonten bk2 ON t.transfer_ziel_bankkonto_id = bk2.id
       ${where}
       ORDER BY t.datum DESC, t.id DESC`,
      values
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.listTransaktionen failed', { error });
    throw new Error('Transaktionen konnten nicht geladen werden');
  }
}

async function getTransaktionById(id: number) {
  try {
    const txResult = await pool.query(
      `SELECT t.*,
              hy.jahr as haushaltsjahr_jahr,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung,
              bk2.bezeichnung AS transfer_ziel_bezeichnung
       FROM buchhaltung_transaktionen t
       JOIN buchhaltung_haushaltsjahre hy ON t.haushaltsjahr_id = hy.id
       LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
       LEFT JOIN buchhaltung_bankkonten bk2 ON t.transfer_ziel_bankkonto_id = bk2.id
       WHERE t.id = $1`,
      [id]
    );
    if (!txResult.rows[0]) return null;
    const tx = txResult.rows[0];
    const belegeResult = await pool.query(
      `SELECT * FROM buchhaltung_belege WHERE transaktion_id = $1 ORDER BY erstellt_am`,
      [id]
    );
    tx.belege = belegeResult.rows;
    return tx;
  } catch (error) {
    logger.error('BuchhaltungService.getTransaktionById failed', { error, id });
    throw new Error('Transaktion konnte nicht geladen werden');
  }
}

async function createTransaktion(
  data: {
    haushaltsjahr_id: number;
    konto_id?: number | null;
    bankkonto_id?: number | null;
    typ: 'einnahme' | 'ausgabe';
    betrag: number;
    datum: string;
    beschreibung?: string;
    empfaenger_auftraggeber?: string;
    verwendungszweck?: string;
    beleg_nr?: string;
    ausgaben_typ?: string | null;
  },
  userId: string
) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_transaktionen
         (haushaltsjahr_id, konto_id, bankkonto_id, typ, betrag, datum, beschreibung, empfaenger_auftraggeber, verwendungszweck, beleg_nr, ausgaben_typ, erstellt_von)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
       RETURNING *`,
      [
        data.haushaltsjahr_id,
        data.konto_id || null,
        data.bankkonto_id || null,
        data.typ,
        data.betrag,
        data.datum,
        data.beschreibung || null,
        data.empfaenger_auftraggeber || null,
        data.verwendungszweck || null,
        data.beleg_nr || null,
        data.typ === 'ausgabe' ? (data.ausgaben_typ || null) : null,
        userId,
      ]
    );
    await logAudit(result.rows[0].id, 'erstellt', { betrag: data.betrag }, userId);
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createTransaktion failed', { error });
    throw new Error('Transaktion konnte nicht erstellt werden');
  }
}

async function updateTransaktion(
  id: number,
  data: {
    konto_id?: number | null;
    bankkonto_id?: number | null;
    betrag?: number;
    datum?: string;
    beschreibung?: string;
    empfaenger_auftraggeber?: string;
    verwendungszweck?: string;
    beleg_nr?: string;
  },
  userId: string
) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.konto_id !== undefined)              { fields.push(`konto_id = $${idx++}`);              values.push(data.konto_id); }
    if (data.bankkonto_id !== undefined)          { fields.push(`bankkonto_id = $${idx++}`);          values.push(data.bankkonto_id); }
    if (data.betrag !== undefined)                { fields.push(`betrag = $${idx++}`);                values.push(data.betrag); }
    if (data.datum !== undefined)                 { fields.push(`datum = $${idx++}`);                 values.push(data.datum); }
    if (data.beschreibung !== undefined)          { fields.push(`beschreibung = $${idx++}`);          values.push(data.beschreibung || null); }
    if (data.empfaenger_auftraggeber !== undefined){ fields.push(`empfaenger_auftraggeber = $${idx++}`); values.push(data.empfaenger_auftraggeber || null); }
    if (data.verwendungszweck !== undefined)      { fields.push(`verwendungszweck = $${idx++}`);      values.push(data.verwendungszweck || null); }
    if (data.beleg_nr !== undefined)              { fields.push(`beleg_nr = $${idx++}`);              values.push(data.beleg_nr || null); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_transaktionen SET ${fields.join(', ')} WHERE id = $${idx} AND status = 'entwurf' RETURNING *`,
      values
    );
    if (result.rows[0]) await logAudit(id, 'aktualisiert', data, userId);
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updateTransaktion failed', { error, id });
    throw new Error('Transaktion konnte nicht aktualisiert werden');
  }
}

async function bookTransaktion(id: number, userId: string) {
  try {
    const result = await pool.query(
      `UPDATE buchhaltung_transaktionen
       SET status = 'gebucht', buchungsdatum = NOW(), gebucht_von = $2
       WHERE id = $1 AND status = 'entwurf'
       RETURNING *`,
      [id, userId]
    );
    if (result.rows[0]) await logAudit(id, 'gebucht', {}, userId);

    // Budget alert check (non-fatal)
    try {
      const tx = result.rows[0];
      if (tx && tx.konto_id) {
        const budget = await getBudgetUtilisation(tx.konto_id);
        if (budget && budget.auslastung_prozent > 0) {
          const settings = await getEinstellungen();
          const globalThreshold = parseInt(settings.default_alert_threshold as string) || 80;
          const threshold = budget.alert_threshold ?? globalThreshold;

          if (budget.auslastung_prozent >= threshold) {
            const users = await permissionService.getUsersWithPermission('buchhaltung:manage_accounts');
            for (const user of users) {
              await notificationService.createNotification({
                user_id: user.id,
                typ: 'buchhaltung_budget_warnung',
                titel: 'Budget-Warnung',
                nachricht: `${budget.bezeichnung}: ${budget.auslastung_prozent}% ausgelastet`,
                schwere: 'warnung',
                link: `/buchhaltung/konto/${tx.konto_id}`,
                quell_id: `budget-alert-${tx.konto_id}`,
                quell_typ: 'buchhaltung_budget_warnung',
              });
            }
          }
        }
      }
    } catch (alertErr) {
      logger.warn('BuchhaltungService.bookTransaktion budget alert failed (non-fatal)', { alertErr });
    }

    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.bookTransaktion failed', { error, id });
    throw new Error('Transaktion konnte nicht gebucht werden');
  }
}

async function stornoTransaktion(id: number, userId: string) {
  try {
    const result = await pool.query(
      `UPDATE buchhaltung_transaktionen
       SET status = 'entwurf'
       WHERE id = $1 AND status IN ('gebucht', 'freigegeben')
       RETURNING *`,
      [id, userId]
    );
    if (result.rows[0]) await logAudit(id, 'storniert_zu_entwurf', {}, userId);
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.stornoTransaktion failed', { error, id });
    throw new Error('Transaktion konnte nicht storniert werden');
  }
}

async function deleteTransaktion(id: number) {
  try {
    // Only entwurf can be deleted
    const result = await pool.query(
      `DELETE FROM buchhaltung_transaktionen WHERE id = $1 AND status = 'entwurf' RETURNING id`,
      [id]
    );
    return result.rows.length > 0;
  } catch (error) {
    logger.error('BuchhaltungService.deleteTransaktion failed', { error, id });
    throw new Error('Transaktion konnte nicht gelöscht werden');
  }
}

// ---------------------------------------------------------------------------
// Transfers (Umbuchungen)
// ---------------------------------------------------------------------------

async function createTransfer(
  data: {
    haushaltsjahr_id: number;
    bankkonto_id: number;
    transfer_ziel_bankkonto_id: number;
    betrag: number;
    datum: string;
    beschreibung?: string;
    beleg_nr?: string;
  },
  userId: string
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Debit row (money leaves source account)
    const debitResult = await client.query(
      `INSERT INTO buchhaltung_transaktionen
         (haushaltsjahr_id, bankkonto_id, transfer_ziel_bankkonto_id, typ, betrag, datum, beschreibung, beleg_nr, erstellt_von, status)
       VALUES ($1, $2, $3, 'transfer', $4, $5, $6, $7, $8, 'gebucht')
       RETURNING *`,
      [data.haushaltsjahr_id, data.bankkonto_id, data.transfer_ziel_bankkonto_id, data.betrag, data.datum, data.beschreibung || null, data.beleg_nr || null, userId]
    );

    // Credit row (money arrives at target account)
    const creditResult = await client.query(
      `INSERT INTO buchhaltung_transaktionen
         (haushaltsjahr_id, bankkonto_id, transfer_ziel_bankkonto_id, typ, betrag, datum, beschreibung, beleg_nr, erstellt_von, status)
       VALUES ($1, $2, $3, 'transfer', $4, $5, $6, $7, $8, 'gebucht')
       RETURNING *`,
      [data.haushaltsjahr_id, data.transfer_ziel_bankkonto_id, data.bankkonto_id, data.betrag, data.datum, data.beschreibung || null, data.beleg_nr || null, userId]
    );

    await client.query('COMMIT');

    await logAudit(debitResult.rows[0].id, 'transfer_erstellt', { betrag: data.betrag, von: data.bankkonto_id, nach: data.transfer_ziel_bankkonto_id }, userId);
    await logAudit(creditResult.rows[0].id, 'transfer_erstellt', { betrag: data.betrag, von: data.bankkonto_id, nach: data.transfer_ziel_bankkonto_id }, userId);

    return { debit: debitResult.rows[0], credit: creditResult.rows[0] };
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.createTransfer failed', { error });
    throw new Error('Umbuchung konnte nicht erstellt werden');
  } finally {
    client.release();
  }
}

// ---------------------------------------------------------------------------
// Belege (Receipts)
// ---------------------------------------------------------------------------

async function getBelegeByTransaktion(transaktionId: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_belege WHERE transaktion_id = $1 ORDER BY erstellt_am`,
      [transaktionId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getBelegeByTransaktion failed', { error });
    throw new Error('Belege konnten nicht geladen werden');
  }
}

async function uploadBeleg(
  transaktionId: number,
  file: { filename: string; originalname: string; mimetype: string; size: number },
  userId: string
) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_belege (transaktion_id, dateiname, original_name, dateityp, dateigroesse, erstellt_von)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [transaktionId, file.filename, file.originalname, file.mimetype, file.size, userId]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.uploadBeleg failed', { error });
    throw new Error('Beleg konnte nicht hochgeladen werden');
  }
}

async function deleteBeleg(id: number) {
  try {
    const result = await pool.query(
      `DELETE FROM buchhaltung_belege WHERE id = $1 RETURNING *`,
      [id]
    );
    if (result.rows[0]) {
      // Delete file from disk
      const filePath = process.env.NODE_ENV === 'production'
        ? `/app/uploads/buchhaltung/${result.rows[0].dateiname}`
        : `${process.cwd()}/uploads/buchhaltung/${result.rows[0].dateiname}`;
      try { fs.unlinkSync(filePath); } catch { /* ignore */ }
    }
    return result.rows.length > 0;
  } catch (error) {
    logger.error('BuchhaltungService.deleteBeleg failed', { error, id });
    throw new Error('Beleg konnte nicht gelöscht werden');
  }
}

// ---------------------------------------------------------------------------
// Freigaben (Approvals)
// ---------------------------------------------------------------------------

async function getFreigabenByTransaktion(transaktionId: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_freigaben WHERE transaktion_id = $1 ORDER BY erstellt_am DESC`,
      [transaktionId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getFreigabenByTransaktion failed', { error });
    throw new Error('Freigaben konnten nicht geladen werden');
  }
}

async function createFreigabe(transaktionId: number, userId: string) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_freigaben (transaktion_id) VALUES ($1) RETURNING *`,
      [transaktionId]
    );
    await logAudit(transaktionId, 'freigabe_angefragt', {}, userId);
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createFreigabe failed', { error });
    throw new Error('Freigabe konnte nicht erstellt werden');
  }
}

async function approveFreigabe(id: number, kommentar: string | undefined, userId: string) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await client.query(
      `UPDATE buchhaltung_freigaben SET status = 'genehmigt', kommentar = $2, freigegeben_von = $3, freigegeben_am = NOW() WHERE id = $1 RETURNING *`,
      [id, kommentar || null, userId]
    );
    if (result.rows[0]) {
      await client.query(
        `UPDATE buchhaltung_transaktionen SET status = 'freigegeben' WHERE id = $1`,
        [result.rows[0].transaktion_id]
      );
      await logAudit(result.rows[0].transaktion_id, 'freigegeben', { kommentar }, userId);
    }
    await client.query('COMMIT');
    return result.rows[0] || null;
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.approveFreigabe failed', { error, id });
    throw new Error('Freigabe konnte nicht genehmigt werden');
  } finally {
    client.release();
  }
}

async function rejectFreigabe(id: number, kommentar: string | undefined, userId: string) {
  try {
    const result = await pool.query(
      `UPDATE buchhaltung_freigaben SET status = 'abgelehnt', kommentar = $2, freigegeben_von = $3, freigegeben_am = NOW() WHERE id = $1 RETURNING *`,
      [id, kommentar || null, userId]
    );
    if (result.rows[0]) await logAudit(result.rows[0].transaktion_id, 'freigabe_abgelehnt', { kommentar }, userId);
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.rejectFreigabe failed', { error, id });
    throw new Error('Freigabe konnte nicht abgelehnt werden');
  }
}

// ---------------------------------------------------------------------------
// Einstellungen (Settings)
// ---------------------------------------------------------------------------

async function getEinstellungen() {
  try {
    const result = await pool.query(`SELECT * FROM buchhaltung_einstellungen`);
    const settings: Record<string, unknown> = {};
    for (const row of result.rows) settings[row.key] = row.value;
    return settings;
  } catch (error) {
    logger.error('BuchhaltungService.getEinstellungen failed', { error });
    throw new Error('Einstellungen konnten nicht geladen werden');
  }
}

async function setEinstellungen(data: Record<string, unknown>) {
  try {
    for (const [key, value] of Object.entries(data)) {
      await pool.query(
        `INSERT INTO buchhaltung_einstellungen (key, value) VALUES ($1, $2)
         ON CONFLICT (key) DO UPDATE SET value = $2, aktualisiert_am = NOW()`,
        [key, JSON.stringify(value)]
      );
    }
  } catch (error) {
    logger.error('BuchhaltungService.setEinstellungen failed', { error });
    throw new Error('Einstellungen konnten nicht gespeichert werden');
  }
}

// ---------------------------------------------------------------------------
// Audit
// ---------------------------------------------------------------------------

async function logAudit(transaktionId: number | null, aktion: string, details: unknown, userId: string) {
  try {
    await pool.query(
      `INSERT INTO buchhaltung_audit (transaktion_id, aktion, details, erstellt_von) VALUES ($1, $2, $3, $4)`,
      [transaktionId, aktion, JSON.stringify(details), userId]
    );
  } catch (err) {
    logger.warn('BuchhaltungService.logAudit failed (non-fatal)', { err });
  }
}

async function getAuditByTransaktion(transaktionId: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_audit WHERE transaktion_id = $1 ORDER BY erstellt_am DESC`,
      [transaktionId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAuditByTransaktion failed', { error });
    throw new Error('Audit-Einträge konnten nicht geladen werden');
  }
}

// ---------------------------------------------------------------------------
// Stats / Overview
// ---------------------------------------------------------------------------

async function getOverview(haushaltsjahrId: number) {
  try {
    const totalsResult = await pool.query(
      `SELECT
         COALESCE(SUM(CASE WHEN typ = 'einnahme' AND status IN ('gebucht','freigegeben') THEN betrag ELSE 0 END), 0) as total_einnahmen,
         COALESCE(SUM(CASE WHEN typ = 'ausgabe'  AND status IN ('gebucht','freigegeben') THEN betrag ELSE 0 END), 0) as total_ausgaben
       FROM buchhaltung_transaktionen
       WHERE haushaltsjahr_id = $1`,
      [haushaltsjahrId]
    );

    const kontenResult = await pool.query(
      `SELECT k.*,
              kt.bezeichnung as konto_typ_bezeichnung,
              kt.art as konto_typ_art,
              COALESCE(SUM(CASE WHEN t.typ = 'ausgabe' AND t.status IN ('gebucht','freigegeben') THEN t.betrag ELSE 0 END), 0) as gebucht_betrag,
              COALESCE(SUM(CASE WHEN t.typ = 'ausgabe' AND t.status = 'entwurf' THEN t.betrag ELSE 0 END), 0) as ausstehend_betrag
       FROM buchhaltung_konten k
       LEFT JOIN buchhaltung_konto_typen kt ON k.konto_typ_id = kt.id
       LEFT JOIN buchhaltung_transaktionen t ON t.konto_id = k.id AND t.haushaltsjahr_id = $1
       WHERE k.haushaltsjahr_id = $1 AND k.aktiv = TRUE
       GROUP BY k.id, kt.bezeichnung, kt.art
       ORDER BY k.kontonummer`,
      [haushaltsjahrId]
    );

    const totalEinnahmen = parseFloat(totalsResult.rows[0].total_einnahmen);
    const totalAusgaben  = parseFloat(totalsResult.rows[0].total_ausgaben);

    return {
      haushaltsjahr_id: haushaltsjahrId,
      total_einnahmen: totalEinnahmen,
      total_ausgaben: totalAusgaben,
      saldo: totalEinnahmen - totalAusgaben,
      konten_budget: kontenResult.rows.map(row => ({
        ...row,
        gebucht_betrag:    parseFloat(row.gebucht_betrag),
        ausstehend_betrag: parseFloat(row.ausstehend_betrag),
        verfuegbar_betrag: (parseFloat(row.budget_gwg) + parseFloat(row.budget_anlagen) + parseFloat(row.budget_instandhaltung)) - parseFloat(row.gebucht_betrag) - parseFloat(row.ausstehend_betrag),
        auslastung_prozent: (parseFloat(row.budget_gwg) + parseFloat(row.budget_anlagen) + parseFloat(row.budget_instandhaltung)) > 0
          ? Math.round((parseFloat(row.gebucht_betrag) / (parseFloat(row.budget_gwg) + parseFloat(row.budget_anlagen) + parseFloat(row.budget_instandhaltung))) * 100)
          : 0,
      })),
    };
  } catch (error) {
    logger.error('BuchhaltungService.getOverview failed', { error, haushaltsjahrId });
    throw new Error('Übersicht konnte nicht geladen werden');
  }
}

// ---------------------------------------------------------------------------
// Wiederkehrend (Recurring Bookings)
// ---------------------------------------------------------------------------

async function getAllWiederkehrend() {
  try {
    const result = await pool.query(
      `SELECT w.*,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung
       FROM buchhaltung_wiederkehrend w
       LEFT JOIN buchhaltung_konten k ON w.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON w.bankkonto_id = bk.id
       ORDER BY w.naechste_ausfuehrung, w.bezeichnung`
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getAllWiederkehrend failed', { error });
    throw new Error('Wiederkehrende Buchungen konnten nicht geladen werden');
  }
}

async function getWiederkehrendById(id: number) {
  try {
    const result = await pool.query(
      `SELECT * FROM buchhaltung_wiederkehrend WHERE id = $1`,
      [id]
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.getWiederkehrendById failed', { error, id });
    throw new Error('Wiederkehrende Buchung konnte nicht geladen werden');
  }
}

async function createWiederkehrend(
  data: {
    bezeichnung: string;
    konto_id?: number | null;
    bankkonto_id?: number | null;
    typ: 'einnahme' | 'ausgabe';
    betrag: number;
    beschreibung?: string;
    empfaenger_auftraggeber?: string;
    intervall: string;
    naechste_ausfuehrung: string;
    aktiv?: boolean;
    ausfuehrungstag?: string;
    ausfuehrungs_monat?: number;
  },
  userId: string
) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_wiederkehrend
         (bezeichnung, konto_id, bankkonto_id, typ, betrag, beschreibung, empfaenger_auftraggeber, intervall, naechste_ausfuehrung, aktiv, erstellt_von, ausfuehrungstag, ausfuehrungs_monat)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
       RETURNING *`,
      [
        data.bezeichnung,
        data.konto_id || null,
        data.bankkonto_id || null,
        data.typ,
        data.betrag,
        data.beschreibung || null,
        data.empfaenger_auftraggeber || null,
        data.intervall,
        data.naechste_ausfuehrung,
        data.aktiv !== false,
        userId,
        data.ausfuehrungstag || 'erster',
        data.ausfuehrungs_monat ?? null,
      ]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createWiederkehrend failed', { error });
    throw new Error('Wiederkehrende Buchung konnte nicht erstellt werden');
  }
}

async function updateWiederkehrend(
  id: number,
  data: {
    bezeichnung?: string;
    konto_id?: number | null;
    bankkonto_id?: number | null;
    typ?: string;
    betrag?: number;
    beschreibung?: string;
    empfaenger_auftraggeber?: string;
    intervall?: string;
    naechste_ausfuehrung?: string;
    aktiv?: boolean;
    ausfuehrungstag?: string;
    ausfuehrungs_monat?: number | null;
  }
) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined)             { fields.push(`bezeichnung = $${idx++}`);             values.push(data.bezeichnung); }
    if (data.konto_id !== undefined)                { fields.push(`konto_id = $${idx++}`);                values.push(data.konto_id); }
    if (data.bankkonto_id !== undefined)            { fields.push(`bankkonto_id = $${idx++}`);            values.push(data.bankkonto_id); }
    if (data.typ !== undefined)                     { fields.push(`typ = $${idx++}`);                     values.push(data.typ); }
    if (data.betrag !== undefined)                  { fields.push(`betrag = $${idx++}`);                  values.push(data.betrag); }
    if (data.beschreibung !== undefined)            { fields.push(`beschreibung = $${idx++}`);            values.push(data.beschreibung || null); }
    if (data.empfaenger_auftraggeber !== undefined) { fields.push(`empfaenger_auftraggeber = $${idx++}`); values.push(data.empfaenger_auftraggeber || null); }
    if (data.intervall !== undefined)               { fields.push(`intervall = $${idx++}`);               values.push(data.intervall); }
    if (data.naechste_ausfuehrung !== undefined)    { fields.push(`naechste_ausfuehrung = $${idx++}`);    values.push(data.naechste_ausfuehrung); }
    if (data.aktiv !== undefined)                   { fields.push(`aktiv = $${idx++}`);                   values.push(data.aktiv); }
    if (data.ausfuehrungstag !== undefined)         { fields.push(`ausfuehrungstag = $${idx++}`);         values.push(data.ausfuehrungstag); }
    if (data.ausfuehrungs_monat !== undefined)      { fields.push(`ausfuehrungs_monat = $${idx++}`);      values.push(data.ausfuehrungs_monat); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_wiederkehrend SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updateWiederkehrend failed', { error, id });
    throw new Error('Wiederkehrende Buchung konnte nicht aktualisiert werden');
  }
}

async function deleteWiederkehrend(id: number) {
  try {
    await pool.query(`DELETE FROM buchhaltung_wiederkehrend WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deleteWiederkehrend failed', { error, id });
    throw new Error('Wiederkehrende Buchung konnte nicht gelöscht werden');
  }
}

// ---------------------------------------------------------------------------
// CSV Export
// ---------------------------------------------------------------------------

async function exportTransaktionenCsv(haushaltsjahrId: number): Promise<string> {
  try {
    const result = await pool.query(
      `SELECT t.*,
              hy.jahr as haushaltsjahr_jahr,
              k.bezeichnung as konto_bezeichnung,
              k.kontonummer as konto_kontonummer,
              bk.bezeichnung as bankkonto_bezeichnung
       FROM buchhaltung_transaktionen t
       JOIN buchhaltung_haushaltsjahre hy ON t.haushaltsjahr_id = hy.id
       LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
       LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
       WHERE t.haushaltsjahr_id = $1
       ORDER BY t.datum DESC, t.id DESC`,
      [haushaltsjahrId]
    );

    const statusLabels: Record<string, string> = {
      entwurf: 'Entwurf',
      gebucht: 'Gebucht',
      freigegeben: 'Freigegeben',
      storniert: 'Storniert',
    };

    const header = [
      'Nr.', 'Datum', 'Typ', 'Beschreibung', 'Empfänger/Auftraggeber',
      'Verwendungszweck', 'Belegnummer', 'Konto', 'Bankkonto', 'Betrag', 'Status',
    ].join(';');

    const rows = result.rows.map(row => {
      const betrag = (row.typ === 'ausgabe' ? '-' : '') + parseFloat(row.betrag).toFixed(2).replace('.', ',');
      const konto = row.konto_kontonummer ? `${row.konto_kontonummer} ${row.konto_bezeichnung}` : '';
      const datum = row.datum ? new Date(row.datum).toLocaleDateString('de-DE') : '';
      const escCsv = (val: unknown) => {
        const s = String(val ?? '');
        return s.includes(';') || s.includes('"') || s.includes('\n') ? `"${s.replace(/"/g, '""')}"` : s;
      };
      return [
        row.laufende_nummer != null ? `${row.haushaltsjahr_jahr}/${row.laufende_nummer}` : `E${row.id}`,
        datum,
        row.typ === 'einnahme' ? 'Einnahme' : 'Ausgabe',
        escCsv(row.beschreibung),
        escCsv(row.empfaenger_auftraggeber),
        escCsv(row.verwendungszweck),
        escCsv(row.beleg_nr),
        escCsv(konto),
        escCsv(row.bankkonto_bezeichnung),
        betrag,
        statusLabels[row.status] || row.status,
      ].join(';');
    });

    return '\uFEFF' + header + '\r\n' + rows.join('\r\n');
  } catch (error) {
    logger.error('BuchhaltungService.exportTransaktionenCsv failed', { error });
    throw new Error('CSV-Export fehlgeschlagen');
  }
}

// ---------------------------------------------------------------------------
// Erstattungen (Reimbursements)
// ---------------------------------------------------------------------------

interface Transaktion {
  id: number;
  [key: string]: unknown;
}

async function createErstattung(data: {
  konto_id: number;
  bankkonto_id: number;
  betrag: number;
  datum: string;
  beschreibung?: string;
  empfaenger_auftraggeber?: string;
  verwendungszweck?: string;
  ausgabe_ids: number[];
  erstellt_von?: number;
}): Promise<Transaktion> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Look up haushaltsjahr_id from the konto
    const kontoResult = await client.query(
      `SELECT haushaltsjahr_id FROM buchhaltung_konten WHERE id = $1`,
      [data.konto_id]
    );
    const haushaltsjahrId = kontoResult.rows[0]?.haushaltsjahr_id;

    const txResult = await client.query(
      `INSERT INTO buchhaltung_transaktionen (typ, konto_id, bankkonto_id, betrag, datum, beschreibung, empfaenger_auftraggeber, verwendungszweck, erstellt_von, haushaltsjahr_id, status)
       VALUES ('einnahme', $1, $2, $3, $4, $5, $6, $7, $8, $9, 'entwurf')
       RETURNING *`,
      [data.konto_id, data.bankkonto_id, data.betrag, data.datum, data.beschreibung || null, data.empfaenger_auftraggeber || null, data.verwendungszweck || null, data.erstellt_von || null, haushaltsjahrId]
    );
    const tx = txResult.rows[0];

    for (const ausgabeId of data.ausgabe_ids) {
      await client.query(
        `INSERT INTO buchhaltung_erstattung_zuordnungen (erstattung_transaktion_id, ausgabe_transaktion_id) VALUES ($1, $2)`,
        [tx.id, ausgabeId]
      );
    }

    await client.query('COMMIT');
    return tx;
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.createErstattung failed', { error });
    throw new Error('Erstattung konnte nicht erstellt werden');
  } finally {
    client.release();
  }
}

async function getErstattungLinks(transaktionId: number): Promise<{
  erstattung?: any;
  ausgaben?: any[];
}> {
  try {
    // If this transaction is an Ausgabe, find its reimbursement
    const erstattungResult = await pool.query(
      `SELECT t.* FROM buchhaltung_transaktionen t
       JOIN buchhaltung_erstattung_zuordnungen ez ON t.id = ez.erstattung_transaktion_id
       WHERE ez.ausgabe_transaktion_id = $1`,
      [transaktionId]
    );

    // If this transaction is an Erstattung, find its linked Ausgaben
    const ausgabenResult = await pool.query(
      `SELECT t.* FROM buchhaltung_transaktionen t
       JOIN buchhaltung_erstattung_zuordnungen ez ON t.id = ez.ausgabe_transaktion_id
       WHERE ez.erstattung_transaktion_id = $1`,
      [transaktionId]
    );

    return {
      erstattung: erstattungResult.rows[0] || undefined,
      ausgaben: ausgabenResult.rows,
    };
  } catch (error) {
    logger.error('BuchhaltungService.getErstattungLinks failed', { error, transaktionId });
    throw new Error('Erstattungsverknüpfungen konnten nicht geladen werden');
  }
}

// ---------------------------------------------------------------------------
// Bestellung → Buchhaltung Integration
// ---------------------------------------------------------------------------

async function createPendingFromBestellung(bestellungId: number, userId: string) {
  try {
    // 1. Fetch the bestellung with total
    const bestResult = await pool.query(
      `SELECT b.*, l.name AS lieferant_name, COALESCE(SUM(bp.menge * bp.einzelpreis), 0) AS gesamtbetrag
       FROM bestellungen b
       LEFT JOIN lieferanten l ON l.id = b.lieferant_id
       LEFT JOIN bestellpositionen bp ON bp.bestellung_id = b.id
       WHERE b.id = $1
       GROUP BY b.id, l.name`,
      [bestellungId]
    );
    if (!bestResult.rows[0]) {
      logger.warn('createPendingFromBestellung: Bestellung not found', { bestellungId });
      return;
    }
    const bestellung = bestResult.rows[0];
    const gesamtbetrag = parseFloat(bestellung.gesamtbetrag);
    if (gesamtbetrag <= 0) {
      logger.info('createPendingFromBestellung: Gesamtbetrag is 0, skipping', { bestellungId });
      return;
    }

    // 2. Find active Haushaltsjahr
    const hjResult = await pool.query(
      `SELECT id FROM buchhaltung_haushaltsjahre WHERE abgeschlossen = FALSE ORDER BY jahr DESC LIMIT 1`
    );
    if (!hjResult.rows[0]) {
      logger.info('createPendingFromBestellung: No active Haushaltsjahr found, skipping', { bestellungId });
      return;
    }
    const haushaltsjahrId = hjResult.rows[0].id;

    // 3. Find default bank account
    const bkResult = await pool.query(
      `SELECT id FROM buchhaltung_bankkonten WHERE aktiv = TRUE ORDER BY ist_standard DESC LIMIT 1`
    );
    const bankkontoId = bkResult.rows[0]?.id || null;

    // 4. Insert pending transaction
    const label = bestellung.laufende_nummer ? `Bestellung #${bestellung.laufende_nummer}` : `Bestellung #${bestellung.id}`;
    await pool.query(
      `INSERT INTO buchhaltung_transaktionen
         (haushaltsjahr_id, bankkonto_id, typ, betrag, datum, beschreibung, empfaenger_auftraggeber, bestellung_id, erstellt_von, status)
       VALUES ($1, $2, 'ausgabe', $3, NOW(), $4, $5, $6, $7, 'entwurf')`,
      [haushaltsjahrId, bankkontoId, gesamtbetrag, label, bestellung.lieferant_name || null, bestellungId, userId]
    );

    logger.info('createPendingFromBestellung: Created pending transaction', { bestellungId, gesamtbetrag });
  } catch (error) {
    logger.error('createPendingFromBestellung failed (non-fatal)', { error, bestellungId });
    // Non-fatal: don't throw
  }
}

// ---------------------------------------------------------------------------
// Planung (Budget Plans)
// ---------------------------------------------------------------------------

async function listPlanungen() {
  try {
    const result = await pool.query(
      `SELECT p.*,
              hj.bezeichnung AS haushaltsjahr_bezeichnung,
              hj.jahr AS haushaltsjahr_jahr,
              COALESCE(pos.positionen_count, 0) AS positionen_count,
              COALESCE(pos.total_gwg, 0) AS total_gwg,
              COALESCE(pos.total_anlagen, 0) AS total_anlagen,
              COALESCE(pos.total_instandhaltung, 0) AS total_instandhaltung
       FROM buchhaltung_planung p
       LEFT JOIN buchhaltung_haushaltsjahre hj ON hj.id = p.haushaltsjahr_id
       LEFT JOIN LATERAL (
         SELECT COUNT(*) AS positionen_count,
                SUM(budget_gwg) AS total_gwg,
                SUM(budget_anlagen) AS total_anlagen,
                SUM(budget_instandhaltung) AS total_instandhaltung
         FROM buchhaltung_planpositionen
         WHERE planung_id = p.id
       ) pos ON true
       ORDER BY p.erstellt_am DESC`
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.listPlanungen failed', { error });
    throw new Error('Planungen konnten nicht geladen werden');
  }
}

async function getPlanungById(id: number) {
  try {
    const planungResult = await pool.query(
      `SELECT p.*,
              hj.bezeichnung AS haushaltsjahr_bezeichnung,
              hj.jahr AS haushaltsjahr_jahr
       FROM buchhaltung_planung p
       LEFT JOIN buchhaltung_haushaltsjahre hj ON hj.id = p.haushaltsjahr_id
       WHERE p.id = $1`,
      [id]
    );
    if (!planungResult.rows[0]) return null;

    const positionenResult = await pool.query(
      `SELECT pp.*,
              k.bezeichnung AS konto_bezeichnung,
              k.kontonummer AS konto_kontonummer
       FROM buchhaltung_planpositionen pp
       LEFT JOIN buchhaltung_konten k ON pp.konto_id = k.id
       WHERE pp.planung_id = $1
       ORDER BY pp.sort_order, pp.id`,
      [id]
    );

    return {
      ...planungResult.rows[0],
      positionen: positionenResult.rows,
    };
  } catch (error) {
    logger.error('BuchhaltungService.getPlanungById failed', { error, id });
    throw new Error('Planung konnte nicht geladen werden');
  }
}

async function createPlanung(data: { haushaltsjahr_id: number; bezeichnung: string }, userId: string) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_planung (haushaltsjahr_id, bezeichnung, erstellt_von)
       VALUES ($1, $2, $3)
       RETURNING *`,
      [data.haushaltsjahr_id, data.bezeichnung, userId]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createPlanung failed', { error });
    throw new Error('Planung konnte nicht erstellt werden');
  }
}

async function updatePlanung(id: number, data: { bezeichnung?: string; status?: string }) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
    if (data.status !== undefined)      { fields.push(`status = $${idx++}`);      values.push(data.status); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_planung SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updatePlanung failed', { error, id });
    throw new Error('Planung konnte nicht aktualisiert werden');
  }
}

async function deletePlanung(id: number) {
  try {
    await pool.query(`DELETE FROM buchhaltung_planung WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deletePlanung failed', { error, id });
    throw new Error('Planung konnte nicht gelöscht werden');
  }
}

async function createPlanposition(planungId: number, data: { konto_id?: number | null; bezeichnung: string; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; sort_order?: number }) {
  try {
    const result = await pool.query(
      `INSERT INTO buchhaltung_planpositionen (planung_id, konto_id, bezeichnung, budget_gwg, budget_anlagen, budget_instandhaltung, notizen, sort_order)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [planungId, data.konto_id || null, data.bezeichnung, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0, data.notizen || null, data.sort_order || 0]
    );
    return result.rows[0];
  } catch (error) {
    logger.error('BuchhaltungService.createPlanposition failed', { error, planungId });
    throw new Error('Planposition konnte nicht erstellt werden');
  }
}

async function updatePlanposition(id: number, data: { konto_id?: number | null; bezeichnung?: string; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; sort_order?: number }) {
  try {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;
    if (data.konto_id !== undefined)                { fields.push(`konto_id = $${idx++}`);                values.push(data.konto_id); }
    if (data.bezeichnung !== undefined)              { fields.push(`bezeichnung = $${idx++}`);              values.push(data.bezeichnung); }
    if (data.budget_gwg !== undefined)               { fields.push(`budget_gwg = $${idx++}`);               values.push(data.budget_gwg); }
    if (data.budget_anlagen !== undefined)            { fields.push(`budget_anlagen = $${idx++}`);            values.push(data.budget_anlagen); }
    if (data.budget_instandhaltung !== undefined)     { fields.push(`budget_instandhaltung = $${idx++}`);     values.push(data.budget_instandhaltung); }
    if (data.notizen !== undefined)                  { fields.push(`notizen = $${idx++}`);                  values.push(data.notizen || null); }
    if (data.sort_order !== undefined)               { fields.push(`sort_order = $${idx++}`);               values.push(data.sort_order); }
    if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
    values.push(id);
    const result = await pool.query(
      `UPDATE buchhaltung_planpositionen SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );
    return result.rows[0] || null;
  } catch (error) {
    logger.error('BuchhaltungService.updatePlanposition failed', { error, id });
    throw new Error('Planposition konnte nicht aktualisiert werden');
  }
}

async function deletePlanposition(id: number) {
  try {
    await pool.query(`DELETE FROM buchhaltung_planpositionen WHERE id = $1`, [id]);
  } catch (error) {
    logger.error('BuchhaltungService.deletePlanposition failed', { error, id });
    throw new Error('Planposition konnte nicht gelöscht werden');
  }
}

async function getPlanpositionenByPlanung(planungId: number) {
  try {
    const result = await pool.query(
      `SELECT pp.*,
              k.bezeichnung AS konto_bezeichnung,
              k.kontonummer AS konto_kontonummer
       FROM buchhaltung_planpositionen pp
       LEFT JOIN buchhaltung_konten k ON pp.konto_id = k.id
       WHERE pp.planung_id = $1
       ORDER BY pp.sort_order, pp.id`,
      [planungId]
    );
    return result.rows;
  } catch (error) {
    logger.error('BuchhaltungService.getPlanpositionenByPlanung failed', { error, planungId });
    throw new Error('Planpositionen konnten nicht geladen werden');
  }
}

async function createHaushaltsjahrFromPlan(planungId: number, userId: string) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    const planResult = await client.query(
      `SELECT p.*, hj.jahr, hj.beginn, hj.ende
       FROM buchhaltung_planung p
       LEFT JOIN buchhaltung_haushaltsjahre hj ON hj.id = p.haushaltsjahr_id
       WHERE p.id = $1`,
      [planungId]
    );
    if (!planResult.rows[0]) throw new Error('Planung nicht gefunden');
    const plan = planResult.rows[0];

    const nextYear = plan.jahr + 1;

    // Check if Haushaltsjahr for next year already exists
    const existingResult = await client.query(
      `SELECT id FROM buchhaltung_haushaltsjahre WHERE jahr = $1`,
      [nextYear]
    );
    if (existingResult.rows.length > 0) {
      throw Object.assign(new Error(`Haushaltsjahr ${nextYear} existiert bereits`), { statusCode: 409 });
    }

    // Create new Haushaltsjahr
    const hjResult = await client.query(
      `INSERT INTO buchhaltung_haushaltsjahre (jahr, bezeichnung, beginn, ende, erstellt_von)
       VALUES ($1, $2, $3, $4, $5)
       RETURNING *`,
      [nextYear, `Haushaltsjahr ${nextYear}`, `${nextYear}-01-01`, `${nextYear}-12-31`, userId]
    );
    const newHj = hjResult.rows[0];

    // Get all positions from the plan
    const posResult = await client.query(
      `SELECT pp.*, k.kontonummer, k.bezeichnung AS konto_bezeichnung, k.konto_typ_id, k.parent_id, k.kategorie_id, k.budget_typ
       FROM buchhaltung_planpositionen pp
       LEFT JOIN buchhaltung_konten k ON pp.konto_id = k.id
       WHERE pp.planung_id = $1
       ORDER BY pp.sort_order, pp.id`,
      [planungId]
    );

    // Create konten in the new Haushaltsjahr from plan positions
    const kontoIdMap = new Map<number, number>(); // old konto_id -> new konto_id
    for (const pos of posResult.rows) {
      if (!pos.konto_id) continue;

      // Map parent_id to new parent if it was already created
      const newParentId = pos.parent_id ? (kontoIdMap.get(pos.parent_id) || null) : null;

      const kontoResult = await client.query(
        `INSERT INTO buchhaltung_konten (haushaltsjahr_id, konto_typ_id, kontonummer, bezeichnung, parent_id, budget_gwg, budget_anlagen, budget_instandhaltung, erstellt_von, kategorie_id, budget_typ)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
         ON CONFLICT (haushaltsjahr_id, kontonummer) DO UPDATE SET
           budget_gwg = EXCLUDED.budget_gwg,
           budget_anlagen = EXCLUDED.budget_anlagen,
           budget_instandhaltung = EXCLUDED.budget_instandhaltung
         RETURNING id`,
        [newHj.id, pos.konto_typ_id, pos.kontonummer, pos.konto_bezeichnung || pos.bezeichnung, newParentId, pos.budget_gwg, pos.budget_anlagen, pos.budget_instandhaltung, userId, pos.kategorie_id, pos.budget_typ || 'detailliert']
      );
      kontoIdMap.set(pos.konto_id, kontoResult.rows[0].id);
    }

    // Mark plan as abgeschlossen
    await client.query(
      `UPDATE buchhaltung_planung SET status = 'abgeschlossen' WHERE id = $1`,
      [planungId]
    );

    await client.query('COMMIT');

    await logAudit(null, 'haushaltsjahr_aus_plan_erstellt', { planung_id: planungId, haushaltsjahr_id: newHj.id, jahr: nextYear }, userId);

    return { haushaltsjahr: newHj };
  } catch (error: any) {
    await client.query('ROLLBACK');
    logger.error('BuchhaltungService.createHaushaltsjahrFromPlan failed', { error, planungId });
    if (error.statusCode) throw error;
    throw new Error('Haushaltsjahr konnte nicht aus Planung erstellt werden');
  } finally {
    client.release();
  }
}

// ---------------------------------------------------------------------------
// Export
// ---------------------------------------------------------------------------

const buchhaltungService = {
  getKategorien,
  createKategorie,
  updateKategorie,
  deleteKategorie,
  getAllHaushaltsjahre,
  getHaushaltsjahrById,
  getCurrentHaushaltsjahr,
  createHaushaltsjahr,
  updateHaushaltsjahr,
  closeHaushaltsjahr,
  getAllKontoTypen,
  createKontoTyp,
  updateKontoTyp,
  deleteKontoTyp,
  getAllBankkonten,
  getBankkontoById,
  createBankkonto,
  updateBankkonto,
  deactivateBankkonto,
  getBankkontoStatement,
  getAllKonten,
  getKontoById,
  getKontenTree,
  getKontoDetail,
  getPendingCount,
  createKonto,
  updateKonto,
  deleteKonto,
  getBudgetUtilisation,
  listTransaktionen,
  getTransaktionById,
  createTransaktion,
  updateTransaktion,
  bookTransaktion,
  stornoTransaktion,
  deleteTransaktion,
  createTransfer,
  getBelegeByTransaktion,
  uploadBeleg,
  deleteBeleg,
  getFreigabenByTransaktion,
  createFreigabe,
  approveFreigabe,
  rejectFreigabe,
  getEinstellungen,
  setEinstellungen,
  logAudit,
  getAuditByTransaktion,
  getOverview,
  getAllWiederkehrend,
  getWiederkehrendById,
  createWiederkehrend,
  updateWiederkehrend,
  deleteWiederkehrend,
  exportTransaktionenCsv,
  createErstattung,
  getErstattungLinks,
  createPendingFromBestellung,
  listPlanungen,
  getPlanungById,
  createPlanung,
  updatePlanung,
  deletePlanung,
  createPlanposition,
  updatePlanposition,
  deletePlanposition,
  getPlanpositionenByPlanung,
  createHaushaltsjahrFromPlan,
};

export default buchhaltungService;
