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 stockLedgerService = require('../services/stockLedgerService');
const negativeStockPreventionService = require('../services/negativeStockPreventionService');

const generateMovementNumber = async (companyId, movementType = '201') => {
  const today = new Date();
  const year = today.getFullYear();
  const prefix = `GI${year}`;
  
  const lastGI = await db.InventoryGoodsIssue.findOne({
    where: {
      company_id: companyId,
      movement_number: { [Op.like]: `${prefix}%` }
    },
    order: [['movement_number', 'DESC']]
  });

  let sequence = 1;
  if (lastGI) {
    const lastSequence = parseInt(lastGI.movement_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 } = 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 (status) {
    where.status = status;
  }

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

  const records = await db.InventoryGoodsIssue.findAndCountAll({
    where,
    include: [
      { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
      { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
      { model: db.CostCenter, as: 'costCenter', attributes: ['id', 'cost_center_code', 'description'] }
    ],
    limit: limitNum,
    offset,
    order: [['movement_date', 'DESC'], ['id', 'DESC']]
  });

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

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

  const gi = await db.InventoryGoodsIssue.findByPk(id, {
    include: [
      { model: db.Company, as: 'company' },
      { model: db.Plant, as: 'plant' },
      { model: db.FiscalYear, as: 'fiscalYear' },
      { model: db.CostCenter, as: 'costCenter' },
      {
        model: db.InventoryGoodsIssueItem,
        as: 'items',
        include: [
          { model: db.Material, as: 'material' },
          { model: db.UOM, as: 'uom' }
        ],
        order: [['line_number', 'ASC']]
      },
      { model: db.User, as: 'postedByUser', attributes: ['id', 'first_name', 'last_name'] },
      { model: db.User, as: 'createdByUser', attributes: ['id', 'first_name', 'last_name'] }
    ]
  });

  if (!gi) {
    return sendError(res, 404, 'Goods Issue not found');
  }

  sendResponse(res, 200, true, 'Goods Issue retrieved successfully', gi);
});

exports.create = asyncHandler(async (req, res) => {
  const transaction = await db.sequelize.transaction();
  
  try {
    const { items, ...giData } = req.body;
    
    if (!giData.movement_number) {
      giData.movement_number = await generateMovementNumber(giData.company_id || req.user.companyId, giData.movement_type);
    }

    giData.created_by = req.user.id;

    const gi = await db.InventoryGoodsIssue.create(giData, { transaction });

    if (items && items.length > 0) {
      const itemsData = items.map((item, index) => ({
        movement_id: gi.id,
        line_number: item.item_number || index + 1,
        material_id: item.material_id,
        description: item.description,
        quantity: item.quantity,
        uom_id: item.uom_id,
        unit_price: item.unit_price || 0,
        total_amount: (item.quantity || 0) * (item.unit_price || 0),
        batch_number: item.batch_number || null,
        remarks: item.remarks || null
      }));

      await db.InventoryGoodsIssueItem.bulkCreate(itemsData, { transaction });
    }

    await transaction.commit();

    const createdGI = await db.InventoryGoodsIssue.findByPk(gi.id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        {
          model: db.InventoryGoodsIssueItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    sendResponse(res, 201, true, 'Goods Issue created successfully', createdGI);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.update = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const transaction = await db.sequelize.transaction();
  
  try {
    const gi = await db.InventoryGoodsIssue.findByPk(id);
    
    if (!gi) {
      await transaction.rollback();
      return sendError(res, 404, 'Goods Issue not found');
    }

    if (gi.status === 'POSTED') {
      await transaction.rollback();
      return sendError(res, 400, 'Cannot update posted Goods Issue');
    }

    const { items, ...giData } = req.body;
    giData.updated_by = req.user.id;

    await gi.update(giData, { transaction });

    if (items) {
      await db.InventoryGoodsIssueItem.destroy({ where: { movement_id: id }, transaction });

      if (items.length > 0) {
        const itemsData = items.map((item, index) => ({
          movement_id: id,
          line_number: item.item_number || index + 1,
          material_id: item.material_id,
          description: item.description,
          quantity: item.quantity,
          uom_id: item.uom_id,
          unit_price: item.unit_price || 0,
          total_amount: (item.quantity || 0) * (item.unit_price || 0),
          batch_number: item.batch_number || null,
          remarks: item.remarks || null
        }));

        await db.InventoryGoodsIssueItem.bulkCreate(itemsData, { transaction });
      }
    }

    await transaction.commit();

    const updatedGI = await db.InventoryGoodsIssue.findByPk(id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        {
          model: db.InventoryGoodsIssueItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    sendResponse(res, 200, true, 'Goods Issue updated successfully', updatedGI);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

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

  const gi = await db.InventoryGoodsIssue.findByPk(id);
  
  if (!gi) {
    return sendError(res, 404, 'Goods Issue not found');
  }

  if (gi.status === 'POSTED') {
    return sendError(res, 400, 'Cannot delete posted Goods Issue. Use status update to cancel.');
  }

  await db.InventoryGoodsIssueItem.destroy({ where: { movement_id: id } });
  await gi.update({ status: 'CANCELLED', updated_by: req.user.id });

  sendResponse(res, 200, true, 'Goods Issue cancelled successfully');
});

exports.post = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const transaction = await db.sequelize.transaction();
  
  try {
    const gi = await db.InventoryGoodsIssue.findByPk(id, {
      include: [{ model: db.InventoryGoodsIssueItem, as: 'items' }]
    });
    
    if (!gi) {
      await transaction.rollback();
      return sendError(res, 404, 'Goods Issue not found');
    }

    if (gi.status !== 'DRAFT') {
      await transaction.rollback();
      return sendError(res, 400, 'Only draft Goods Issue can be posted');
    }

    if (!gi.items || gi.items.length === 0) {
      await transaction.rollback();
      return sendError(res, 400, 'Cannot post Goods Issue without items');
    }

    // Validate storage location for all items
    for (const item of gi.items) {
      if (!gi.storage_location && !item.storage_location) {
        await transaction.rollback();
        return sendError(res, 400, `Storage location is required for material ${item.material_id || 'N/A'}`);
      }
    }

    // Validate stock availability for all items before posting
    const itemsWithMaterials = await Promise.all(
      gi.items.map(async (item) => {
        const material = await db.Material.findByPk(item.material_id, { transaction });
        return {
          ...item.toJSON(),
          material,
          source_storage_location: gi.storage_location || item.storage_location || null
        };
      })
    );

    const batchValidation = await negativeStockPreventionService.validateBatchStockIssues(
      itemsWithMaterials,
      {
        companyId: gi.company_id,
        plantId: gi.plant_id,
        operation: 'Inventory Goods Issue',
        transaction
      }
    );

    if (!batchValidation.isValid) {
      await transaction.rollback();
      const errorMessage = negativeStockPreventionService.formatValidationErrors(batchValidation.errors);
      return sendError(res, 400, errorMessage);
    }

    // Update stock ledger for each item
    for (let i = 0; i < gi.items.length; i++) {
      const item = gi.items[i];
      const quantity = parseFloat(item.quantity || 0);
      const unitPrice = parseFloat(item.unit_price || 0) || 0;
      const totalAmount = quantity * unitPrice;
      const storageLocation = gi.storage_location || item.storage_location || null;

      // Create stock ledger entry - ISSUE
      await stockLedgerService.createLedgerEntry({
        company_id: gi.company_id,
        plant_id: gi.plant_id,
        material_id: item.material_id,
        transaction_date: gi.movement_date,
        transaction_type: 'ISSUE',
        reference_type: 'GOODS_ISSUE',
        reference_id: gi.id,
        reference_number: gi.movement_number,
        storage_location: storageLocation,
        quantity_in: 0,
        quantity_out: quantity,
        unit_price: unitPrice,
        remarks: `Inventory Goods Issue: ${gi.movement_number}${gi.reference_number ? ` (Ref: ${gi.reference_number})` : ''}`,
      }, transaction);
    }

    await gi.update({
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: req.user.id
    }, { transaction });

    // Post to GL (accounting integration) if enabled
    try {
      const giForGL = await db.InventoryGoodsIssue.findByPk(gi.id, {
        include: [
          {
            model: db.InventoryGoodsIssueItem,
            as: 'items',
            include: [
              { model: db.Material, as: 'material' }
            ]
          }
        ],
        transaction
      });
      await glPostingService.postInventoryMovementToGL(giForGL, 'GOODS_ISSUE', req.user.id);
    } catch (glError) {
      console.warn('GL posting failed for inventory goods issue:', glError.message);
      // Don't fail the posting if GL posting fails
    }

    await transaction.commit();

    const postedGI = await db.InventoryGoodsIssue.findByPk(id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        {
          model: db.InventoryGoodsIssueItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    sendResponse(res, 200, true, 'Goods Issue posted successfully', postedGI);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

