const db = require('../models');
const { sendResponse, sendError } = require('../utils/response');
const asyncHandler = require('../middlewares/asyncHandler');
const { Op } = require('sequelize');
const stockLedgerService = require('../services/stockLedgerService');

/**
 * Get all Material Plants
 */
exports.getAll = asyncHandler(async (req, res) => {
  const { material_id, plant_id, status } = req.query;
  const companyId = req.user?.companyId;

  const where = {};
  if (companyId) {
    // MaterialPlant doesn't have company_id directly, filter through Material
    const materials = await db.Material.findAll({
      where: { company_id: companyId },
      attributes: ['id']
    });
    const materialIds = materials.map(m => m.id);
    if (materialIds.length > 0) {
      where.material_id = { [Op.in]: materialIds };
    } else {
      where.material_id = { [Op.in]: [] }; // No materials for this company
    }
  }

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

  const materialPlants = await db.MaterialPlant.findAll({
    where,
    include: [
      {
        model: db.Material,
        as: 'material',
        attributes: ['id', 'material_number', 'description', 'material_type', 'base_uom_id'],
        include: [
          {
            model: db.UOM,
            as: 'baseUOM',
            attributes: ['id', 'uom_code', 'uom_name']
          }
        ]
      },
      {
        model: db.Plant,
        as: 'plant',
        attributes: ['id', 'code', 'name']
      },
      {
        model: db.UOM,
        as: 'openingBalanceUOM',
        attributes: ['id', 'uom_code', 'uom_name'],
        required: false
      }
    ],
    order: [['material_id', 'ASC'], ['plant_id', 'ASC']]
  });

  sendResponse(res, 200, true, 'Material plants retrieved successfully', { items: materialPlants });
});

/**
 * Get Material Plant by ID
 */
exports.getById = asyncHandler(async (req, res) => {
  const { id } = req.params;

  const materialPlant = await db.MaterialPlant.findByPk(id, {
    include: [
      {
        model: db.Material,
        as: 'material',
        attributes: ['id', 'material_number', 'description', 'material_type', 'base_uom_id'],
        include: [
          {
            model: db.UOM,
            as: 'baseUOM',
            attributes: ['id', 'uom_code', 'uom_name']
          }
        ]
      },
      {
        model: db.Plant,
        as: 'plant',
        attributes: ['id', 'code', 'name']
      },
      {
        model: db.UOM,
        as: 'openingBalanceUOM',
        attributes: ['id', 'uom_code', 'uom_name'],
        required: false
      }
    ]
  });

  if (!materialPlant) {
    return sendError(res, 'Material plant not found', 404);
  }

  sendResponse(res, 200, true, 'Material plant retrieved successfully', materialPlant);
});

/**
 * Create Material Plant with opening balance handling
 */
exports.create = asyncHandler(async (req, res) => {
  const data = req.body;
  const userId = req.user?.userId || null;
  const companyId = req.user?.companyId;

  console.log('MaterialPlant create - Received data:', JSON.stringify(data, null, 2));
  console.log('MaterialPlant create - User ID:', userId, 'Company ID:', companyId);

  // Validate required fields
  if (!data.material_id || !data.plant_id) {
    console.error('MaterialPlant create - Missing required fields:', { material_id: data.material_id, plant_id: data.plant_id });
    return sendError(res, 'Material ID and Plant ID are required', 400);
  }

  // Check if material plant already exists
  const existing = await db.MaterialPlant.findOne({
    where: {
      material_id: data.material_id,
      plant_id: data.plant_id
    }
  });

  if (existing) {
    return sendError(res, 'Material plant combination already exists', 400);
  }

  // Get material to get company_id
  const material = await db.Material.findByPk(data.material_id, {
    attributes: ['id', 'company_id', 'base_uom_id']
  });

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

  if (companyId && material.company_id !== companyId) {
    return sendError(res, 'Material does not belong to your company', 403);
  }

  // Get plant to get company_id
  const plant = await db.Plant.findByPk(data.plant_id, {
    attributes: ['id', 'company_id']
  });

  if (!plant) {
    return sendError(res, 'Plant not found', 404);
  }

  if (companyId && plant.company_id !== companyId) {
    return sendError(res, 'Plant does not belong to your company', 403);
  }

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

  try {
    // Prepare material plant data
    const materialPlantData = {
      ...data,
      created_by: userId,
      updated_by: userId
    };

    // Use material's base UOM if opening balance UOM not specified
    if (data.opening_balance && !data.opening_balance_uom_id) {
      materialPlantData.opening_balance_uom_id = material.base_uom_id;
    }

    // Set opening balance date if not provided
    if (data.opening_balance && !data.opening_balance_date) {
      materialPlantData.opening_balance_date = new Date().toISOString().split('T')[0];
    }

    // Create material plant
    const materialPlant = await db.MaterialPlant.create(materialPlantData, { transaction });

    // Create stock ledger entry if opening balance is set
    if (data.opening_balance && parseFloat(data.opening_balance) > 0) {
      const openingBalance = parseFloat(data.opening_balance);
      const unitCost = parseFloat(data.opening_balance_unit_cost || 0);
      const totalCost = openingBalance * unitCost;
      const openingBalanceDate = data.opening_balance_date || new Date().toISOString().split('T')[0];
      const openingBalanceUOMId = data.opening_balance_uom_id || material.base_uom_id;

      // Check if opening balance entry already exists
      const existingOpeningBalance = await db.InventoryStockLedger.findOne({
        where: {
          company_id: material.company_id,
          plant_id: data.plant_id,
          material_id: data.material_id,
          transaction_type: 'OPENING_BALANCE',
          reference_type: 'OPENING_BALANCE'
        },
        transaction
      });

      if (!existingOpeningBalance) {
        // Create opening balance ledger entry
        await stockLedgerService.createLedgerEntry({
          company_id: material.company_id,
          plant_id: data.plant_id,
          material_id: data.material_id,
          transaction_date: openingBalanceDate,
          transaction_type: 'OPENING_BALANCE',
          reference_type: 'OPENING_BALANCE',
          reference_id: materialPlant.id,
          reference_number: `OB-${materialPlant.id}`,
          storage_location: data.storage_location || null,
          quantity_in: openingBalance,
          quantity_out: 0,
          unit_cost: unitCost,
          total_cost: totalCost,
          valuation_method: 'FIFO',
          remarks: `Opening balance from Material Plant setup`
        }, transaction);
      }
    }

    await transaction.commit();

    console.log('MaterialPlant create - Successfully created with ID:', materialPlant.id);

    // Fetch created material plant with associations
    const createdMaterialPlant = await db.MaterialPlant.findByPk(materialPlant.id, {
      include: [
        {
          model: db.Material,
          as: 'material',
          attributes: ['id', 'material_number', 'description', 'material_type', 'base_uom_id'],
          include: [
            {
              model: db.UOM,
              as: 'baseUOM',
              attributes: ['id', 'uom_code', 'uom_name']
            }
          ]
        },
        {
          model: db.Plant,
          as: 'plant',
          attributes: ['id', 'code', 'name']
        },
        {
          model: db.UOM,
          as: 'openingBalanceUOM',
          attributes: ['id', 'uom_code', 'uom_name'],
          required: false
        }
      ]
    });

    sendResponse(res, 201, true, 'Material plant created successfully', createdMaterialPlant);
  } catch (error) {
    await transaction.rollback();
    console.error('Error creating material plant:', error);
    console.error('Error stack:', error.stack);
    console.error('Error details:', {
      name: error.name,
      message: error.message,
      errors: error.errors
    });
    sendError(res, error.message || 'Failed to create material plant', 500);
  }
});

/**
 * Update Material Plant with opening balance handling
 */
exports.update = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const data = req.body;
  const userId = req.user?.userId || null;
  const companyId = req.user?.companyId;

  const materialPlant = await db.MaterialPlant.findByPk(id, {
    include: [
      {
        model: db.Material,
        as: 'material',
        attributes: ['id', 'company_id', 'base_uom_id']
      }
    ]
  });

  if (!materialPlant) {
    return sendError(res, 'Material plant not found', 404);
  }

  if (companyId && materialPlant.material.company_id !== companyId) {
    return sendError(res, 'Material plant does not belong to your company', 403);
  }

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

  try {
    const oldOpeningBalance = parseFloat(materialPlant.opening_balance || 0);
    const newOpeningBalance = data.opening_balance ? parseFloat(data.opening_balance) : 0;

    // Prepare update data
    const updateData = {
      ...data,
      updated_by: userId
    };

    // Use material's base UOM if opening balance UOM not specified
    if (data.opening_balance && !data.opening_balance_uom_id) {
      updateData.opening_balance_uom_id = materialPlant.material.base_uom_id;
    }

    // Set opening balance date if not provided and opening balance is being set
    if (data.opening_balance && !data.opening_balance_date) {
      updateData.opening_balance_date = new Date().toISOString().split('T')[0];
    }

    // Update material plant
    await materialPlant.update(updateData, { transaction });

    // Handle opening balance changes
    if (newOpeningBalance !== oldOpeningBalance) {
      // Check if opening balance entry exists
      const existingOpeningBalance = await db.InventoryStockLedger.findOne({
        where: {
          company_id: materialPlant.material.company_id,
          plant_id: materialPlant.plant_id,
          material_id: materialPlant.material_id,
          transaction_type: 'OPENING_BALANCE',
          reference_type: 'OPENING_BALANCE'
        },
        transaction
      });

      if (existingOpeningBalance) {
        if (newOpeningBalance > 0) {
          // Update existing opening balance entry
          const unitCost = parseFloat(data.opening_balance_unit_cost || existingOpeningBalance.unit_cost || 0);
          const totalCost = newOpeningBalance * unitCost;
          const openingBalanceDate = data.opening_balance_date || existingOpeningBalance.transaction_date;

          await existingOpeningBalance.update({
            transaction_date: openingBalanceDate,
            quantity_in: newOpeningBalance,
            balance_quantity: newOpeningBalance, // Opening balance sets the initial balance
            unit_cost: unitCost,
            total_cost: totalCost,
            storage_location: data.storage_location || materialPlant.storage_location || null,
            remarks: `Opening balance updated from Material Plant`
          }, { transaction });

          // Recalculate all subsequent balances
          const subsequentEntries = await db.InventoryStockLedger.findAll({
            where: {
              company_id: materialPlant.material.company_id,
              plant_id: materialPlant.plant_id,
              material_id: materialPlant.material_id,
              transaction_date: { [Op.gt]: openingBalanceDate },
              id: { [Op.ne]: existingOpeningBalance.id }
            },
            order: [['transaction_date', 'ASC'], ['id', 'ASC']],
            transaction
          });

          let runningBalance = newOpeningBalance;
          for (const entry of subsequentEntries) {
            const qtyIn = parseFloat(entry.quantity_in || 0);
            const qtyOut = parseFloat(entry.quantity_out || 0);
            runningBalance = runningBalance + qtyIn - qtyOut;
            await entry.update({ balance_quantity: runningBalance }, { transaction });
          }
        } else {
          // Remove opening balance entry if set to 0
          await existingOpeningBalance.destroy({ transaction });

          // Recalculate all subsequent balances starting from 0
          const allEntries = await db.InventoryStockLedger.findAll({
            where: {
              company_id: materialPlant.material.company_id,
              plant_id: materialPlant.plant_id,
              material_id: materialPlant.material_id,
              id: { [Op.ne]: existingOpeningBalance.id }
            },
            order: [['transaction_date', 'ASC'], ['id', 'ASC']],
            transaction
          });

          let runningBalance = 0;
          for (const entry of allEntries) {
            const qtyIn = parseFloat(entry.quantity_in || 0);
            const qtyOut = parseFloat(entry.quantity_out || 0);
            runningBalance = runningBalance + qtyIn - qtyOut;
            await entry.update({ balance_quantity: runningBalance }, { transaction });
          }
        }
      } else if (newOpeningBalance > 0) {
        // Create new opening balance entry
        const unitCost = parseFloat(data.opening_balance_unit_cost || 0);
        const totalCost = newOpeningBalance * unitCost;
        const openingBalanceDate = data.opening_balance_date || new Date().toISOString().split('T')[0];
        const openingBalanceUOMId = data.opening_balance_uom_id || materialPlant.material.base_uom_id;

        await stockLedgerService.createLedgerEntry({
          company_id: materialPlant.material.company_id,
          plant_id: materialPlant.plant_id,
          material_id: materialPlant.material_id,
          transaction_date: openingBalanceDate,
          transaction_type: 'OPENING_BALANCE',
          reference_type: 'OPENING_BALANCE',
          reference_id: materialPlant.id,
          reference_number: `OB-${materialPlant.id}`,
          storage_location: data.storage_location || materialPlant.storage_location || null,
          quantity_in: newOpeningBalance,
          quantity_out: 0,
          unit_cost: unitCost,
          total_cost: totalCost,
          valuation_method: 'FIFO',
          remarks: `Opening balance from Material Plant setup`
        }, transaction);
      }
    }

    await transaction.commit();

    // Fetch updated material plant with associations
    const updatedMaterialPlant = await db.MaterialPlant.findByPk(id, {
      include: [
        {
          model: db.Material,
          as: 'material',
          attributes: ['id', 'material_number', 'description', 'material_type', 'base_uom_id'],
          include: [
            {
              model: db.UOM,
              as: 'baseUOM',
              attributes: ['id', 'uom_code', 'uom_name']
            }
          ]
        },
        {
          model: db.Plant,
          as: 'plant',
          attributes: ['id', 'code', 'name']
        },
        {
          model: db.UOM,
          as: 'openingBalanceUOM',
          attributes: ['id', 'uom_code', 'uom_name'],
          required: false
        }
      ]
    });

    sendResponse(res, 200, true, 'Material plant updated successfully', updatedMaterialPlant);
  } catch (error) {
    await transaction.rollback();
    console.error('Error updating material plant:', error);
    sendError(res, error.message || 'Failed to update material plant', 500);
  }
});

/**
 * Delete Material Plant
 */
exports.delete = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const companyId = req.user?.companyId;

  const materialPlant = await db.MaterialPlant.findByPk(id, {
    include: [
      {
        model: db.Material,
        as: 'material',
        attributes: ['id', 'company_id']
      }
    ]
  });

  if (!materialPlant) {
    return sendError(res, 'Material plant not found', 404);
  }

  if (companyId && materialPlant.material.company_id !== companyId) {
    return sendError(res, 'Material plant does not belong to your company', 403);
  }

  await materialPlant.destroy();

  sendResponse(res, 200, true, 'Material plant deleted successfully', null);
});

