const { Op, where } = require("sequelize");
const dbSAPPS = require("../../helpers/db");
const moment = require("moment/moment");
const { enabled } = require("../companies/company.service");
require("dotenv").config();

module.exports = {
  createAccountEntries,
  updateDebitBalances,
  updateCreditBalances,
  reviseDebitBalances,
  reviseCreditBalances,
  saldoBalances,
};

async function createAccountEntries(params) {
  if (!Array.isArray(params) || params.length === 0) {
    throw new Error("Parameter harus berupa array dan tidak boleh kosong");
  }

  const transaction = await dbSAPPS.sequelize.transaction();

  try {
    console.log("params", params);
    const accountEntries = params.map((ca) => ({
      ...ca,
      creation: Date.now(),
      modified: Date.now(),
    }));

    await dbSAPPS.AccountEntries.bulkCreate(accountEntries, { transaction, lock: transaction.LOCK.UPDATE });
    await transaction.commit();

    return {
      success: true,
      message: "Data berhasil disimpan!",
    };
  } catch (error) {
    await transaction.rollback();
    const errorMessage = error.message || "An error occurred during Bulk update processing.";
    return {
      success: false,
      message: errorMessage,
    };
  }
}

/**
 * Fungsi untuk mengupdate saldo client berdasarkan data di AccountEntries
 *
 * @param {Array<Object>} params - Array of objects yang berisi data untuk di proses
 * @param {number} params.id - ID dari AccountEntries yang akan di proses
 *
 * @returns {Promise<Object>}
 *
 * @throws {Error} Jika parameter IDs tidak berupa array atau kosong
 * @throws {Error} Jika terjadi kesalahan saat mengupdate saldo
 */
async function updateDebitBalances(params) {
  const ids = params?.map((param) => param.id);

  if (!Array.isArray(ids) || ids.length === 0) {
    throw new Error("Parameter IDs harus berupa array dan tidak boleh kosong");
  }

  console.log("ids", ids);

  const transaction = await dbSAPPS.sequelize.transaction();
  try {
    // Step 1: Update status ke 1
    await dbSAPPS.AccountEntries.update({ status: 1 }, { where: { id: { [Op.in]: ids } }, transaction });

    // Step 2: Ambil data
    const balances = await dbSAPPS.AccountEntries.findAll({
      where: { id: { [Op.in]: ids } },
      include: {
        model: dbSAPPS.SalesDistributor,
        as: "sales",
        include: {
          model: dbSAPPS.Item,
          as: "item",
          attributes: ["id", "price"],
        },
      },
      transaction,
    });

    const clientBalances = {};

    for (const row of balances) {
      const clientId = row.client_id;
      const price = row.sales.price;
      const quantity = row.quantity;
      const ks = row.kerja_sama;

      const nilai = price * quantity * (ks / 100);

      if (!clientBalances[clientId]) {
        clientBalances[clientId] = 0;
      }

      clientBalances[clientId] += nilai;
    }

    // Step 3: Update saldo
    for (const [clientId, amount] of Object.entries(clientBalances)) {
      const exists = await dbSAPPS.Client.findByPk(clientId);
      if (!exists) {
        console.warn(`Client ID ${clientId} tidak ditemukan`);
        continue;
      }

      if (!amount || isNaN(amount)) {
        console.warn(`Amount tidak valid untuk clientId ${clientId}:`, amount);
        continue;
      }

      console.log(`Menambahkan ${amount} ke debit_balance client ID ${clientId}`);

      await dbSAPPS.Client.increment({ debit_balance: amount }, { where: { id: clientId }, transaction });
    }

    // Commit jika semua berhasil
    await transaction.commit();

    return {
      success: true,
      message: "Update saldo & status berhasil",
      data: clientBalances,
      balances: balances,
      // before:before,
      // before_test: before.debit_balance,
      // after_test: after.debit_balance
    };
  } catch (error) {
    // Rollback the transaction in case of error
    await transaction.rollback();
    const errorMessage = error.message || "An error occurred during Bulk update processing.";
    return {
      success: false,
      message: errorMessage,
    };
  }
}

/**
 * Fungsi untuk mengurangi saldo client berdasarkan data di AccountEntries
 *
 * @param {Array<Object>} params - Array of objects yang berisi data untuk di proses
 * @param {number} params.id - ID dari AccountEntries yang akan di proses
 *
 * @returns {Promise<Object>}
 *
 * @throws {Error} Jika parameter IDs tidak berupa array atau kosong
 * @throws {Error} Jika terjadi kesalahan saat mengupdate saldo
 */
async function reviseDebitBalances(params) {
  const ids = params?.map((param) => param.id);

  if (!Array.isArray(ids) || ids.length === 0) {
    throw new Error("Parameter IDs harus berupa array dan tidak boleh kosong");
  }

  const transaction = await dbSAPPS.sequelize.transaction();
  try {
    // Step 2: Ambil data
    const balances = await dbSAPPS.AccountEntries.findAll({
      where: { id: { [Op.in]: ids } },
      include: {
        model: dbSAPPS.SalesDistributor,
        as: "sales",
        include: {
          model: dbSAPPS.Item,
          as: "item",
          attributes: ["id", "price"],
        },
      },
      transaction,
    });

    const clientBalances = {};

    for (const row of balances) {
      const clientId = row.client_id;
      const price = row.sales.price;
      const quantity = row.quantity;
      const ks = row.kerja_sama;

      const nilai = price * quantity * (ks / 100);

      if (!clientBalances[clientId]) {
        clientBalances[clientId] = 0;
      }

      clientBalances[clientId] += nilai;
    }

    // Step 3: kurangi saldo
    for (const [clientId, amount] of Object.entries(clientBalances)) {
      const exists = await dbSAPPS.Client.findByPk(clientId);
      if (!exists) {
        console.warn(`Client ID ${clientId} tidak ditemukan`);
        continue;
      }

      if (!amount || isNaN(amount)) {
        console.warn(`Amount tidak valid untuk clientId ${clientId}:`, amount);
        continue;
      }

      console.log(`Mengurangi ${amount} dari debit_balance client ID ${clientId}`);

      await dbSAPPS.Client.decrement({ debit_balance: amount }, { where: { id: clientId }, transaction });

      // Hapus data di AccountEntries setelah saldo dikurangi
      await dbSAPPS.AccountEntries.destroy({
        where: { id: { [Op.in]: ids } },
        transaction,
        hooks: false, // disable hooks, karena kita sudah mengupdate saldo client di atas
      });
    }

    // Commit jika semua berhasil
    await transaction.commit();

    return {
      success: true,
      message: "Update saldo & status berhasil",
      data: clientBalances,
      balances: balances,
    };
  } catch (error) {
    // Rollback the transaction in case of error
    await transaction.rollback();
    const errorMessage = error.message || "An error occurred during Bulk update processing.";
    return {
      success: false,
      message: errorMessage,
    };
  }
}

async function updateCreditBalances(params) {
  const transaction = await dbSAPPS.sequelize.transaction();

  try {
    // Step 1: Ambil data periode berdasarkan transfer_realization_date dari gimmick request
    const date = moment(params.transfer_realization_date).format("YYYY-MM-DD");

    const period = await dbSAPPS.Period.findOne({
      where: {
        start_date: { [Op.lte]: date },
        end_date: { [Op.gte]: date },
      },
      include: [
        {
          model: dbSAPPS.Year,
          as: "year",
          attributes: ["id", "display_name"],
        },
      ],
      transaction,
    });

    if (!period) {
      throw new Error(`Periode tidak ditemukan untuk tanggal ${date}`);
    }

    if (!period.year) {
      throw new Error("Relasi year tidak ditemukan");
    }

    // Update status account entries
    await dbSAPPS.AccountEntries.update(
      { status: 1, period_id: period.id, year_id: period.year_id },
      { where: { gimmickreq_id: params.id }, transaction }
    );

    // Get allocation data
    const allocation = await dbSAPPS.GimmickAllocation.findAll({
      where: {
        gimmickreq_id: params.id,
        enabled: true,
      },
      transaction,
    });

    const ids = allocation.map((item) => item.id);
    const balances = await dbSAPPS.GimmickAllocation.findAll({
      where: { id: { [Op.in]: ids } },
      include: [
        {
          model: dbSAPPS.Item,
          as: "item",
          attributes: ["id", "display_name", "price"],
        },
      ],
      transaction,
    });

    const clientBalances = {};

    for (const row of balances) {
      const clientId = params.client_id;
      const gimmickValue = row.gimmick_value;
      const period = params.periode;

      const nilai = gimmickValue * period;

      clientBalances[clientId] = (clientBalances[clientId] || 0) + nilai;
    }

    // Update balances
    for (const [clientId, amount] of Object.entries(clientBalances)) {
      const exists = await dbSAPPS.Client.findByPk(clientId);
      if (!exists) {
        console.warn(`Client ID ${clientId} not found`);
        continue;
      }

      if (!amount || isNaN(amount)) {
        console.warn(`Invalid amount for clientId ${clientId}:`, amount);
        continue;
      }

      console.log(`Adding ${amount} to credit_balance for client ID ${clientId}`);

      await dbSAPPS.Client.increment({ credit_balance: amount }, { where: { id: clientId }, transaction });
    }

    await dbSAPPS.GimmickRequest.update(
      {
        is_balance_posted: true,
      },
      { where: { id: params.id }, transaction }
    );

    // Commit transaction if successful
    await transaction.commit();

    return {
      success: true,
      message: "Balance and status update successful",
      data: clientBalances,
      balances: balances,
    };
  } catch (error) {
    // Rollback the transaction in case of error
    await transaction.rollback();
    const errorMessage = error.message || "An error occurred during Bulk update processing.";
    return {
      success: false,
      message: errorMessage,
    };
  }
}

// async function reviseCreditBalances(params) {
//   const transaction = await dbSAPPS.sequelize.transaction();

//   try {
//     const gimmick = await dbSAPPS.GimmickRequest.findOne({
//       where: { id: params.id },
//       order: [["transfer_realization_date", "ASC"]],
//       transaction,
//     });

//     if (!gimmick) {
//       return {
//         success: false,
//         message: "Data gimmick tidak ditemukan",
//       };
//     }

//     const period = await dbSAPPS.Period.findOne({
//       where: {
//         start_date: { [Op.lte]: gimmick.transfer_realization_date },
//         end_date: { [Op.gte]: gimmick.transfer_realization_date },
//       },
//       include: [
//         {
//           model: dbSAPPS.Year,
//           as: "year",
//           attributes: ["id", "display_name"],
//         },
//       ],
//       transaction,
//     });

//     // Update status account entries
//     await dbSAPPS.AccountEntries.update(
//       { status: 0, period_id: period.id, year_id: period.year_id },
//       { where: { gimmickreq_id: params.id }, transaction }
//     );

//     // Get allocation data
//     const allocation = await dbSAPPS.GimmickAllocation.findAll({
//       where: {
//         gimmickreq_id: params.id,
//         enabled: true,
//       },
//       transaction,
//     });

//     const ids = allocation.map((item) => item.id);
//     const balances = await dbSAPPS.GimmickAllocation.findAll({
//       where: { id: { [Op.in]: ids } },
//       include: [
//         {
//           model: dbSAPPS.Item,
//           as: "item",
//           attributes: ["id", "display_name", "price"],
//         },
//       ],
//       transaction,
//     });

//     const clientBalances = {};

//     for (const row of balances) {
//       const clientId = params.client_id;
//       const gimmickValue = row.gimmick_value;
//       const period = params.periode;

//       const nilai = gimmickValue * period;

//       clientBalances[clientId] = (clientBalances[clientId] || 0) - nilai;
//     }

//     // Update balances
//     for (const [clientId, amount] of Object.entries(clientBalances)) {
//       const exists = await dbSAPPS.Client.findByPk(clientId);
//       if (!exists) {
//         console.warn(`Client ID ${clientId} not found`);
//         continue;
//       }

//       if (!amount || isNaN(amount)) {
//         console.warn(`Invalid amount for clientId ${clientId}:`, amount);
//         continue;
//       }

//       console.log(`Subtracting ${amount} from credit_balance for client ID ${clientId}`);

//       await dbSAPPS.Client.decrement({ credit_balance: amount }, { where: { id: clientId }, transaction });
//     }

//     await dbSAPPS.GimmickRequest.update(
//       {
//         is_balance_posted: false,
//       },
//       { where: { id: params.id }, transaction }
//     );

//     // Commit transaction if successful
//     await transaction.commit();

//     return {
//       success: true,
//       message: "Balance and status update successful",
//       data: clientBalances,
//       balances: balances,
//     };
//   } catch (error) {
//     // Rollback the transaction in case of error
//     await transaction.rollback();
//     const errorMessage = error.message || "An error occurred during Bulk update processing.";
//     return {
//       success: false,
//       message: errorMessage,
//     };
//   }
// }

async function reviseCreditBalances(params) {
  const transaction = await dbSAPPS.sequelize.transaction();

  try {
    // ================================
    // 1. Ambil data gimmick
    // ================================
    const gimmick = await dbSAPPS.GimmickRequest.findByPk(params.id, {
      transaction,
    });

    if (!gimmick) {
      throw new Error("Data gimmick tidak ditemukan");
    }

    if (!gimmick.is_balance_posted) {
      throw new Error("Balance belum pernah diposting, tidak bisa direvisi");
    }

    // ================================
    // 2. Ambil periode berdasarkan tanggal realisasi
    // ================================
    const date = moment(gimmick.transfer_realization_date).format("YYYY-MM-DD");

    const period = await dbSAPPS.Period.findOne({
      where: {
        start_date: { [Op.lte]: date },
        end_date: { [Op.gte]: date },
      },
      include: [
        {
          model: dbSAPPS.Year,
          as: "year",
          attributes: ["id", "display_name"],
        },
      ],
      transaction,
    });

    if (!period || !period.year) {
      throw new Error("Periode / Tahun tidak ditemukan");
    }

    // ================================
    // 3. Update Account Entries
    // ================================
    await dbSAPPS.AccountEntries.update(
      {
        status: 0,
        period_id: period.id,
        year_id: period.year_id,
      },
      {
        where: { gimmickreq_id: params.id },
        transaction,
      }
    );

    // ================================
    // 4. Ambil Allocation
    // ================================
    const allocations = await dbSAPPS.GimmickAllocation.findAll({
      where: {
        gimmickreq_id: params.id,
        enabled: true,
      },
      transaction,
    });

    if (!allocations.length) {
      throw new Error("Data allocation tidak ditemukan");
    }

    // ================================
    // 5. Hitung total pengurangan (AMAN)
    // ================================
    const totalAllocation = allocations.reduce((sum, row) => {
      return sum + Number(row.gimmick_value || 0);
    }, 0);

    const totalAmount = totalAllocation * Number(params.periode || 1);

    if (!totalAmount || isNaN(totalAmount)) {
      throw new Error("Nilai balance tidak valid");
    }

    // ================================
    // 6. Kurangi saldo client
    // ================================
    const client = await dbSAPPS.Client.findByPk(params.client_id, {
      transaction,
    });

    if (!client) {
      throw new Error(`Client ID ${params.client_id} tidak ditemukan`);
    }

    await dbSAPPS.Client.decrement({ credit_balance: totalAmount }, { where: { id: params.client_id }, transaction });

    // ================================
    // 7. Update status gimmick
    // ================================
    await dbSAPPS.GimmickRequest.update(
      {
        is_balance_posted: false,
      },
      { where: { id: params.id }, transaction }
    );

    // ================================
    // 8. Commit
    // ================================
    await transaction.commit();

    return {
      success: true,
      message: "Revisi balance berhasil",
      data: {
        client_id: params.client_id,
        amount_reduced: totalAmount,
      },
    };
  } catch (error) {
    await transaction.rollback();

    return {
      success: false,
      message: error.message || "Terjadi kesalahan saat revisi balance",
    };
  }
}

// SALDO BALANCE PER USER
// async function saldoBalances(params) {
//   console.log("PARAMS SB: ", params);

//   // Get client id
//   const clientId = params.client_id;

//   // Get data AccountEntries with status 1 owned by clientId
//   const accountEntries = await dbSAPPS.AccountEntries.findAll({
//     where: { client_id: clientId, status: 1 },
//     include: [
//       {
//         model: dbSAPPS.SalesDistributor,
//         as: "sales",
//         include: {
//           model: dbSAPPS.Item,
//           as: "item",
//           attributes: ["id", "price", "display_name"],
//         },
//       },
//       {
//         model: dbSAPPS.GimmickRequest,
//         as: "gimmickreq",
//       },
//       {
//         model: dbSAPPS.Item,
//         as: "item",
//         attributes: ["id", "price", "display_name"],
//       },
//     ],

//     order: [["createdAt", "ASC"]], // Order by creation date to calculate running balance
//   });

//   const saldoAwal = await dbSAPPS.SaldoAwalClient.findAll({
//     where: { client_id: clientId, enabled: true },
//   });

//   console.log("saldoAwal : ", saldoAwal);

//   // Initialize variables
//   let balance = 0;
//   const reportData = [];
//   const products = {};

//   // Process each entry
//   for (const entry of accountEntries) {
//     // const productName = entry.sales.item.display_name || entry.item.display_name;
//     // const debitAmount = entry.sales.item.price * entry.quantity * (entry.kerja_sama / 100);
//     // const creditAmount = entry.item.price * entry.quantity * (entry.kerja_sama / 100);
//     const productName = entry.sales?.item?.display_name || entry.item?.display_name || "Unknown Product";

//     const debitAmount = (entry.sales?.item?.price || 0) * entry.quantity * (entry.kerja_sama / 100);
//     const creditAmount =
//       (entry.item?.price || 0) * entry.quantity * (entry.kerja_sama / 100) * entry?.gimmickreq?.periode;

//     // Initialize product if not exists
//     if (!products[productName]) {
//       products[productName] = { debit: 0, credit: 0, balance: 0 };
//     }

//     // Update debit and credit
//     if (entry.type === "debit") {
//       products[productName].debit += debitAmount;
//     } else {
//       products[productName].credit += creditAmount;
//     }

//     // Calculate balance
//     products[productName].balance = products[productName].debit - products[productName].credit;

//     // Add to report data
//     reportData.push({
//       product: productName,
//       debit: products[productName].debit.toLocaleString("id-ID"),
//       credit: products[productName].credit.toLocaleString("id-ID"),
//       balance: products[productName].balance.toLocaleString("id-ID"),
//     });
//   }

//   // Format report data to group by product name
//   const groupedReportData = Object.entries(products).map(([productName, { debit, credit, balance }]) => ({
//     product: productName,
//     debit,
//     credit,
//     balance,
//   }));

//   // Calculate total debit and credit
//   const totalDebit = Object.values(products).reduce((acc, { debit }) => acc + debit, 0);
//   const totalCredit = Object.values(products).reduce((acc, { credit }) => acc + credit, 0);

//   return {
//     success: true,
//     message: "Saldo balance has been calculated successfully",
//     saldoAwal: saldoAwal,
//     data: groupedReportData,
//     totalDebit: totalDebit,
//     totalCredit: totalCredit,
//     totalBalance: totalDebit - totalCredit,
//   };
// }

// OPTIMIZED SALDO BALANCE PER USER
async function saldoBalances(params) {
  try {
    // =========================
    // HELPER
    // =========================
    const toNumber = (val) => {
      const num = Number(val);
      return isNaN(num) ? 0 : num;
    };
   
    // =========================
    //  LOAD ACCOUNT ENTRIES
    // =========================
    const accountEntries = await dbSAPPS.AccountEntries.findAll({
      where: {
        client_id: { [Op.eq]: params.client_id },
        status: 1,
      },
      include: [
        { model: dbSAPPS.Client, as: "client" },
        { model: dbSAPPS.Item, as: "item" },
        {
          model: dbSAPPS.GimmickRequest,
          as: "gimmickreq",
          include: [
            {
              model: dbSAPPS.GimmickAllocation,
              as: "gimmickallocations",
              include: [
                {
                  model: dbSAPPS.Item,
                  as: "item",
                  attributes: ["id", "display_name", "price"],
                },
              ],
            },
          ],
        },
      ],
    });

    // =========================
    //  LOAD SALDO AWAL (ONCE)
    // =========================
    const saldoAwals = await dbSAPPS.SaldoAwalClient.findAll({
      where: { client_id: { [Op.eq]: params.client_id }, enabled: true },
      include: [
        { model: dbSAPPS.Client, as: "client", attributes: ["id", "display_name"] },
        { model: dbSAPPS.Item, as: "item", attributes: ["id", "display_name"] },
      ],
    });

    // Map saldo awal per client_id dan item_id
    const saldoMap = {};
    for (const s of saldoAwals) {
      const key = `${s.client_id}_${s.item_id}`;
      saldoMap[key] ??= { debit: 0, credit: 0 };

      const balance = toNumber(s.balance);

      if (s.type === "debit") saldoMap[key].debit += balance;
      if (s.type === "credit") saldoMap[key].credit += balance;
    }

    // =========================
    //  BUILD DATA
    // =========================
    const clientMap = {};
    const itemMap = {}; // Temporary map untuk tracking items per client

    for (const entry of accountEntries) {
      const clientId = entry.client_id;
      const itemId = entry.item_id;

      // Initialize client if not exists
      if (!clientMap[clientId]) {
        clientMap[clientId] = {
          client_id: clientId,
          client_name: entry.client?.display_name,
          initial_debit: 0,
          initial_credit: 0,
          debit: 0,
          credit: 0,
          balance: 0,
          items: [],
        };
        itemMap[clientId] = {};
      }

      const client = clientMap[clientId];

      // Initialize item if not exists
      if (!itemMap[clientId][itemId]) {
        const saldoKey = `${clientId}_${itemId}`;
        const saldoAwalDebit = saldoMap[saldoKey]?.debit || 0;
        const saldoAwalCredit = saldoMap[saldoKey]?.credit || 0;

        itemMap[clientId][itemId] = {
          item_id: itemId,
          item_name: entry.item?.display_name,
          initial_debit: saldoAwalDebit,
          initial_credit: saldoAwalCredit,
          debit: 0,
          credit: 0,
          balance: 0,
        };

        // Add to client's initial totals
        client.initial_debit += saldoAwalDebit;
        client.initial_credit += saldoAwalCredit;
      }

      const item = itemMap[clientId][itemId];

      // ===== DEBIT =====
      if (entry.type === "debit") {
        const val = toNumber(entry.item?.price) * toNumber(entry.quantity) * (toNumber(entry.kerja_sama) / 100);
        item.debit += val;
        client.debit += val;
      }

      // ===== CREDIT =====
      // RUMUS: HNA * QTY * KS / 100 * periode
      if (entry.type === "credit") {
        const val =
          toNumber(entry.item?.price) *
          toNumber(entry.quantity) *
          (toNumber(entry.kerja_sama) / 100) *
          toNumber(entry?.gimmickreq?.periode || 1);
        item.credit += val;
        client.credit += val;
      }
    }

    // Add items that only exist in saldo awal but not in accountEntries
    for (const s of saldoAwals) {
      const clientId = s.client_id;
      const itemId = s.item_id;
      const saldoKey = `${clientId}_${itemId}`;

      // Initialize client if not exists (for items with only saldo awal)
      if (!clientMap[clientId]) {
        // Get client name from saldo awal if available
        const clientName = s.client?.display_name || "Unknown Client";
        clientMap[clientId] = {
          client_id: clientId,
          client_name: clientName,
          initial_debit: 0,
          initial_credit: 0,
          debit: 0,
          credit: 0,
          balance: 0,
          items: [],
        };
        itemMap[clientId] = {};
      }

      // Add item if not already added
      if (!itemMap[clientId][itemId]) {
        const saldoAwalDebit = saldoMap[saldoKey]?.debit || 0;
        const saldoAwalCredit = saldoMap[saldoKey]?.credit || 0;

        itemMap[clientId][itemId] = {
          item_id: itemId,
          item_name: s.item?.display_name || "Unknown Item",
          initial_debit: saldoAwalDebit,
          initial_credit: saldoAwalCredit,
          debit: 0,
          credit: 0,
          balance: 0,
        };

        // Add to client's initial totals
        clientMap[clientId].initial_debit += saldoAwalDebit;
        clientMap[clientId].initial_credit += saldoAwalCredit;
      }
    }

    // Convert itemMap to items array and calculate item balance
    Object.entries(clientMap).forEach(([clientId, client]) => {
      if (itemMap[clientId]) {
        Object.values(itemMap[clientId]).forEach((item) => {
          // Calculate balance for each item
          item.balance =
            toNumber(item.initial_debit) +
            toNumber(item.debit) -
            (toNumber(item.initial_credit) + toNumber(item.credit));
          client.items.push(item);
        });
      }
    });

    // =========================
    //  HITUNG BALANCE (CLIENT TOTAL)
    // =========================
    Object.values(clientMap).forEach((client) => {
      client.balance =
        toNumber(client.initial_debit) +
        toNumber(client.debit) -
        (toNumber(client.initial_credit) + toNumber(client.credit));
    });

    // =========================
    //  SUMMARY (FINAL)
    // =========================
    const summaryByClient = {};

    Object.entries(clientMap).forEach(([clientId, client]) => {
      const summary = {
        client_name: client.client_name,
        initial_debit: toNumber(client.initial_debit),
        initial_credit: toNumber(client.initial_credit),
        debit: toNumber(client.debit),
        credit: toNumber(client.credit),
        balance: toNumber(client.balance),
      };

      summaryByClient[clientId] = summary;
    });

    // =========================
    //  RETURN (FINAL)
    // =========================
    return {
      success: true,
      message: "Report saldo balance have been loaded!",
      data: {
        clients: Object.values(clientMap),
        summary_by_client: Object.values(summaryByClient),
      },
    };
  } catch (err) {
    return {
      success: false,
      message: err.message,
    };
  }
}
// async function saldoBalances(params) {
//   console.log("PARAMS SB:", params);

//   const clientId = params.client_id;

//   /* =========================
//    * 1. GET TRANSACTIONS
//    * ========================= */
//   const accountEntries = await dbSAPPS.AccountEntries.findAll({
//     where: {
//       client_id: clientId,
//       status: 1,
//     },
//     include: [
//       {
//         model: dbSAPPS.SalesDistributor,
//         as: "sales",
//         include: {
//           model: dbSAPPS.Item,
//           as: "item",
//           attributes: ["id", "price", "display_name"],
//         },
//       },
//       {
//         model: dbSAPPS.GimmickRequest,
//         as: "gimmickreq",
//       },
//       {
//         model: dbSAPPS.Item,
//         as: "item",
//         attributes: ["id", "price", "display_name"],
//       },
//     ],
//     order: [["createdAt", "ASC"]],
//   });

//   /* =========================
//    * 2. GET SALDO AWAL
//    * ========================= */
//   const saldoAwal = await dbSAPPS.SaldoAwalClient.findAll({
//     where: {
//       client_id: clientId,
//       enabled: true,
//     },
//   });

//   /* =========================
//    * 3. BUILD SALDO AWAL MAP
//    * ========================= */
//   const saldoAwalMap = {};
//   for (const sa of saldoAwal) {
//     const itemId = sa.item_id;

//     if (!saldoAwalMap[itemId]) {
//       saldoAwalMap[itemId] = {
//         initialDebit: 0,
//         initialCredit: 0,
//       };
//     }

//     const amount = Number(sa.balance) || 0;

//     if (sa.type === "debit") {
//       saldoAwalMap[itemId].initialDebit += amount;
//     } else if (sa.type === "credit") {
//       saldoAwalMap[itemId].initialCredit += amount;
//     }
//   }

//   /* =========================
//    * 4. PROCESS TRANSACTIONS
//    * ========================= */
//   const products = {};

//   for (const entry of accountEntries) {
//     const item = entry.sales?.item || entry.item || null;

//     if (!item) continue;

//     const itemId = item.id;
//     const productName = item.display_name || "Unknown Product";

//     // init product
//     if (!products[productName]) {
//       const initial = saldoAwalMap[itemId] || {
//         initialDebit: 0,
//         initialCredit: 0,
//       };

//       products[productName] = {
//         item_id: itemId,
//         initialDebit: initial.initialDebit,
//         initialCredit: initial.initialCredit,
//         debit: 0,
//         credit: 0,
//         balance: 0,
//       };
//     }

//     const quantity = Number(entry.quantity) || 0;
//     const kerjaSama = Number(entry.kerja_sama) || 0;
//     const price = Number(item.price) || 0;
//     const periode = Number(entry?.gimmickreq?.periode) || 1;

//     const amount = price * quantity * (kerjaSama / 100) * periode;

//     if (entry.type === "debit") {
//       products[productName].debit += amount;
//     } else if (entry.type === "credit") {
//       products[productName].credit += amount;
//     }

//     // final balance per product
//     products[productName].balance =
//       products[productName].initialDebit +
//       products[productName].debit -
//       (products[productName].initialCredit + products[productName].credit);
//   }

//   /* =========================
//    * 5. FORMAT RESPONSE DATA
//    * ========================= */
//   const data = Object.entries(products).map(([productName, p]) => ({
//     product: productName,
//     item_id: p.item_id,
//     initial_debit: p.initialDebit,
//     initial_credit: p.initialCredit,
//     debit: p.debit,
//     credit: p.credit,
//     balance: p.balance,
//   }));

//   /* =========================
//    * 6. SUMMARY TOTAL
//    * ========================= */
//   const summary = {
//     initialDebit: 0,
//     initialCredit: 0,
//     debit: 0,
//     credit: 0,
//     finalBalance: 0,
//   };

//   for (const p of Object.values(products)) {
//     summary.initialDebit += p.initialDebit;
//     summary.initialCredit += p.initialCredit;
//     summary.debit += p.debit;
//     summary.credit += p.credit;
//   }

//   summary.finalBalance = summary.initialDebit + summary.debit - (summary.initialCredit + summary.credit);

//   /* =========================
//    * 7. RETURN
//    * ========================= */
//   return {
//     success: true,
//     message: "Saldo balance calculated successfully",
//     saldoAwal,
//     data,
//     summary,
//   };
// }
