const db = require('../models');
const { sendResponse, sendError } = require('../utils/response');
const asyncHandler = require('../middlewares/asyncHandler');
const { getPagination, getPagingData } = require('../utils/pagination');
const { Op } = require('sequelize');
const glPostingService = require('../services/glPostingService');
const { generateNextNumber } = require('../services/numberFormatService');

const generateInvoiceNumber = async (companyId, fiscalYearId = null, transaction = null) => {
  try {
    return await generateNextNumber(companyId, 'VENDOR_INVOICE', fiscalYearId, transaction);
  } catch (err) {
    console.warn('Vendor invoice number from format master failed, using fallback:', err.message);
    const today = new Date();
    const year = today.getFullYear();
    const prefix = `PINV${year}`;
    const lastInvoice = await db.VendorInvoice.findOne({
      where: { company_id: companyId, invoice_number: { [Op.like]: `${prefix}%` } },
      order: [['invoice_number', 'DESC']],
      ...(transaction ? { transaction } : {})
    });
    let sequence = 1;
    if (lastInvoice) {
      const lastSequence = parseInt(lastInvoice.invoice_number.slice(-4)) || 0;
      sequence = lastSequence + 1;
    }
    return `${prefix}${sequence.toString().padStart(4, '0')}`;
  }
};

exports.getAll = asyncHandler(async (req, res) => {
  const { page = 1, limit = 25, search, status, companyId, plantId, fiscalYearId, vendorId, grnId } = req.query;
  const { limit: limitNum, offset } = getPagination(page, limit);

  const where = {};
  
  if (companyId) {
    where.company_id = companyId;
  } else if (req.user && !req.user.isSuperAdmin) {
    where.company_id = req.user.companyId;
  }

  if (plantId) {
    where.plant_id = plantId;
  }

  if (fiscalYearId) {
    where.fiscal_year_id = fiscalYearId;
  }

  if (vendorId) {
    where.vendor_id = vendorId;
  }

  if (grnId) {
    where.grn_id = grnId;
  }

  if (status) {
    where.status = status;
  }

  if (search) {
    where[Op.or] = [
      { invoice_number: { [Op.like]: `%${search}%` } },
      { vendor_invoice_number: { [Op.like]: `%${search}%` } }
    ];
  }

  const records = await db.VendorInvoice.findAndCountAll({
    where,
    include: [
      { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
      { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
      { model: db.BusinessPartner, as: 'vendor', attributes: ['id', 'bp_number', 'name', 'legal_name'] },
      { model: db.GoodsReceiptNote, as: 'goodsReceiptNote', attributes: ['id', 'grn_number'] },
      { model: db.PurchaseOrder, as: 'purchaseOrder', attributes: ['id', 'po_number'] }
    ],
    limit: limitNum,
    offset,
    order: [['invoice_date', 'DESC'], ['id', 'DESC']]
  });

  const response = getPagingData(records, page, limitNum);
  sendResponse(res, 200, true, 'Vendor Invoices retrieved successfully', response);
});

exports.getById = asyncHandler(async (req, res) => {
  const { id } = req.params;

  const invoice = await db.VendorInvoice.findByPk(id, {
    include: [
      { model: db.Company, as: 'company' },
      { model: db.Plant, as: 'plant' },
      { model: db.FiscalYear, as: 'fiscalYear' },
      { model: db.BusinessPartner, as: 'vendor' },
      { model: db.GoodsReceiptNote, as: 'goodsReceiptNote' },
      { model: db.PurchaseOrder, as: 'purchaseOrder' },
      { model: db.Currency, as: 'currency' },
      {
        model: db.VendorInvoiceItem,
        as: 'items',
        include: [
          { model: db.Material, as: 'material' },
          { model: db.UOM, as: 'uom' },
          { model: db.GoodsReceiptNoteItem, as: 'grnItem' }
        ],
        order: [['line_number', 'ASC']]
      }
    ]
  });

  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  sendResponse(res, 200, true, 'Vendor Invoice retrieved successfully', invoice);
});

exports.create = asyncHandler(async (req, res) => {
  const data = req.body;
  const userId = req.user?.userId || null;
  const companyId = req.user?.companyId;

  if (!companyId) {
    return sendError(res, 'Company ID is required', 400);
  }

  if (!data.plant_id || !data.fiscal_year_id || !data.vendor_id) {
    return sendError(res, 'Plant ID, Fiscal Year ID, and Vendor ID are required', 400);
  }

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

  try {
    if (!data.invoice_number) {
      data.invoice_number = await generateInvoiceNumber(companyId, data.fiscal_year_id, transaction);
    }

    data.company_id = companyId;
    data.created_by = userId;
    data.updated_by = userId;

    const { items, ...invoiceData } = data;
    
    let subTotal = 0;
    let discountAmount = 0;
    let cgstAmount = 0;
    let sgstAmount = 0;
    let igstAmount = 0;
    let cessAmount = 0;

    if (items && Array.isArray(items)) {
      items.forEach(item => {
        const quantity = parseFloat(item.quantity) || 0;
        const unitPrice = parseFloat(item.unit_price) || 0;
        const discountPercent = parseFloat(item.discount_percent) || 0;

        const lineTotal = quantity * unitPrice;
        const lineDiscount = (lineTotal * discountPercent) / 100;
        const taxableAmount = lineTotal - lineDiscount;

        const cgstRate = parseFloat(item.cgst_rate) || 0;
        const sgstRate = parseFloat(item.sgst_rate) || 0;
        const igstRate = parseFloat(item.igst_rate) || 0;
        const cessRate = parseFloat(item.cess_rate) || 0;

        const lineCGST = (taxableAmount * cgstRate) / 100;
        const lineSGST = (taxableAmount * sgstRate) / 100;
        const lineIGST = (taxableAmount * igstRate) / 100;
        const lineCess = (taxableAmount * cessRate) / 100;

        subTotal += lineTotal;
        discountAmount += lineDiscount;
        cgstAmount += lineCGST;
        sgstAmount += lineSGST;
        igstAmount += lineIGST;
        cessAmount += lineCess;
      });
    }

    invoiceData.sub_total = subTotal;
    invoiceData.discount_amount = discountAmount;
    invoiceData.cgst_amount = cgstAmount;
    invoiceData.sgst_amount = sgstAmount;
    invoiceData.igst_amount = igstAmount;
    invoiceData.cess_amount = cessAmount;
    invoiceData.total_tax_amount = cgstAmount + sgstAmount + igstAmount + cessAmount;
    invoiceData.total_amount = subTotal - discountAmount + invoiceData.total_tax_amount;
    invoiceData.net_payable = invoiceData.total_amount - (parseFloat(invoiceData.tds_amount) || 0);

    const invoice = await db.VendorInvoice.create(invoiceData, { transaction });

    if (items && Array.isArray(items)) {
      const invoiceItems = items.map((item, index) => {
        const quantity = parseFloat(item.quantity) || 0;
        const unitPrice = parseFloat(item.unit_price) || 0;
        const discountPercent = parseFloat(item.discount_percent) || 0;

        const lineTotal = quantity * unitPrice;
        const lineDiscount = (lineTotal * discountPercent) / 100;
        const taxableAmount = lineTotal - lineDiscount;

        const cgstRate = parseFloat(item.cgst_rate) || 0;
        const sgstRate = parseFloat(item.sgst_rate) || 0;
        const igstRate = parseFloat(item.igst_rate) || 0;
        const cessRate = parseFloat(item.cess_rate) || 0;

        const lineCGST = (taxableAmount * cgstRate) / 100;
        const lineSGST = (taxableAmount * sgstRate) / 100;
        const lineIGST = (taxableAmount * igstRate) / 100;
        const lineCess = (taxableAmount * cessRate) / 100;

        return {
          invoice_id: invoice.id,
          grn_item_id: item.grn_item_id,
          line_number: index + 1,
          material_id: item.material_id,
          description: item.description,
          quantity: quantity,
          uom_id: item.uom_id,
          unit_price: unitPrice,
          discount_percent: discountPercent,
          discount_amount: lineDiscount,
          taxable_amount: taxableAmount,
          cgst_rate: cgstRate,
          cgst_amount: lineCGST,
          sgst_rate: sgstRate,
          sgst_amount: lineSGST,
          igst_rate: igstRate,
          igst_amount: lineIGST,
          cess_rate: cessRate,
          cess_amount: lineCess,
          line_total: taxableAmount + lineCGST + lineSGST + lineIGST + lineCess,
          hsn_code: item.hsn_code,
          remarks: item.remarks
        };
      });

      await db.VendorInvoiceItem.bulkCreate(invoiceItems, { transaction });
    }

    await transaction.commit();

    const createdInvoice = await db.VendorInvoice.findByPk(invoice.id, {
      include: [
        { model: db.VendorInvoiceItem, as: 'items', include: [{ model: db.Material, as: 'material' }, { model: db.UOM, as: 'uom' }] }
      ]
    });

    sendResponse(res, 201, true, 'Vendor Invoice created successfully', createdInvoice);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.update = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const data = req.body;
  const userId = req.user?.userId || null;

  const invoice = await db.VendorInvoice.findByPk(id);
  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  if (invoice.status !== 'DRAFT') {
    return sendError(res, 'Only DRAFT Vendor Invoices can be updated', 400);
  }

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

  try {
    const { items, ...invoiceData } = data;
    invoiceData.updated_by = userId;

    let subTotal = 0;
    let discountAmount = 0;
    let cgstAmount = 0;
    let sgstAmount = 0;
    let igstAmount = 0;
    let cessAmount = 0;

    if (items && Array.isArray(items)) {
      items.forEach(item => {
        const quantity = parseFloat(item.quantity) || 0;
        const unitPrice = parseFloat(item.unit_price) || 0;
        const discountPercent = parseFloat(item.discount_percent) || 0;

        const lineTotal = quantity * unitPrice;
        const lineDiscount = (lineTotal * discountPercent) / 100;
        const taxableAmount = lineTotal - lineDiscount;

        const cgstRate = parseFloat(item.cgst_rate) || 0;
        const sgstRate = parseFloat(item.sgst_rate) || 0;
        const igstRate = parseFloat(item.igst_rate) || 0;
        const cessRate = parseFloat(item.cess_rate) || 0;

        const lineCGST = (taxableAmount * cgstRate) / 100;
        const lineSGST = (taxableAmount * sgstRate) / 100;
        const lineIGST = (taxableAmount * igstRate) / 100;
        const lineCess = (taxableAmount * cessRate) / 100;

        subTotal += lineTotal;
        discountAmount += lineDiscount;
        cgstAmount += lineCGST;
        sgstAmount += lineSGST;
        igstAmount += lineIGST;
        cessAmount += lineCess;
      });
    }

    invoiceData.sub_total = subTotal;
    invoiceData.discount_amount = discountAmount;
    invoiceData.cgst_amount = cgstAmount;
    invoiceData.sgst_amount = sgstAmount;
    invoiceData.igst_amount = igstAmount;
    invoiceData.cess_amount = cessAmount;
    invoiceData.total_tax_amount = cgstAmount + sgstAmount + igstAmount + cessAmount;
    invoiceData.total_amount = subTotal - discountAmount + invoiceData.total_tax_amount;
    invoiceData.net_payable = invoiceData.total_amount - (parseFloat(invoiceData.tds_amount) || 0);

    await invoice.update(invoiceData, { transaction });

    if (items && Array.isArray(items)) {
      await db.VendorInvoiceItem.destroy({ where: { invoice_id: invoice.id }, transaction });

      const invoiceItems = items.map((item, index) => {
        const quantity = parseFloat(item.quantity) || 0;
        const unitPrice = parseFloat(item.unit_price) || 0;
        const discountPercent = parseFloat(item.discount_percent) || 0;

        const lineTotal = quantity * unitPrice;
        const lineDiscount = (lineTotal * discountPercent) / 100;
        const taxableAmount = lineTotal - lineDiscount;

        const cgstRate = parseFloat(item.cgst_rate) || 0;
        const sgstRate = parseFloat(item.sgst_rate) || 0;
        const igstRate = parseFloat(item.igst_rate) || 0;
        const cessRate = parseFloat(item.cess_rate) || 0;

        const lineCGST = (taxableAmount * cgstRate) / 100;
        const lineSGST = (taxableAmount * sgstRate) / 100;
        const lineIGST = (taxableAmount * igstRate) / 100;
        const lineCess = (taxableAmount * cessRate) / 100;

        return {
          invoice_id: invoice.id,
          grn_item_id: item.grn_item_id,
          line_number: index + 1,
          material_id: item.material_id,
          description: item.description,
          quantity: quantity,
          uom_id: item.uom_id,
          unit_price: unitPrice,
          discount_percent: discountPercent,
          discount_amount: lineDiscount,
          taxable_amount: taxableAmount,
          cgst_rate: cgstRate,
          cgst_amount: lineCGST,
          sgst_rate: sgstRate,
          sgst_amount: lineSGST,
          igst_rate: igstRate,
          igst_amount: lineIGST,
          cess_rate: cessRate,
          cess_amount: lineCess,
          line_total: taxableAmount + lineCGST + lineSGST + lineIGST + lineCess,
          hsn_code: item.hsn_code,
          remarks: item.remarks
        };
      });

      await db.VendorInvoiceItem.bulkCreate(invoiceItems, { transaction });
    }

    await transaction.commit();

    const updatedInvoice = await db.VendorInvoice.findByPk(invoice.id, {
      include: [
        { model: db.VendorInvoiceItem, as: 'items', include: [{ model: db.Material, as: 'material' }, { model: db.UOM, as: 'uom' }] }
      ]
    });

    sendResponse(res, 200, true, 'Vendor Invoice updated successfully', updatedInvoice);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.submit = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const userId = req.user?.userId || null;

  const invoice = await db.VendorInvoice.findByPk(id);
  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  if (invoice.status !== 'DRAFT') {
    return sendError(res, 'Only DRAFT Vendor Invoices can be submitted', 400);
  }

  await invoice.update({
    status: 'SUBMITTED',
    submitted_at: new Date(),
    submitted_by: userId,
    updated_by: userId
  });

  sendResponse(res, 200, true, 'Vendor Invoice submitted successfully', invoice);
});

exports.approve = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const userId = req.user?.userId || null;

  const invoice = await db.VendorInvoice.findByPk(id);
  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  if (invoice.status !== 'SUBMITTED') {
    return sendError(res, 'Only SUBMITTED Vendor Invoices can be approved', 400);
  }

  await invoice.update({
    status: 'APPROVED',
    approved_at: new Date(),
    approved_by: userId,
    updated_by: userId
  });

  sendResponse(res, 200, true, 'Vendor Invoice approved successfully', invoice);
});

exports.post = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const userId = req.user?.userId || null;

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

  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  if (invoice.status !== 'APPROVED') {
    return sendError(res, 'Only APPROVED Vendor Invoices can be posted', 400);
  }

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

  try {
    await invoice.update({
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      updated_by: userId
    }, { transaction });

    await glPostingService.postVendorInvoiceToGL(invoice, userId);

    await transaction.commit();

    const updatedInvoice = await db.VendorInvoice.findByPk(id, {
      include: [
        { model: db.VendorInvoiceItem, as: 'items', include: [{ model: db.Material, as: 'material' }, { model: db.UOM, as: 'uom' }] }
      ]
    });

    sendResponse(res, 200, true, 'Vendor Invoice posted successfully', updatedInvoice);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.delete = asyncHandler(async (req, res) => {
  const { id } = req.params;

  const invoice = await db.VendorInvoice.findByPk(id);
  if (!invoice) {
    return sendError(res, 'Vendor Invoice not found', 404);
  }

  if (invoice.status !== 'DRAFT') {
    return sendError(res, 'Only DRAFT Vendor Invoices can be deleted', 400);
  }

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

  try {
    await db.VendorInvoiceItem.destroy({ where: { invoice_id: id }, transaction });
    await invoice.destroy({ transaction });
    await transaction.commit();

    sendResponse(res, 200, true, 'Vendor Invoice deleted successfully', null);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

