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

const generateMRNNumber = async (companyId, plantId, fiscalYearId) => {
  try {
    const number = await numberGenerationService.generateNumber({
      companyId,
      plantId,
      fiscalYearId,
      module: 'material_return_note',
      format: 'MRN-{YYYY}-{####}'
    });
    return number;
  } catch (error) {
    // Fallback to simple generation
    const today = new Date();
    const year = today.getFullYear();
    const prefix = `MRN${year}`;
    
    const lastMRN = await db.MaterialReturnNote.findOne({
      where: {
        company_id: companyId,
        mrn_number: { [Op.like]: `${prefix}%` }
      },
      order: [['mrn_number', 'DESC']]
    });

    let sequence = 1;
    if (lastMRN) {
      const lastSequence = parseInt(lastMRN.mrn_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, productionOrderId, departmentId } = 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 (productionOrderId) {
    where.production_order_id = productionOrderId;
  }

  if (departmentId) {
    where.department_id = departmentId;
  }

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

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

  const records = await db.MaterialReturnNote.findAndCountAll({
    where,
    include: [
      { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
      { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
      { model: db.ProductionOrder, as: 'productionOrder', attributes: ['id', 'production_order_number'] },
      { model: db.Department, as: 'department', attributes: ['id', 'department_code', 'department_name'] },
      { model: db.MaterialIssue, as: 'materialIssue', attributes: ['id', 'issue_number'] }
    ],
    limit: limitNum,
    offset,
    order: [['return_date', 'DESC'], ['id', 'DESC']]
  });

  const response = getPagingData(records, page, limitNum);
  sendResponse(res, 200, true, 'Material Return Notes retrieved successfully', response);
});

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

  const mrn = await db.MaterialReturnNote.findByPk(id, {
    include: [
      { model: db.Company, as: 'company' },
      { model: db.Plant, as: 'plant' },
      { model: db.FiscalYear, as: 'fiscalYear' },
      { model: db.ProductionOrder, as: 'productionOrder' },
      { model: db.Department, as: 'department' },
      { model: db.MaterialIssue, as: 'materialIssue' },
      {
        model: db.MaterialReturnNoteItem,
        as: 'items',
        include: [
          { model: db.Material, as: 'material' },
          { model: db.UOM, as: 'uom' },
          { model: db.MaterialIssueItem, as: 'materialIssueItem' },
          { model: db.ProductionOrderComponent, as: 'productionOrderComponent' }
        ],
        order: [['line_number', 'ASC']]
      },
      { model: db.User, as: 'returnedByUser', attributes: ['id', 'username', 'email', 'first_name', 'last_name'] },
      { model: db.User, as: 'receivedByUser', attributes: ['id', 'username', 'email', 'first_name', 'last_name'] },
      { model: db.User, as: 'postedByUser', attributes: ['id', 'username', 'email', 'first_name', 'last_name'] },
      { model: db.User, as: 'createdByUser', attributes: ['id', 'username', 'email', 'first_name', 'last_name'] },
      { model: db.User, as: 'updatedByUser', attributes: ['id', 'username', 'email', 'first_name', 'last_name'] }
    ]
  });

  if (!mrn) {
    return sendError(res, 404, 'Material Return Note not found');
  }

  sendResponse(res, 200, true, 'Material Return Note retrieved successfully', mrn);
});

exports.create = asyncHandler(async (req, res) => {
  const { items, ...mrnData } = req.body;
  const userId = req.user?.userId || req.user?.id || null;

  if (!items || !Array.isArray(items) || items.length === 0) {
    return sendError(res, 400, 'At least one item is required');
  }

  // Department is required to identify return origin (even if production order is not present)
  if (!mrnData.department_id) {
    return sendError(res, 400, 'department_id is required');
  }

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

  try {
    // Generate MRN number
    const mrnNumber = await generateMRNNumber(
      mrnData.company_id || req.user.companyId,
      mrnData.plant_id,
      mrnData.fiscal_year_id
    );

    // Create MRN
    const mrn = await db.MaterialReturnNote.create({
      ...mrnData,
      mrn_number: mrnNumber,
      status: 'DRAFT',
      returned_by: userId,
      created_by: userId,
      updated_by: userId
    }, { transaction });

    // Create items
    const mrnItems = await Promise.all(
      items.map((item, index) =>
        db.MaterialReturnNoteItem.create({
          ...item,
          mrn_id: mrn.id,
          line_number: item.line_number || index + 1,
          created_at: new Date(),
          updated_at: new Date()
        }, { transaction })
      )
    );

    await transaction.commit();

    // Fetch created MRN with associations
    const createdMRN = await db.MaterialReturnNote.findByPk(mrn.id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        { model: db.FiscalYear, as: 'fiscalYear' },
        { model: db.ProductionOrder, as: 'productionOrder' },
        { model: db.Department, as: 'department' },
        {
          model: db.MaterialReturnNoteItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    // Notify Stores about a new return note to receive/post
    try {
      const storesUserRoles = await db.UserRole.findAll({
        include: [
          {
            model: db.Role,
            as: 'role',
            where: {
              company_id: mrn.company_id,
              status: 'ACTIVE',
              [Op.or]: [
                { role_code: { [Op.like]: '%STORES%' } },
                { role_code: { [Op.like]: '%STORE%' } },
                { role_name: { [Op.like]: '%Stores%' } },
                { role_name: { [Op.like]: '%Store%' } }
              ]
            },
            required: true
          },
          {
            model: db.User,
            as: 'user',
            where: { status: 'ACTIVE', is_active: true },
            required: true
          }
        ]
      });

      const ids = new Set();
      storesUserRoles.forEach((ur) => ur.user?.id && ids.add(ur.user.id));
      if (userId) ids.add(userId);

      await Promise.all(
        Array.from(ids).map((uid) =>
          notificationService.createNotification({
            companyId: mrn.company_id,
            userId: uid,
            type: 'INFO',
            moduleName: 'MaterialReturnNote',
            transactionId: mrn.id,
            transactionNumber: mrn.mrn_number,
            title: 'Material Return to Stores',
            message: `Material Return Note ${mrn.mrn_number} created for Stores receipt.`,
            link: `/stores/materialreturnnote/${mrn.id}/view`,
            priority: 'MEDIUM',
            createdBy: userId,
          })
        )
      );
    } catch (e) {
      console.error('Failed to create notification for MRN:', e.message);
    }

    sendResponse(res, 201, true, 'Material Return Note created successfully', createdMRN);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

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

  const mrn = await db.MaterialReturnNote.findByPk(id);

  if (!mrn) {
    return sendError(res, 404, 'Material Return Note not found');
  }

  if (mrn.status === 'POSTED') {
    return sendError(res, 400, 'Cannot update a posted Material Return Note');
  }

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

  try {
    // Update MRN
    await mrn.update({
      ...mrnData,
      updated_by: userId
    }, { transaction });

    // Delete existing items
    await db.MaterialReturnNoteItem.destroy({
      where: { mrn_id: id },
      transaction
    });

    // Create new items
    if (items && Array.isArray(items) && items.length > 0) {
      await Promise.all(
        items.map((item, index) =>
          db.MaterialReturnNoteItem.create({
            ...item,
            mrn_id: id,
            line_number: item.line_number || index + 1,
            created_at: new Date(),
            updated_at: new Date()
          }, { transaction })
        )
      );
    }

    await transaction.commit();

    // Fetch updated MRN
    const updatedMRN = await db.MaterialReturnNote.findByPk(id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        { model: db.FiscalYear, as: 'fiscalYear' },
        { model: db.ProductionOrder, as: 'productionOrder' },
        {
          model: db.MaterialReturnNoteItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    sendResponse(res, 200, true, 'Material Return Note updated successfully', updatedMRN);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

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

  const mrn = await db.MaterialReturnNote.findByPk(id);

  if (!mrn) {
    return sendError(res, 404, 'Material Return Note not found');
  }

  if (mrn.status === 'POSTED') {
    return sendError(res, 400, 'Cannot delete a posted Material Return Note');
  }

  await mrn.destroy();

  sendResponse(res, 200, true, 'Material Return Note deleted successfully');
});

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

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

  if (!mrn) {
    return sendError(res, 404, 'Material Return Note not found');
  }

  if (mrn.status === 'POSTED') {
    return sendError(res, 400, 'Material Return Note is already posted');
  }

  if (mrn.status !== 'IN_TRANSIT') {
    return sendError(res, 400, `Material Return Note must be IN_TRANSIT to be received. Current status: ${mrn.status}`);
  }

  if (mrn.status === 'CANCELLED') {
    return sendError(res, 400, 'Cannot post a cancelled Material Return Note');
  }

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

  try {
    // Update MRN status
    await mrn.update({
      status: 'POSTED',
      posted_at: new Date(),
      posted_by: userId,
      received_by: userId, // Stores person receives the returned materials
      updated_by: userId
    }, { transaction });

    for (const item of mrn.items) {
      const quantityIn = parseFloat(item.returned_quantity || 0);
      if (quantityIn <= 0) continue;

      const sourceLoc = item.source_storage_location || item.storage_location || null;
      const destLoc = item.destination_storage_location || item.storage_location || null;

      // Get source location from material issue item if not set
      if (!sourceLoc && item.material_issue_item_id) {
        const issueItem = await db.MaterialIssueItem.findByPk(item.material_issue_item_id, { transaction });
        if (issueItem) {
          sourceLoc = issueItem.storage_location; // Production location
        }
      }

      if (!sourceLoc) {
        if (transaction && !transaction.finished) {
          await transaction.rollback();
        }
        return sendError(res, 400, `Source storage location is required for material ${item.material?.material_number || item.material_id}`);
      }

      // Default destination: first active warehouse location if not provided
      let destination = destLoc;
      if (!destination) {
        const storesLocation = await db.StorageLocation.findOne({
          where: {
            company_id: mrn.company_id,
            plant_id: mrn.plant_id,
            storage_location_type: 'WAREHOUSE',
            status: 'ACTIVE'
          },
          transaction
        });
        if (!storesLocation) {
          if (transaction && !transaction.finished) {
            await transaction.rollback();
          }
          return sendError(res, 400, 'Stores storage location not found. Please configure a WAREHOUSE type storage location.');
        }
        destination = storesLocation.storage_location_code;
      }

      // For MRN receive, we're receiving from production (sourceLoc) to stores (destination)
      // So we need to check stock in production location (sourceLoc), not issue from it
      // Actually, the stock was already reduced when MRN was created (IN_TRANSIT)
      // Now we just need to add it to stores location
      
      // Validate that stock exists in source location (production) - it should have been reduced when MRN was created
      const currentBalance = await stockLedgerService.getCurrentBalance(
        mrn.company_id,
        mrn.plant_id,
        item.material_id,
        sourceLoc
      );
      
      // Note: For IN_TRANSIT MRN, stock was already reduced from production location when MRN was created
      // We only need to add stock to stores location now (RECEIPT entry)
      // No need to create ISSUE entry again as it was already created when MRN status was set to IN_TRANSIT

      // RECEIPT to stores/destination location
      await stockLedgerService.createLedgerEntry({
        company_id: mrn.company_id,
        plant_id: mrn.plant_id,
        material_id: item.material_id,
        transaction_date: mrn.return_date,
        transaction_type: 'RECEIPT',
        reference_type: 'MRN',
        reference_id: mrn.id,
        reference_number: mrn.mrn_number,
        storage_location: destination,
        quantity_in: quantityIn,
        quantity_out: 0,
        unit_cost: 0,
        total_cost: 0,
        batch_number: item.batch_number || null,
        lot_number: item.lot_number || null,
        remarks: `Material Return (MRN: ${mrn.mrn_number}) - Received at Stores ${destination} from ${sourceLoc}`,
        created_by: userId
      }, transaction);

      // Update Production Order Component returned_quantity if linked
      if (item.production_order_component_id) {
        const component = await db.ProductionOrderComponent.findByPk(item.production_order_component_id);
        if (component) {
          const currentReturned = parseFloat(component.returned_quantity || 0);
          await component.update({
            returned_quantity: currentReturned + parseFloat(item.returned_quantity || 0)
          }, { transaction });
        }
      }
    }

    await transaction.commit();

    // Fetch posted MRN
    const postedMRN = await db.MaterialReturnNote.findByPk(id, {
      include: [
        { model: db.Company, as: 'company' },
        { model: db.Plant, as: 'plant' },
        { model: db.ProductionOrder, as: 'productionOrder' },
        { model: db.Department, as: 'department' },
        {
          model: db.MaterialReturnNoteItem,
          as: 'items',
          include: [
            { model: db.Material, as: 'material' },
            { model: db.UOM, as: 'uom' }
          ]
        }
      ]
    });

    sendResponse(res, 200, true, 'Material Return Note posted successfully', postedMRN);
  } catch (error) {
    // Only rollback if transaction is still active
    if (transaction && !transaction.finished) {
      await transaction.rollback();
    }
    console.error('Error posting Material Return Note:', error);
    sendError(res, error.message || 'Failed to post Material Return Note', 400);
  }
});

