import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";
import type { Response } from "express";
import { generateCashBankExcel, type CashBankExportRow } from "../utils/excel/CashBankExcelExport.js";
import { CashBankPdf, type CashBankPdfRow } from "../utils/pdf/CashBankPdf.js";

export interface NormalizedTransaction {
  id: string;
  date: string;
  rawDate: Date;
  createdAt: Date;
  voucherNo: string;
  type: "SALE" | "CUSTOMER_RECEIPT" | "SUPPLIER_PAYMENT" | "EXPENSE" | "TRANSFER";
  typeLabelTamil: string;
  typeLabelEnglish: string;
  account: "CASH" | "BANK";
  accountLabelTamil: string;
  paymentMode: "CASH" | "UPI" | "BANK";
  party: string;
  description: string;
  varavu: number;
  patru: number;
  balance: number;
  source: string;
  sourceId: string;
}

export class CashBankService {
  /**
   * Get active CashBankSetting or create default
   */
  async getSetting() {
    let setting = await prisma.cashBankSetting.findFirst({
      orderBy: { id: "asc" },
    });

    if (!setting) {
      setting = await prisma.cashBankSetting.create({
        data: {
          openingCashInHand: new Prisma.Decimal(0),
          openingCashInBank: new Prisma.Decimal(0),
          openingDate: new Date("2020-01-01"),
          remarks: "Default initial balance configuration",
        },
      });
    }

    return setting;
  }

  /**
   * Update or initialize active CashBankSetting with audit log
   */
  async updateSetting(
    data: {
      openingCashInHand: number | string;
      openingCashInBank: number | string;
      openingDate: string | Date;
      remarks?: string | null | undefined;
    },
    userId?: bigint
  ) {
    const existing = await this.getSetting();

    if (existing.isLocked) {
      throw new Error(
        "Initial opening balance is locked and cannot be edited. Opening balance can only be set once to ensure ledger integrity."
      );
    }

    const openingCashInHand = new Prisma.Decimal(data.openingCashInHand ?? 0);
    const openingCashInBank = new Prisma.Decimal(data.openingCashInBank ?? 0);
    const openingDate = new Date(data.openingDate);

    if (isNaN(openingDate.getTime())) {
      throw new Error("Invalid opening date provided");
    }

    const updated = await prisma.$transaction(async (tx) => {
      const saved = await tx.cashBankSetting.update({
        where: { id: existing.id },
        data: {
          openingCashInHand,
          openingCashInBank,
          openingDate,
          remarks: data.remarks ?? existing.remarks,
          isLocked: true,
        },
      });

      await tx.auditLog.create({
        data: {
          entityName: "CashBankSetting",
          entityId: existing.id,
          action: "UPDATE",
          oldValues: {
            openingCashInHand: existing.openingCashInHand.toNumber(),
            openingCashInBank: existing.openingCashInBank.toNumber(),
            openingDate: existing.openingDate.toISOString(),
            remarks: existing.remarks,
          },
          newValues: {
            openingCashInHand: saved.openingCashInHand.toNumber(),
            openingCashInBank: saved.openingCashInBank.toNumber(),
            openingDate: saved.openingDate.toISOString(),
            remarks: saved.remarks,
          },
          userId: userId ?? null,
        },
      });

      return saved;
    });

    return {
      ...updated,
      id: updated.id.toString(),
      openingCashInHand: updated.openingCashInHand.toNumber(),
      openingCashInBank: updated.openingCashInBank.toNumber(),
    };
  }

  /**
   * Calculate live balances for Cash in Hand, Cash in Bank (UPI + Bank), and Combined
   */
  async getBalances() {
    const setting = await this.getSetting();
    const openingDate = setting.openingDate;

    // 1. Sales (Received Cash vs Received Bank/UPI)
    const sales = await prisma.sale.findMany({
      where: {
        deletedAt: null,
        invoiceDate: { gte: openingDate },
      },
      select: {
        cashAmount: true,
        upiAmount: true,
        bankAmount: true,
      },
    });

    let cashSalesTotal = new Prisma.Decimal(0);
    let bankSalesTotal = new Prisma.Decimal(0);

    for (const s of sales) {
      cashSalesTotal = cashSalesTotal.add(s.cashAmount || 0);
      bankSalesTotal = bankSalesTotal.add(s.upiAmount || 0).add(s.bankAmount || 0);
    }

    // 2. Customer Receipts (Varavu)
    const cashReceiptsAgg = await prisma.customerReceipt.aggregate({
      _sum: { amount: true },
      where: {
        deletedAt: null,
        paymentMode: "CASH",
        receiptDate: { gte: openingDate },
      },
    });

    const bankReceiptsAgg = await prisma.customerReceipt.aggregate({
      _sum: { amount: true },
      where: {
        deletedAt: null,
        paymentMode: { in: ["UPI", "BANK"] },
        receiptDate: { gte: openingDate },
      },
    });

    const cashReceiptsTotal = cashReceiptsAgg._sum.amount ?? new Prisma.Decimal(0);
    const bankReceiptsTotal = bankReceiptsAgg._sum.amount ?? new Prisma.Decimal(0);

    // 3. Supplier Payments (Patru)
    const cashSuppPaymentsAgg = await prisma.supplierPayment.aggregate({
      _sum: { amount: true },
      where: {
        deletedAt: null,
        paymentMode: "CASH",
        paymentDate: { gte: openingDate },
      },
    });

    const bankSuppPaymentsAgg = await prisma.supplierPayment.aggregate({
      _sum: { amount: true },
      where: {
        deletedAt: null,
        paymentMode: { in: ["UPI", "BANK"] },
        paymentDate: { gte: openingDate },
      },
    });

    const cashSupplierTotal = cashSuppPaymentsAgg._sum.amount ?? new Prisma.Decimal(0);
    const bankSupplierTotal = bankSuppPaymentsAgg._sum.amount ?? new Prisma.Decimal(0);

    // 4. Expenses (Patru)
    const cashExpensesAgg = await prisma.expense.aggregate({
      _sum: { amount: true },
      where: {
        paymentMode: "CASH",
        expenseDate: { gte: openingDate },
      },
    });

    const bankExpensesAgg = await prisma.expense.aggregate({
      _sum: { amount: true },
      where: {
        paymentMode: { in: ["UPI", "BANK"] },
        expenseDate: { gte: openingDate },
      },
    });

    const cashExpensesTotal = cashExpensesAgg._sum.amount ?? new Prisma.Decimal(0);
    const bankExpensesTotal = bankExpensesAgg._sum.amount ?? new Prisma.Decimal(0);

    // 5. Cash-to-Bank Transfers (Cash Patru, Bank Varavu)
    const transfersAgg = await prisma.cashBankTransfer.aggregate({
      _sum: { amount: true },
      where: {
        deletedAt: null,
        transferDate: { gte: openingDate },
      },
    });

    const transfersTotal = transfersAgg._sum.amount ?? new Prisma.Decimal(0);

    // Totals & Closing Balances
    const openingCash = setting.openingCashInHand;
    const openingBank = setting.openingCashInBank;

    const totalCashVaravu = cashSalesTotal.add(cashReceiptsTotal);
    const totalCashPatru = cashSupplierTotal.add(cashExpensesTotal).add(transfersTotal);
    const currentCashInHand = openingCash.add(totalCashVaravu).sub(totalCashPatru);

    const totalBankVaravu = bankSalesTotal.add(bankReceiptsTotal).add(transfersTotal);
    const totalBankPatru = bankSupplierTotal.add(bankExpensesTotal);
    const currentCashInBank = openingBank.add(totalBankVaravu).sub(totalBankPatru);

    const totalCombinedBalance = currentCashInHand.add(currentCashInBank);

    return {
      openingDate: setting.openingDate.toISOString().split("T")[0] ?? "",
      remarks: setting.remarks,
      openingCashInHand: openingCash.toNumber(),
      openingCashInBank: openingBank.toNumber(),
      openingCombined: openingCash.add(openingBank).toNumber(),

      totalCashVaravu: totalCashVaravu.toNumber(),
      totalCashPatru: totalCashPatru.toNumber(),
      currentCashInHand: currentCashInHand.toNumber(),

      totalBankVaravu: totalBankVaravu.toNumber(),
      totalBankPatru: totalBankPatru.toNumber(),
      currentCashInBank: currentCashInBank.toNumber(),

      totalTransfers: transfersTotal.toNumber(),
      totalCombinedBalance: totalCombinedBalance.toNumber(),
      isLocked: Boolean(setting.isLocked),
    };
  }

  /**
   * Create a Cash to Bank transfer (Contra)
   */
  async createTransfer(
    data: {
      transferDate: string | Date;
      amount: number | string;
      referenceNo?: string | null | undefined;
      remarks?: string | null | undefined;
    },
    userId?: bigint
  ) {
    const amount = new Prisma.Decimal(data.amount);

    if (amount.lte(0)) {
      throw new Error("Transfer amount must be greater than 0");
    }

    const balances = await this.getBalances();
    if (amount.gt(balances.currentCashInHand)) {
      throw new Error(
        `Insufficient Cash in Hand. Available: ₹${balances.currentCashInHand.toFixed(
          2
        )}, Requested: ₹${amount.toFixed(2)}`
      );
    }

    const transferDate = new Date(data.transferDate);
    if (isNaN(transferDate.getTime())) {
      throw new Error("Invalid transfer date provided");
    }

    const transfer = await prisma.cashBankTransfer.create({
      data: {
        transferDate,
        amount,
        referenceNo: data.referenceNo ?? null,
        remarks: data.remarks ?? null,
        createdBy: userId ?? null,
      },
    });

    return {
      ...transfer,
      id: transfer.id.toString(),
      amount: transfer.amount.toNumber(),
    };
  }

  /**
   * Soft-delete a transfer
   */
  async deleteTransfer(id: bigint, userId?: bigint) {
    const existing = await prisma.cashBankTransfer.findUnique({
      where: { id },
    });

    if (!existing || existing.deletedAt !== null) {
      throw new Error("Transfer record not found");
    }

    const updated = await prisma.$transaction(async (tx) => {
      const res = await tx.cashBankTransfer.update({
        where: { id },
        data: {
          deletedAt: new Date(),
          deletedBy: userId ?? null,
        },
      });

      await tx.auditLog.create({
        data: {
          entityName: "CashBankTransfer",
          entityId: id,
          action: "DELETE",
          oldValues: {
            amount: existing.amount.toNumber(),
            transferDate: existing.transferDate.toISOString(),
            referenceNo: existing.referenceNo,
            remarks: existing.remarks,
          },
          userId: userId ?? null,
        },
      });

      return res;
    });

    return {
      ...updated,
      id: updated.id.toString(),
      amount: updated.amount.toNumber(),
    };
  }

  /**
   * Get list of transfers
   */
  async getTransfers(dateFrom?: string, dateTo?: string) {
    const where: Prisma.CashBankTransferWhereInput = {
      deletedAt: null,
    };

    if (dateFrom || dateTo) {
      where.transferDate = {};
      if (dateFrom) where.transferDate.gte = new Date(dateFrom);
      if (dateTo) where.transferDate.lte = new Date(dateTo);
    }

    const list = await prisma.cashBankTransfer.findMany({
      where,
      orderBy: [{ transferDate: "desc" }, { id: "desc" }],
      include: {
        createdByUser: { select: { name: true } },
      },
    });

    return list.map((t) => ({
      id: t.id.toString(),
      transferDate: t.transferDate.toISOString().split("T")[0] ?? "",
      amount: t.amount.toNumber(),
      referenceNo: t.referenceNo,
      remarks: t.remarks,
      createdByName: t.createdByUser?.name ?? "System",
      createdAt: t.createdAt,
    }));
  }

  /**
   * Fetch all raw transactions starting from openingDate and normalize them
   */
  private async fetchNormalizedTransactions(openingDate: Date) {
    const rawItems: NormalizedTransaction[] = [];

    // 1. Sales
    const sales = await prisma.sale.findMany({
      where: {
        deletedAt: null,
        invoiceDate: { gte: openingDate },
      },
      include: {
        customer: { select: { name: true, tamilName: true } },
      },
    });

    for (const s of sales) {
      const party = s.customer?.name || s.walkInName || "Walk-in Customer";
      const dateStr = s.invoiceDate.toISOString().split("T")[0] ?? "";

      // Cash portion
      if (s.cashAmount && s.cashAmount.gt(0)) {
        rawItems.push({
          id: `sale-cash-${s.id.toString()}`,
          date: dateStr,
          rawDate: s.invoiceDate,
          createdAt: s.createdAt,
          voucherNo: s.invoiceNo,
          type: "SALE",
          typeLabelTamil: "Sale (Cash Varavu)",
          typeLabelEnglish: "Cash bill",
          account: "CASH",
          accountLabelTamil: "Cash in Hand",
          paymentMode: "CASH",
          party,
          description: s.remarks ? `Sale Cash Received - ${s.remarks}` : "Sale Cash Received",
          varavu: s.cashAmount.toNumber(),
          patru: 0,
          balance: 0,
          source: "Sale",
          sourceId: s.id.toString(),
        });
      }

      // Bank/UPI portion (UPI & Bank treated as one Bank account)
      const bankUpiTotal = new Prisma.Decimal(s.upiAmount || 0).add(s.bankAmount || 0);
      if (bankUpiTotal.gt(0)) {
        const modeDesc = s.upiAmount.gt(0) && s.bankAmount.gt(0) ? "UPI/Bank" : s.upiAmount.gt(0) ? "UPI" : "Bank";
        rawItems.push({
          id: `sale-bank-${s.id.toString()}`,
          date: dateStr,
          rawDate: s.invoiceDate,
          createdAt: s.createdAt,
          voucherNo: s.invoiceNo,
          type: "SALE",
          typeLabelTamil: `Sale (${modeDesc} Varavu)`,
          typeLabelEnglish: "Cash bill pay",
          account: "BANK",
          accountLabelTamil: "Cash in Bank",
          paymentMode: s.upiAmount.gt(0) ? "UPI" : "BANK",
          party,
          description: s.remarks ? `Sale ${modeDesc} Received - ${s.remarks}` : `Sale ${modeDesc} Received`,
          varavu: bankUpiTotal.toNumber(),
          patru: 0,
          balance: 0,
          source: "Sale",
          sourceId: s.id.toString(),
        });
      }
    }

    // 2. Customer Receipts
    const receipts = await prisma.customerReceipt.findMany({
      where: {
        deletedAt: null,
        receiptDate: { gte: openingDate },
      },
      include: {
        customer: { select: { name: true, tamilName: true } },
      },
    });

    for (const r of receipts) {
      const party = r.customer?.name || "Customer";
      const isCash = r.paymentMode === "CASH";
      const account: "CASH" | "BANK" = isCash ? "CASH" : "BANK";
      const dateStr = r.receiptDate.toISOString().split("T")[0] ?? "";

      rawItems.push({
        id: `rcpt-${r.id.toString()}`,
        date: dateStr,
        rawDate: r.receiptDate,
        createdAt: r.createdAt,
        voucherNo: r.referenceNo || `RCPT-${r.id.toString()}`,
        type: "CUSTOMER_RECEIPT",
        typeLabelTamil: isCash ? "Customer Receipt (Cash Varavu)" : "Customer Receipt (Bank Varavu)",
        typeLabelEnglish: "Customer pay",
        account,
        accountLabelTamil: isCash ? "Cash in Hand" : "Cash in Bank",
        paymentMode: r.paymentMode as any,
        party,
        description: r.remarks || "Customer payment received",
        varavu: r.amount.toNumber(),
        patru: 0,
        balance: 0,
        source: "CustomerReceipt",
        sourceId: r.id.toString(),
      });
    }

    // 3. Supplier Payments
    const supplierPayments = await prisma.supplierPayment.findMany({
      where: {
        deletedAt: null,
        paymentDate: { gte: openingDate },
      },
      include: {
        supplier: { select: { name: true, tamilName: true } },
      },
    });

    for (const sp of supplierPayments) {
      const party = sp.supplier?.name || "Supplier";
      const isCash = sp.paymentMode === "CASH";
      const account: "CASH" | "BANK" = isCash ? "CASH" : "BANK";
      const dateStr = sp.paymentDate.toISOString().split("T")[0] ?? "";

      rawItems.push({
        id: `spay-${sp.id.toString()}`,
        date: dateStr,
        rawDate: sp.paymentDate,
        createdAt: sp.createdAt,
        voucherNo: sp.referenceNo || `SPAY-${sp.id.toString()}`,
        type: "SUPPLIER_PAYMENT",
        typeLabelTamil: isCash ? "Supplier Payment (Cash Patru)" : "Supplier Payment (Bank Patru)",
        typeLabelEnglish: "Supplier payment",
        account,
        accountLabelTamil: isCash ? "Cash in Hand" : "Cash in Bank",
        paymentMode: sp.paymentMode as any,
        party,
        description: sp.remarks || "Supplier payment made",
        varavu: 0,
        patru: sp.amount.toNumber(),
        balance: 0,
        source: "SupplierPayment",
        sourceId: sp.id.toString(),
      });
    }

    // 4. Expenses
    const expenses = await prisma.expense.findMany({
      where: {
        expenseDate: { gte: openingDate },
      },
    });

    for (const e of expenses) {
      const isCash = e.paymentMode === "CASH";
      const account: "CASH" | "BANK" = isCash ? "CASH" : "BANK";
      const dateStr = e.expenseDate.toISOString().split("T")[0] ?? "";

      rawItems.push({
        id: `exp-${e.id.toString()}`,
        date: dateStr,
        rawDate: e.expenseDate,
        createdAt: e.createdAt,
        voucherNo: `EXP-${e.id.toString()}`,
        type: "EXPENSE",
        typeLabelTamil: isCash ? "Expense (Cash Patru)" : "Expense (Bank Patru)",
        typeLabelEnglish: "Expenses",
        account,
        accountLabelTamil: isCash ? "Cash in Hand" : "Cash in Bank",
        paymentMode: e.paymentMode as any,
        party: e.category,
        description: e.remarks || e.category,
        varavu: 0,
        patru: e.amount.toNumber(),
        balance: 0,
        source: "Expense",
        sourceId: e.id.toString(),
      });
    }

    // 5. Cash-to-Bank Transfers
    const transfers = await prisma.cashBankTransfer.findMany({
      where: {
        deletedAt: null,
        transferDate: { gte: openingDate },
      },
    });

    for (const t of transfers) {
      const dateStr = t.transferDate.toISOString().split("T")[0] ?? "";
      const voucherNo = t.referenceNo || `TRF-${t.id.toString()}`;

      // Cash side (Patru / Outflow from Cash)
      rawItems.push({
        id: `trf-cash-${t.id.toString()}`,
        date: dateStr,
        rawDate: t.transferDate,
        createdAt: t.createdAt,
        voucherNo,
        type: "TRANSFER",
        typeLabelTamil: "Cash to Bank (Cash Patru)",
        typeLabelEnglish: "Cash move into bank",
        account: "CASH",
        accountLabelTamil: "Cash in Hand",
        paymentMode: "CASH",
        party: "Cash moved into bank",
        description: t.remarks || "Transfer cash to bank account",
        varavu: 0,
        patru: t.amount.toNumber(),
        balance: 0,
        source: "CashBankTransfer",
        sourceId: t.id.toString(),
      });

      // Bank side (Varavu / Inflow to Bank)
      rawItems.push({
        id: `trf-bank-${t.id.toString()}`,
        date: dateStr,
        rawDate: t.transferDate,
        createdAt: t.createdAt,
        voucherNo,
        type: "TRANSFER",
        typeLabelTamil: "Cash to Bank (Bank Varavu)",
        typeLabelEnglish: "Cash move into bank",
        account: "BANK",
        accountLabelTamil: "Cash in Bank",
        paymentMode: "BANK",
        party: "Cash move into bank",
        description: t.remarks || "Cash deposited to bank account",
        varavu: t.amount.toNumber(),
        patru: 0,
        balance: 0,
        source: "CashBankTransfer",
        sourceId: t.id.toString(),
      });
    }

    // Sort all transactions chronologically: rawDate asc, createdAt asc, id asc
    rawItems.sort((a, b) => {
      const timeDiff = a.rawDate.getTime() - b.rawDate.getTime();
      if (timeDiff !== 0) return timeDiff;
      const createdDiff = a.createdAt.getTime() - b.createdAt.getTime();
      if (createdDiff !== 0) return createdDiff;
      return a.id.localeCompare(b.id);
    });

    return rawItems;
  }

  /**
   * Get ledger with filters, search, and running balances
   */
  async getLedger(filters?: {
    account?: "ALL" | "CASH" | "BANK" | undefined;
    type?: string | undefined;
    date?: string | undefined;
    dateFrom?: string | undefined;
    dateTo?: string | undefined;
    search?: string | undefined;
  }) {
    const setting = await this.getSetting();
    const openingDate = setting.openingDate;
    const openingDateStr = setting.openingDate.toISOString().split("T")[0] ?? "";

    const accountFilter = filters?.account || "ALL";
    const typeFilter = filters?.type || "ALL";
    const singleDate = filters?.date;
    const dateFrom = singleDate || filters?.dateFrom;
    const dateTo = singleDate || filters?.dateTo;
    const search = filters?.search?.trim().toLowerCase();

    // Fetch all items from system openingDate
    const allItems = await this.fetchNormalizedTransactions(openingDate);

    // Initial base opening balance
    const baseOpeningCash = setting.openingCashInHand.toNumber();
    const baseOpeningBank = setting.openingCashInBank.toNumber();

    // Calculate carry-forward opening balance if dateFrom is specified
    let effectiveOpeningCash = baseOpeningCash;
    let effectiveOpeningBank = baseOpeningBank;

    if (dateFrom && dateFrom > openingDateStr) {
      for (const item of allItems) {
        if (item.date < dateFrom) {
          if (item.account === "CASH") {
            effectiveOpeningCash += item.varavu - item.patru;
          } else if (item.account === "BANK") {
            effectiveOpeningBank += item.varavu - item.patru;
          }
        }
      }
    }

    let startOpeningBalance = 0;
    if (accountFilter === "CASH") {
      startOpeningBalance = effectiveOpeningCash;
    } else if (accountFilter === "BANK") {
      startOpeningBalance = effectiveOpeningBank;
    } else {
      startOpeningBalance = effectiveOpeningCash + effectiveOpeningBank;
    }

    // Filter items within requested period (strictly on or after openingDate)
    const filteredPeriodItems = allItems.filter((item) => {
      if (item.date < openingDateStr) return false;
      if (dateFrom && item.date < dateFrom) return false;
      if (dateTo && item.date > dateTo) return false;

      // Account filter
      if (accountFilter !== "ALL" && item.account !== accountFilter) {
        return false;
      }

      // Type filter
      if (typeFilter !== "ALL" && item.type !== typeFilter) {
        return false;
      }

      // Search keyword filter
      if (search) {
        const matchesParty = item.party.toLowerCase().includes(search);
        const matchesVoucher = item.voucherNo.toLowerCase().includes(search);
        const matchesDesc = item.description.toLowerCase().includes(search);
        const matchesType = item.typeLabelTamil.toLowerCase().includes(search) || item.typeLabelEnglish.toLowerCase().includes(search);
        if (!matchesParty && !matchesVoucher && !matchesDesc && !matchesType) {
          return false;
        }
      }

      return true;
    });

    // Compute running balance based on account selection
    let runningBalance = startOpeningBalance;
    let totalVaravu = 0;
    let totalPatru = 0;

    const resultEntries: NormalizedTransaction[] = [];

    for (const item of filteredPeriodItems) {
      // In combined (ALL) view: a transfer has 0 net change on combined balance
      // We keep the entries for full audit transparency
      let netChange = item.varavu - item.patru;

      runningBalance += netChange;
      totalVaravu += item.varavu;
      totalPatru += item.patru;

      resultEntries.push({
        ...item,
        balance: Math.round(runningBalance * 100) / 100,
      });
    }

    const closingBalance = Math.round(runningBalance * 100) / 100;

    // Overall current status
    const liveBalances = await this.getBalances();

    return {
      openingBalance: Math.round(startOpeningBalance * 100) / 100,
      totalVaravu: Math.round(totalVaravu * 100) / 100,
      totalPatru: Math.round(totalPatru * 100) / 100,
      closingBalance,
      currentCashInHand: liveBalances.currentCashInHand,
      currentCashInBank: liveBalances.currentCashInBank,
      totalCombined: liveBalances.totalCombinedBalance,
      openingDate: setting.openingDate.toISOString().split("T")[0],
      entries: resultEntries,
    };
  }

  /**
   * Export Cash & Bank ledger to Excel
   */
  async exportExcel(
    res: Response,
    filters?: {
      account?: "ALL" | "CASH" | "BANK" | undefined;
      type?: string | undefined;
      date?: string | undefined;
      dateFrom?: string | undefined;
      dateTo?: string | undefined;
      search?: string | undefined;
    }
  ) {
    const ledger = await this.getLedger(filters);

    const exportRows: CashBankExportRow[] = ledger.entries.map((e) => ({
      date: e.date,
      voucherNo: e.voucherNo,
      transactionType: e.typeLabelTamil,
      account: e.account === "CASH" ? "Cash in Hand" : "Cash in Bank",
      paymentMode: e.paymentMode,
      party: e.party,
      description: e.description,
      varavu: e.varavu,
      patru: e.patru,
      balance: e.balance,
    }));

    await generateCashBankExcel(
      exportRows,
      {
        openingBalance: ledger.openingBalance,
        totalVaravu: ledger.totalVaravu,
        totalPatru: ledger.totalPatru,
        closingBalance: ledger.closingBalance,
        currentCashInHand: ledger.currentCashInHand,
        currentCashInBank: ledger.currentCashInBank,
        totalCombined: ledger.totalCombined,
      },
      res,
      filters
    );
  }

  /**
   * Export Cash & Bank ledger to PDF
   */
  async exportPdf(
    res: Response,
    filters?: {
      account?: "ALL" | "CASH" | "BANK" | undefined;
      type?: string | undefined;
      date?: string | undefined;
      dateFrom?: string | undefined;
      dateTo?: string | undefined;
      search?: string | undefined;
    }
  ) {
    const ledger = await this.getLedger(filters);

    const pdfRows: CashBankPdfRow[] = ledger.entries.map((e) => ({
      date: e.date,
      voucherNo: e.voucherNo,
      transactionType: e.typeLabelEnglish,
      account: e.account,
      paymentMode: e.paymentMode,
      party: e.party,
      description: e.description,
      varavu: e.varavu,
      patru: e.patru,
      balance: e.balance,
    }));

    await CashBankPdf.generate(
      pdfRows,
      {
        openingBalance: ledger.openingBalance,
        totalVaravu: ledger.totalVaravu,
        totalPatru: ledger.totalPatru,
        closingBalance: ledger.closingBalance,
        currentCashInHand: ledger.currentCashInHand,
        currentCashInBank: ledger.currentCashInBank,
        totalCombined: ledger.totalCombined,
      },
      res,
      filters
    );
  }
}

export default new CashBankService();
