const db = require('../models');
const { Op } = require('sequelize');

const generateAccountingDocumentNumber = async (companyId, documentType) => {
  const today = new Date();
  const year = today.getFullYear();
  const prefix = `AC${year}${documentType.substring(0, 2)}`;
  
  const lastDoc = await db.AccountingDocument.findOne({
    where: {
      company_id: companyId,
      document_number: { [Op.like]: `${prefix}%` }
    },
    order: [['document_number', 'DESC']]
  });

  let sequence = 1;
  if (lastDoc) {
    const lastSequence = parseInt(lastDoc.document_number.slice(-4)) || 0;
    sequence = lastSequence + 1;
  }

  return `${prefix}${sequence.toString().padStart(4, '0')}`;
};

const getFiscalPeriod = async (companyId, date) => {
  const fiscalPeriod = await db.FiscalPeriod.findOne({
    where: {
      start_date: { [Op.lte]: date },
      end_date: { [Op.gte]: date }
    },
    include: [{
      model: db.FiscalYear,
      as: 'fiscalYear',
      where: { company_id: companyId, status: 'ACTIVE' },
      attributes: ['id', 'fiscal_year']
    }],
    order: [['start_date', 'DESC']]
  });

  return fiscalPeriod;
};

const getAccountGroupCandidates = (accountGroup) => {
  const key = String(accountGroup || '').toUpperCase();
  const aliases = {
    OUTPUT_TAX: ['OUTPUT_TAX', 'TAX_LIABILITIES', 'GST_OUTPUT', 'GST_PAYABLE'],
    INPUT_TAX: ['INPUT_TAX', 'TAX_ASSETS', 'GST_INPUT', 'GST_INPUT_CREDIT'],
    SALES_REVENUE: ['SALES_REVENUE', 'SALES', 'REVENUE'],
    COST_OF_GOODS_SOLD: ['COST_OF_GOODS_SOLD', 'COGS', 'EXPENSES'],
    EXPENSE: ['EXPENSE', 'OPEX', 'EXPENSES'],
    INVENTORY: ['INVENTORY', 'STOCK', 'CURRENT_ASSETS'],
    INVENTORY_DIFFERENCE: ['INVENTORY_DIFFERENCE', 'INVENTORY_ADJUSTMENT', 'EXPENSES'],
    WORK_IN_PROGRESS: ['WORK_IN_PROGRESS', 'WIP', 'CURRENT_ASSETS']
  };
  return aliases[key] || [key];
};

const getDefaultGLAccount = async (companyId, accountGroup) => {
  const candidates = getAccountGroupCandidates(accountGroup);

  // Try exact/alias account-group matches first (preferred)
  for (const groupCode of candidates) {
    // eslint-disable-next-line no-await-in-loop
    const account = await db.GLAccount.findOne({
      where: {
        company_id: companyId,
        account_group: groupCode,
        status: 'ACTIVE',
        allows_posting: true
      },
      order: [['id', 'ASC']]
    });
    if (account) return account;
  }

  // Last fallback for tax groups by account name pattern (for legacy masters)
  const isOutputTaxLookup = String(accountGroup).toUpperCase() === 'OUTPUT_TAX';
  const isInputTaxLookup = String(accountGroup).toUpperCase() === 'INPUT_TAX';
  if (isOutputTaxLookup || isInputTaxLookup) {
    const taxNamePatterns = isOutputTaxLookup
      ? [{ [Op.like]: '%GST Payable%' }, { [Op.like]: '%Output Tax%' }]
      : [{ [Op.like]: '%GST Input%' }, { [Op.like]: '%Input Tax%' }];
    const taxAccount = await db.GLAccount.findOne({
      where: {
        company_id: companyId,
        status: 'ACTIVE',
        allows_posting: true,
        account_name: { [Op.or]: taxNamePatterns }
      },
      order: [['id', 'ASC']]
    });
    if (taxAccount) return taxAccount;
  }

  throw new Error(`Default GL account not found for account group: ${accountGroup}`);
};

const getCashBankGLAccount = async (companyId, paymentMode) => {
  const mode = String(paymentMode || '').toUpperCase();
  const preferredGroups = mode === 'CASH'
    ? ['CASH', 'CASH_IN_HAND', 'CASH_AND_BANK']
    : ['BANK', 'BANK_ACCOUNT', 'CASH_AND_BANK', 'CASH', 'CASH_IN_HAND'];

  for (const group of preferredGroups) {
    // eslint-disable-next-line no-await-in-loop
    const account = await db.GLAccount.findOne({
      where: {
        company_id: companyId,
        account_group: group,
        status: 'ACTIVE',
        allows_posting: true
      },
      order: [['id', 'ASC']]
    });
    if (account) return account;
  }

  const namePatterns = mode === 'CASH'
    ? [{ [Op.like]: '%Cash%' }]
    : [{ [Op.like]: '%Bank%' }, { [Op.like]: '%Current Account%' }, { [Op.like]: '%UPI%' }, { [Op.like]: '%Cash%' }];

  const byName = await db.GLAccount.findOne({
    where: {
      company_id: companyId,
      status: 'ACTIVE',
      allows_posting: true,
      account_name: { [Op.or]: namePatterns }
    },
    order: [['id', 'ASC']]
  });
  if (byName) return byName;

  // Last fallback: any active posting ASSET account for cash/bank settlement.
  const anyAssetAccount = await db.GLAccount.findOne({
    where: {
      company_id: companyId,
      account_type: 'ASSET',
      status: 'ACTIVE',
      allows_posting: true
    },
    order: [['id', 'ASC']]
  });
  if (anyAssetAccount) return anyAssetAccount;

  throw new Error(`Cash/Bank GL account not configured for payment mode: ${mode}`);
};

const postGRNToGL = async (grn, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const fiscalPeriod = await getFiscalPeriod(grn.company_id, grn.grn_date);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the GRN date');
    }

    const documentNumber = await generateAccountingDocumentNumber(grn.company_id, 'GRN');
    
    const vendor = await db.BusinessPartner.findByPk(grn.vendor_id, {
      include: [{ model: db.GLAccount, as: 'glAccount' }]
    });

    if (!vendor || !vendor.gl_account_id) {
      throw new Error('Vendor GL account not configured');
    }

    const vendorGLAccount = await db.GLAccount.findByPk(vendor.gl_account_id);
    if (!vendorGLAccount) {
      throw new Error('Vendor GL account not found');
    }

    const inventoryGLAccount = await getDefaultGLAccount(grn.company_id, 'INVENTORY');

    const accountingDocument = await db.AccountingDocument.create({
      company_id: grn.company_id,
      plant_id: grn.plant_id,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: grn.grn_date,
      posting_date: grn.grn_date,
      document_type: 'GRN',
      reference_type: 'GoodsReceiptNote',
      reference_id: grn.id,
      reference_number: grn.grn_number,
      currency_id: grn.currency_id || null,
      exchange_rate: 1.0,
      total_debit: grn.total_amount,
      total_credit: grn.total_amount,
      remarks: `GL posting for GRN ${grn.grn_number}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    const items = grn.items || await db.GoodsReceiptNoteItem.findAll({
      where: { grn_id: grn.id },
      include: [{ model: db.Material, as: 'material' }]
    });

    let lineNumber = 1;

    for (const item of items) {
      const itemValue = parseFloat(item.quantity) * parseFloat(item.unit_price);
      
      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: inventoryGLAccount.id,
        cost_center_id: null,
        debit_amount: itemValue,
        credit_amount: 0,
        description: `Material: ${item.material?.material_number || 'N/A'} - GRN ${grn.grn_number}`
      }, { transaction });
    }

    await db.AccountingDocumentItem.create({
      document_id: accountingDocument.id,
      line_number: lineNumber,
      gl_account_id: vendorGLAccount.id,
      cost_center_id: null,
      debit_amount: 0,
      credit_amount: grn.total_amount,
      description: `Vendor Payable: ${vendor.name} - GRN ${grn.grn_number}`
    }, { transaction });

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

const postVendorInvoiceToGL = async (invoice, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const fiscalPeriod = await getFiscalPeriod(invoice.company_id, invoice.invoice_date);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the invoice date');
    }

    const documentNumber = await generateAccountingDocumentNumber(invoice.company_id, 'VENDOR_INVOICE');
    
    const vendor = await db.BusinessPartner.findByPk(invoice.vendor_id, {
      include: [{ model: db.GLAccount, as: 'glAccount' }]
    });

    if (!vendor || !vendor.gl_account_id) {
      throw new Error('Vendor GL account not configured');
    }

    const vendorGLAccount = await db.GLAccount.findByPk(vendor.gl_account_id);
    if (!vendorGLAccount) {
      throw new Error('Vendor GL account not found');
    }

    const inputTaxGLAccount = await getDefaultGLAccount(invoice.company_id, 'INPUT_TAX');
    const expenseGLAccount = await getDefaultGLAccount(invoice.company_id, 'EXPENSE');

    const accountingDocument = await db.AccountingDocument.create({
      company_id: invoice.company_id,
      plant_id: invoice.plant_id || null,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: invoice.invoice_date,
      posting_date: invoice.invoice_date,
      document_type: 'VENDOR_INVOICE',
      reference_type: 'VendorInvoice',
      reference_id: invoice.id,
      reference_number: invoice.invoice_number,
      currency_id: invoice.currency_id || null,
      exchange_rate: 1.0,
      total_debit: invoice.net_payable || invoice.total_amount,
      total_credit: invoice.net_payable || invoice.total_amount,
      remarks: `GL posting for Vendor Invoice ${invoice.invoice_number}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    const items = invoice.items || await db.VendorInvoiceItem.findAll({
      where: { invoice_id: invoice.id },
      include: [{ model: db.Material, as: 'material' }]
    });

    let lineNumber = 1;
    let totalExpense = 0;

    for (const item of items) {
      const itemValue = parseFloat(item.quantity) * parseFloat(item.unit_price);
      totalExpense += itemValue;

      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: expenseGLAccount.id,
        cost_center_id: null,
        debit_amount: itemValue,
        credit_amount: 0,
        description: `Expense: ${item.material?.material_number || 'N/A'} - Invoice ${invoice.invoice_number}`
      }, { transaction });
    }

    const totalTax = (parseFloat(invoice.cgst_amount || 0) + 
                      parseFloat(invoice.sgst_amount || 0) + 
                      parseFloat(invoice.igst_amount || 0) + 
                      parseFloat(invoice.cess_amount || 0));

    if (totalTax > 0) {
      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: inputTaxGLAccount.id,
        cost_center_id: null,
        debit_amount: totalTax,
        credit_amount: 0,
        description: `Input Tax - Invoice ${invoice.invoice_number}`
      }, { transaction });
    }

    const totalPayable = invoice.net_payable || invoice.total_amount;

    await db.AccountingDocumentItem.create({
      document_id: accountingDocument.id,
      line_number: lineNumber,
      gl_account_id: vendorGLAccount.id,
      cost_center_id: null,
      debit_amount: 0,
      credit_amount: totalPayable,
      description: `Vendor Payable: ${vendor.name} - Invoice ${invoice.invoice_number}`
    }, { transaction });

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

const postSalesInvoiceToGL = async (invoice, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const fiscalPeriod = await getFiscalPeriod(invoice.company_id, invoice.invoice_date);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the invoice date');
    }

    const documentNumber = await generateAccountingDocumentNumber(invoice.company_id, 'SALES_INVOICE');
    
    const customer = await db.BusinessPartner.findByPk(invoice.customer_id, {
      include: [{ model: db.GLAccount, as: 'glAccount' }]
    });

    if (!customer || !customer.gl_account_id) {
      throw new Error('Customer GL account not configured');
    }

    const customerGLAccount = await db.GLAccount.findByPk(customer.gl_account_id);
    if (!customerGLAccount) {
      throw new Error('Customer GL account not found');
    }

    const outputTaxGLAccount = await getDefaultGLAccount(invoice.company_id, 'OUTPUT_TAX');
    const salesRevenueGLAccount = await getDefaultGLAccount(invoice.company_id, 'SALES_REVENUE');
    const inventoryGLAccount = await getDefaultGLAccount(invoice.company_id, 'INVENTORY');
    const costOfGoodsSoldGLAccount = await getDefaultGLAccount(invoice.company_id, 'COST_OF_GOODS_SOLD');

    const accountingDocument = await db.AccountingDocument.create({
      company_id: invoice.company_id,
      plant_id: invoice.plant_id || null,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: invoice.invoice_date,
      posting_date: invoice.invoice_date,
      document_type: 'SALES_INVOICE',
      reference_type: 'SalesInvoice',
      reference_id: invoice.id,
      reference_number: invoice.invoice_number,
      currency_id: invoice.currency_id || null,
      exchange_rate: 1.0,
      total_debit: invoice.total_amount,
      total_credit: invoice.total_amount,
      remarks: `GL posting for Sales Invoice ${invoice.invoice_number}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    const items = invoice.items || await db.SalesInvoiceItem.findAll({
      where: { invoice_id: invoice.id },
      include: [{ model: db.Material, as: 'material' }]
    });

    let lineNumber = 1;

    for (const item of items) {
      const salesValue = parseFloat(item.quantity) * parseFloat(item.unit_price);

      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: customerGLAccount.id,
        cost_center_id: null,
        debit_amount: salesValue,
        credit_amount: 0,
        description: `Customer Receivable - Invoice ${invoice.invoice_number}`
      }, { transaction });

      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: salesRevenueGLAccount.id,
        cost_center_id: null,
        debit_amount: 0,
        credit_amount: salesValue,
        description: `Sales Revenue: ${item.material?.material_number || 'N/A'} - Invoice ${invoice.invoice_number}`
      }, { transaction });
    }

    const totalTax = (parseFloat(invoice.cgst_amount || 0) + 
                      parseFloat(invoice.sgst_amount || 0) + 
                      parseFloat(invoice.igst_amount || 0) + 
                      parseFloat(invoice.cess_amount || 0));

    if (totalTax > 0) {
      await db.AccountingDocumentItem.create({
        document_id: accountingDocument.id,
        line_number: lineNumber++,
        gl_account_id: outputTaxGLAccount.id,
        cost_center_id: null,
        debit_amount: 0,
        credit_amount: totalTax,
        description: `Output Tax - Invoice ${invoice.invoice_number}`
      }, { transaction });
    }

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

const postInventoryMovementToGL = async (movement, movementType, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const movementDate = movement.movement_date || movement.receipt_date || movement.issue_date || movement.transfer_date || movement.adjustment_date;
    const fiscalPeriod = await getFiscalPeriod(movement.company_id, movementDate);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the movement date');
    }

    const documentNumber = await generateAccountingDocumentNumber(movement.company_id, movementType);
    
    const inventoryGLAccount = await getDefaultGLAccount(movement.company_id, 'INVENTORY');
    const inventoryDifferenceGLAccount = await getDefaultGLAccount(movement.company_id, 'INVENTORY_DIFFERENCE');

    const referenceNumber = movement.grn_number || movement.movement_number || movement.gin_number || movement.stt_number || movement.psa_number;

    const accountingDocument = await db.AccountingDocument.create({
      company_id: movement.company_id,
      plant_id: movement.plant_id || null,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: movementDate,
      posting_date: movementDate,
      document_type: movementType,
      reference_type: movement.constructor.name,
      reference_id: movement.id,
      reference_number: referenceNumber,
      currency_id: null,
      exchange_rate: 1.0,
      total_debit: 0,
      total_credit: 0,
      remarks: `GL posting for ${movementType} ${referenceNumber || movement.id}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    let lineNumber = 1;
    let totalDebit = 0;
    let totalCredit = 0;

    let items = [];
    if (movementType === 'GOODS_RECEIPT') {
      items = movement.items || await db.InventoryGoodsReceiptItem.findAll({
        where: { movement_id: movement.id },
        include: [{ model: db.Material, as: 'material' }]
      });
    } else if (movementType === 'GOODS_ISSUE') {
      items = movement.items || await db.InventoryGoodsIssueItem.findAll({
        where: { movement_id: movement.id },
        include: [{ model: db.Material, as: 'material' }]
      });
    }

    for (const item of items) {
      const itemValue = parseFloat(item.quantity || 0) * parseFloat(item.unit_price || item.unit_cost || 0);
      
      if (movementType === 'GOODS_RECEIPT' && itemValue > 0) {
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: inventoryGLAccount.id,
          cost_center_id: null,
          debit_amount: itemValue,
          credit_amount: 0,
          description: `Inventory Receipt: ${item.material?.material_number || 'N/A'}`
        }, { transaction });
        totalDebit += itemValue;
      } else if (movementType === 'GOODS_ISSUE' && itemValue > 0) {
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: inventoryGLAccount.id,
          cost_center_id: null,
          debit_amount: 0,
          credit_amount: itemValue,
          description: `Inventory Issue: ${item.material?.material_number || 'N/A'}`
        }, { transaction });
        totalCredit += itemValue;
      }
    }

    if (movementType === 'STOCK_ADJUSTMENT') {
      const adjustmentItems = await db.InventoryStockAdjustmentItem.findAll({
        where: { adjustment_id: movement.id },
        include: [{ model: db.Material, as: 'material' }]
      });

      for (const item of adjustmentItems) {
        const adjustmentQty = parseFloat(item.adjustment_quantity || 0);
        const itemValue = Math.abs(adjustmentQty) * parseFloat(item.unit_price || 0);
        
        if (adjustmentQty > 0) {
          await db.AccountingDocumentItem.create({
            document_id: accountingDocument.id,
            line_number: lineNumber++,
            gl_account_id: inventoryGLAccount.id,
            cost_center_id: null,
            debit_amount: itemValue,
            credit_amount: 0,
            description: `Inventory Adjustment Increase: ${item.material?.material_number || 'N/A'}`
          }, { transaction });
          totalDebit += itemValue;
        } else if (adjustmentQty < 0) {
          await db.AccountingDocumentItem.create({
            document_id: accountingDocument.id,
            line_number: lineNumber++,
            gl_account_id: inventoryGLAccount.id,
            cost_center_id: null,
            debit_amount: 0,
            credit_amount: itemValue,
            description: `Inventory Adjustment Decrease: ${item.material?.material_number || 'N/A'}`
          }, { transaction });
          totalCredit += itemValue;
        }

        const adjustmentAmount = parseFloat(item.adjustment_amount || 0);
        if (adjustmentAmount !== 0 && Math.abs(adjustmentAmount - itemValue) > 0.01) {
          const diffValue = Math.abs(adjustmentAmount - itemValue);
          await db.AccountingDocumentItem.create({
            document_id: accountingDocument.id,
            line_number: lineNumber++,
            gl_account_id: inventoryDifferenceGLAccount.id,
            cost_center_id: null,
            debit_amount: (adjustmentAmount - itemValue) > 0 ? diffValue : 0,
            credit_amount: (adjustmentAmount - itemValue) < 0 ? diffValue : 0,
            description: `Inventory Value Difference: ${item.material?.material_number || 'N/A'}`
          }, { transaction });
          
          if ((adjustmentAmount - itemValue) > 0) {
            totalDebit += diffValue;
          } else {
            totalCredit += diffValue;
          }
        }
      }
    }

    await accountingDocument.update({
      total_debit: totalDebit,
      total_credit: totalCredit
    }, { transaction });

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

/**
 * Post Delivery to GL
 * Creates accounting entries for delivery:
 * - Debit: Cost of Goods Sold (COGS)
 * - Credit: Inventory
 */
const postDeliveryToGL = async (delivery, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const fiscalPeriod = await getFiscalPeriod(delivery.company_id, delivery.delivery_date);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the delivery date');
    }

    const documentNumber = await generateAccountingDocumentNumber(delivery.company_id, 'DELIVERY');
    
    const cogsGLAccount = await getDefaultGLAccount(delivery.company_id, 'COST_OF_GOODS_SOLD');
    const inventoryGLAccount = await getDefaultGLAccount(delivery.company_id, 'INVENTORY');

    const accountingDocument = await db.AccountingDocument.create({
      company_id: delivery.company_id,
      plant_id: delivery.plant_id,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: delivery.delivery_date,
      posting_date: delivery.delivery_date,
      document_type: 'DELIVERY',
      reference_type: 'Delivery',
      reference_id: delivery.id,
      reference_number: delivery.delivery_number,
      currency_id: null,
      exchange_rate: 1.0,
      total_debit: 0,
      total_credit: 0,
      remarks: `GL posting for Delivery ${delivery.delivery_number}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    const items = delivery.items || await db.DeliveryItem.findAll({
      where: { delivery_id: delivery.id },
      include: [{ model: db.Material, as: 'material' }]
    });

    let lineNumber = 1;
    let totalDebit = 0;
    let totalCredit = 0;

    for (const item of items) {
      const quantity = parseFloat(item.quantity || 0);
      const unitCost = parseFloat(item.unit_price || 0) || 0;
      const totalCost = quantity * unitCost;

      if (totalCost > 0) {
        // Debit: Cost of Goods Sold
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: cogsGLAccount.id,
          cost_center_id: null,
          debit_amount: totalCost,
          credit_amount: 0,
          description: `COGS: ${item.material?.material_number || 'N/A'} - Delivery ${delivery.delivery_number}`
        }, { transaction });

        // Credit: Inventory
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: inventoryGLAccount.id,
          cost_center_id: null,
          debit_amount: 0,
          credit_amount: totalCost,
          description: `Inventory: ${item.material?.material_number || 'N/A'} - Delivery ${delivery.delivery_number}`
        }, { transaction });

        totalDebit += totalCost;
        totalCredit += totalCost;
      }
    }

    await accountingDocument.update({
      total_debit: totalDebit,
      total_credit: totalCredit
    }, { transaction });

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

/**
 * Post Material Issue to GL
 * Creates accounting entries for material issue to production:
 * - Debit: Work in Progress (WIP)
 * - Credit: Inventory
 */
const postMaterialIssueToGL = async (materialIssue, userId) => {
  const transaction = await db.sequelize.transaction();

  try {
    const issueDate = materialIssue.issue_date || new Date().toISOString().split('T')[0];
    const fiscalPeriod = await getFiscalPeriod(materialIssue.company_id, issueDate);
    if (!fiscalPeriod) {
      throw new Error('Fiscal period not found for the issue date');
    }

    const documentNumber = await generateAccountingDocumentNumber(materialIssue.company_id, 'MATERIAL_ISSUE');
    
    const wipGLAccount = await getDefaultGLAccount(materialIssue.company_id, 'WORK_IN_PROGRESS');
    const inventoryGLAccount = await getDefaultGLAccount(materialIssue.company_id, 'INVENTORY');

    const accountingDocument = await db.AccountingDocument.create({
      company_id: materialIssue.company_id,
      plant_id: materialIssue.plant_id,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: issueDate,
      posting_date: issueDate,
      document_type: 'MATERIAL_ISSUE',
      reference_type: 'MaterialIssue',
      reference_id: materialIssue.id,
      reference_number: materialIssue.issue_number,
      currency_id: null,
      exchange_rate: 1.0,
      total_debit: 0,
      total_credit: 0,
      remarks: `GL posting for Material Issue ${materialIssue.issue_number}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    const items = materialIssue.items || await db.MaterialIssueItem.findAll({
      where: { issue_id: materialIssue.id },
      include: [{ model: db.Material, as: 'material' }]
    });

    let lineNumber = 1;
    let totalDebit = 0;
    let totalCredit = 0;

    for (const item of items) {
      const quantity = parseFloat(item.quantity || 0);
      const unitCost = parseFloat(item.unit_price || 0) || 0;
      const totalCost = quantity * unitCost;

      if (totalCost > 0) {
        // Debit: Work in Progress
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: wipGLAccount.id,
          cost_center_id: null,
          debit_amount: totalCost,
          credit_amount: 0,
          description: `WIP: ${item.material?.material_number || 'N/A'} - Material Issue ${materialIssue.issue_number}`
        }, { transaction });

        // Credit: Inventory
        await db.AccountingDocumentItem.create({
          document_id: accountingDocument.id,
          line_number: lineNumber++,
          gl_account_id: inventoryGLAccount.id,
          cost_center_id: null,
          debit_amount: 0,
          credit_amount: totalCost,
          description: `Inventory: ${item.material?.material_number || 'N/A'} - Material Issue ${materialIssue.issue_number}`
        }, { transaction });

        totalDebit += totalCost;
        totalCredit += totalCost;
      }
    }

    await accountingDocument.update({
      total_debit: totalDebit,
      total_credit: totalCredit
    }, { transaction });

    await transaction.commit();
    return accountingDocument;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

const postCustomerReceiptToGL = async (receipt, userId) => {
  const transaction = await db.sequelize.transaction();
  try {
    const postingDate = receipt.receipt_date || new Date();
    const fiscalPeriod = await getFiscalPeriod(receipt.company_id, postingDate);
    if (!fiscalPeriod) throw new Error('Fiscal period not found for receipt date');

    const customer = await db.BusinessPartner.findByPk(receipt.customer_id);
    if (!customer || !customer.gl_account_id) throw new Error('Customer GL account not configured');
    const customerGL = await db.GLAccount.findByPk(customer.gl_account_id);
    if (!customerGL) throw new Error('Customer GL account not found');

    const cashBankGL = await getCashBankGLAccount(receipt.company_id, receipt.payment_mode);
    const documentNumber = await generateAccountingDocumentNumber(receipt.company_id, 'CUSTOMER_RECEIPT');
    const amount = parseFloat(receipt.paid_amount || 0);

    const doc = await db.AccountingDocument.create({
      company_id: receipt.company_id,
      plant_id: receipt.plant_id || null,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: postingDate,
      posting_date: postingDate,
      document_type: 'JOURNAL_ENTRY',
      reference_type: 'CustomerReceipt',
      reference_id: receipt.id,
      reference_number: receipt.receipt_no,
      exchange_rate: 1.0,
      total_debit: amount,
      total_credit: amount,
      remarks: `GL posting for Customer Receipt ${receipt.receipt_no}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    await db.AccountingDocumentItem.create({
      document_id: doc.id,
      line_number: 1,
      gl_account_id: cashBankGL.id,
      debit_amount: amount,
      credit_amount: 0,
      description: `Cash/Bank receipt from customer - ${customer.name}`
    }, { transaction });

    await db.AccountingDocumentItem.create({
      document_id: doc.id,
      line_number: 2,
      gl_account_id: customerGL.id,
      debit_amount: 0,
      credit_amount: amount,
      description: `Customer settlement - ${customer.name}`
    }, { transaction });

    await transaction.commit();
    return doc;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

const postVendorPaymentToGL = async (payment, userId) => {
  const transaction = await db.sequelize.transaction();
  try {
    const postingDate = payment.payment_date || new Date();
    const fiscalPeriod = await getFiscalPeriod(payment.company_id, postingDate);
    if (!fiscalPeriod) throw new Error('Fiscal period not found for payment date');

    const vendor = await db.BusinessPartner.findByPk(payment.vendor_id);
    if (!vendor || !vendor.gl_account_id) throw new Error('Vendor GL account not configured');
    const vendorGL = await db.GLAccount.findByPk(vendor.gl_account_id);
    if (!vendorGL) throw new Error('Vendor GL account not found');

    const cashBankGL = await getCashBankGLAccount(payment.company_id, payment.payment_mode);
    const documentNumber = await generateAccountingDocumentNumber(payment.company_id, 'VENDOR_PAYMENT');
    const amount = parseFloat(payment.paid_amount || 0);

    const doc = await db.AccountingDocument.create({
      company_id: payment.company_id,
      plant_id: payment.plant_id || null,
      fiscal_year_id: fiscalPeriod.fiscalYear.id,
      fiscal_period_id: fiscalPeriod.id,
      document_number: documentNumber,
      document_date: postingDate,
      posting_date: postingDate,
      document_type: 'JOURNAL_ENTRY',
      reference_type: 'VendorPayment',
      reference_id: payment.id,
      reference_number: payment.payment_no,
      exchange_rate: 1.0,
      total_debit: amount,
      total_credit: amount,
      remarks: `GL posting for Vendor Payment ${payment.payment_no}`,
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      created_by: userId
    }, { transaction });

    await db.AccountingDocumentItem.create({
      document_id: doc.id,
      line_number: 1,
      gl_account_id: vendorGL.id,
      debit_amount: amount,
      credit_amount: 0,
      description: `Vendor payment settlement - ${vendor.name}`
    }, { transaction });

    await db.AccountingDocumentItem.create({
      document_id: doc.id,
      line_number: 2,
      gl_account_id: cashBankGL.id,
      debit_amount: 0,
      credit_amount: amount,
      description: `Cash/Bank paid to vendor - ${vendor.name}`
    }, { transaction });

    await transaction.commit();
    return doc;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

module.exports = {
  postGRNToGL,
  postVendorInvoiceToGL,
  postSalesInvoiceToGL,
  postInventoryMovementToGL,
  postDeliveryToGL,
  postMaterialIssueToGL,
  postCustomerReceiptToGL,
  postVendorPaymentToGL
};

