/**
 * Purchase Order Service
 * Business logic for purchase order management
 */

// Import PurchaseOrder and related models
const { PurchaseOrder, PurchaseOrderItem, Vendor, GRN, GRNItem, Product, sequelize } = require('../../../models');
// Import custom error classes
const { NotFoundError, ValidationError } = require('../../../utils/errors');
// Import logger for logging
const logger = require('../../../utils/logger');
// Import Sequelize operators
const { Op } = require('sequelize');

/**
 * Create purchase order
 * Creates a new purchase order
 * @param {Object} poData - Purchase order data (vendor_id, status)
 * @returns {Promise<Object>} Created purchase order
 */
const createPurchaseOrder = async (poData) => {
  // Extract purchase order data
  const { vendor_id, status = 'DRAFT', items = [] } = poData; // Extract data
  
  // Validate required fields
  if (!vendor_id) {
    throw new ValidationError('Vendor ID is required'); // Throw error if vendor_id missing
  }
  
  // Validate vendor exists
  const vendor = await Vendor.findByPk(vendor_id); // Find vendor
  if (!vendor) {
    throw new NotFoundError(`Vendor with ID ${vendor_id} not found`); // Throw error if vendor not found
  }
  
  // Validate status
  const validStatuses = ['DRAFT', 'CONFIRMED', 'CLOSED']; // Valid statuses
  if (!validStatuses.includes(status)) {
    throw new ValidationError(`Status must be one of: ${validStatuses.join(', ')}`); // Throw error if invalid status
  }

  // Start transaction
  const transaction = await sequelize.transaction();
  
  try {
    // Create purchase order
    const purchaseOrder = await PurchaseOrder.create({
      vendor_id, // Vendor ID
      status, // Status
    }, { transaction });
    
    // Create purchase order items if provided
    if (items && Array.isArray(items) && items.length > 0) {
      for (const item of items) {
        const { 
          product_id, 
          quantity, 
          unit_cost,
          piece_length,
          piece_width,
          dimension_unit,
          pieces_count
        } = item;
        
        // Validate required fields
        if (!product_id || !quantity || unit_cost === undefined) {
          throw new ValidationError('Item must have product_id, quantity, and unit_cost');
        }
        
        // Validate product exists
        const product = await Product.findByPk(product_id, { transaction });
        if (!product) {
          throw new NotFoundError(`Product not found: ${product_id}`);
        }
        
        // Create purchase order item data
        const poItemData = {
          purchase_order_id: purchaseOrder.id,
          product_id,
          quantity: parseFloat(quantity),
          unit_cost: parseFloat(unit_cost),
        };
        
        // Include dimension fields if provided (for dimension-based RM products)
        // These represent the expected/ordered dimensions
        // Actual dimensions may differ when goods are received via GRN
        if (piece_length !== null && piece_length !== undefined && piece_length !== '') {
          poItemData.piece_length = parseFloat(piece_length);
        }
        if (piece_width !== null && piece_width !== undefined && piece_width !== '') {
          poItemData.piece_width = parseFloat(piece_width);
        }
        if (dimension_unit && dimension_unit !== '') {
          if (!['inch', 'cm', 'm'].includes(dimension_unit)) {
            throw new ValidationError('Dimension unit must be one of: inch, cm, m');
          }
          poItemData.dimension_unit = dimension_unit;
        }
        if (pieces_count !== null && pieces_count !== undefined && pieces_count !== '') {
          poItemData.pieces_count = parseInt(pieces_count);
        }
        
        await PurchaseOrderItem.create(poItemData, { transaction });
      }
    }
    
    // Commit transaction
    await transaction.commit();
    
    logger.info(`Purchase order created: ${purchaseOrder.id} - Vendor: ${vendor_id}`); // Log PO creation
    
    // Return created purchase order with vendor and items
    return await getPurchaseOrder(purchaseOrder.id); // Return PO with associations
  } catch (error) {
    // Rollback on error
    await transaction.rollback();
    throw error;
  }
};

/**
 * Get purchase order by ID
 * Retrieves a purchase order by ID with received quantities calculated
 * @param {number} poId - Purchase order ID
 * @returns {Promise<Object>} Purchase order with received quantities
 */
const getPurchaseOrder = async (poId) => {
  // Validate purchase order ID
  if (!poId) {
    throw new ValidationError('Purchase order ID is required'); // Throw error if ID missing
  }
  
  // Find purchase order with associations
  const purchaseOrder = await PurchaseOrder.findByPk(poId, {
    include: [
      {
        model: Vendor, // Include vendor
        as: 'vendor', // Use vendor alias
      },
      {
        model: PurchaseOrderItem, // Include purchase order items
        as: 'items', // Use items alias
        required: false, // Left join
        include: [
          {
            model: Product, // Include product
            as: 'product', // Use product alias
          },
        ],
      },
      {
        model: GRN, // Include GRNs
        as: 'grns', // Use grns alias
        required: false, // Left join
        include: [
          {
            model: GRNItem, // Include GRN items to calculate received quantities
            as: 'items', // Use items alias
            required: false, // Left join
          },
        ],
      },
    ],
  });
  
  // Check if purchase order exists
  if (!purchaseOrder) {
    throw new NotFoundError(`Purchase order with ID ${poId} not found`); // Throw error if not found
  }
  
  // Calculate received quantities for each PO item
  if (purchaseOrder.items && purchaseOrder.items.length > 0) {
    // Get all GRNs linked to this PO
    const grns = purchaseOrder.grns || [];
    
    // Helper function to create matching key for GRN items
    // PO items don't have dimensions - they're matched by product_id only
    // GRN items may have dimensions (for dimension-based RM)
    const createGRNItemKey = (grnItem, product = null) => {
      const baseKey = `${grnItem.product_id}`;
      
      // For dimension-based RM in GRN, include dimensions in the key
      // This allows tracking different dimension sizes received
      if (product && product.product_type === 'RM' && product.track_by_dimensions) {
        const length = grnItem.piece_length ? parseFloat(grnItem.piece_length).toFixed(3) : 'null';
        const width = grnItem.piece_width ? parseFloat(grnItem.piece_width).toFixed(3) : 'null';
        const unit = grnItem.dimension_unit || 'null';
        return `${baseKey}_${length}_${width}_${unit}`;
      }
      
      return baseKey;
    };
    
    // Build a map of received quantities by product_id only (PO items don't have dimensions)
    // For dimension-based RM, we sum all received pieces regardless of dimensions
    const receivedMap = new Map();
    
    // Iterate through all GRNs and sum up received quantities by product_id
    for (const grn of grns) {
      if (grn.items && grn.items.length > 0) {
        for (const grnItem of grn.items) {
          // Match by product_id only (PO items don't have dimensions)
          const key = `${grnItem.product_id}`;
          const currentReceived = receivedMap.get(key) || 0;
          // For dimension-based RM, sum pieces_count; for others, sum quantity
          const receivedQty = grnItem.pieces_count || parseFloat(grnItem.quantity || 0);
          receivedMap.set(key, currentReceived + receivedQty);
        }
      }
    }
    
    // Add received_quantity and remaining_quantity to each PO item
    const itemsWithReceived = purchaseOrder.items.map((poItem) => {
      // PO items are matched by product_id only (no dimensions)
      const key = `${poItem.product_id}`;
      const receivedQuantity = receivedMap.get(key) || 0;
      const orderedQuantity = parseFloat(poItem.quantity || 0);
      const remainingQuantity = Math.max(0, orderedQuantity - receivedQuantity);
      
      return {
        ...poItem.toJSON(),
        received_quantity: receivedQuantity,
        remaining_quantity: remainingQuantity,
      };
    });
    
    // Replace items with enriched data
    purchaseOrder.items = itemsWithReceived;
  }
  
  // Convert to JSON and return
  return purchaseOrder.toJSON ? purchaseOrder.toJSON() : purchaseOrder;
};

/**
 * Update purchase order
 * Updates an existing purchase order
 * @param {number} poId - Purchase order ID
 * @param {Object} updateData - Update data
 * @returns {Promise<Object>} Updated purchase order
 */
const updatePurchaseOrder = async (poId, updateData) => {
  // Validate purchase order ID
  if (!poId) {
    throw new ValidationError('Purchase order ID is required'); // Throw error if ID missing
  }
  
  // Find purchase order
  const purchaseOrder = await PurchaseOrder.findByPk(poId); // Find by ID
  
  // Check if purchase order exists
  if (!purchaseOrder) {
    throw new NotFoundError(`Purchase order with ID ${poId} not found`); // Throw error if not found
  }
  
  // Extract update data
  const { vendor_id, status, items } = updateData; // Extract data
  
  // Validate vendor if provided
  if (vendor_id !== undefined) {
    const vendor = await Vendor.findByPk(vendor_id); // Find vendor
    if (!vendor) {
      throw new NotFoundError(`Vendor with ID ${vendor_id} not found`); // Throw error if vendor not found
    }
  }
  
  // Validate status if provided
  if (status !== undefined) {
    const validStatuses = ['DRAFT', 'CONFIRMED', 'CLOSED']; // Valid statuses
    if (!validStatuses.includes(status)) {
      throw new ValidationError(`Status must be one of: ${validStatuses.join(', ')}`); // Throw error if invalid status
    }
  }
  
  // Start transaction
  const transaction = await sequelize.transaction();
  
  try {
    // Update purchase order
    await purchaseOrder.update({
      ...(vendor_id !== undefined && { vendor_id }), // Update vendor_id if provided
      ...(status !== undefined && { status }), // Update status if provided
    }, { transaction });
    
    // Update items if provided
    if (items && Array.isArray(items)) {
      // Delete all existing items (we'll recreate them)
      // Note: This is a simple approach - in production, you might want to update/delete selectively
      await PurchaseOrderItem.destroy({
        where: { purchase_order_id: poId },
        transaction,
      });
      
      // Create new items
      if (items.length > 0) {
        for (const item of items) {
          const { 
            product_id, 
            quantity, 
            unit_cost,
            piece_length,
            piece_width,
            dimension_unit,
            pieces_count
          } = item;
          
          // Validate required fields
          if (!product_id || !quantity || unit_cost === undefined) {
            throw new ValidationError('Item must have product_id, quantity, and unit_cost');
          }
          
          // Validate product exists
          const product = await Product.findByPk(product_id, { transaction });
          if (!product) {
            throw new NotFoundError(`Product not found: ${product_id}`);
          }
          
          // Create purchase order item data
          const poItemData = {
            purchase_order_id: poId,
            product_id,
            quantity: parseFloat(quantity),
            unit_cost: parseFloat(unit_cost),
          };
          
          // Include dimension fields if provided (for dimension-based RM products)
          if (piece_length !== null && piece_length !== undefined && piece_length !== '') {
            poItemData.piece_length = parseFloat(piece_length);
          }
          if (piece_width !== null && piece_width !== undefined && piece_width !== '') {
            poItemData.piece_width = parseFloat(piece_width);
          }
          if (dimension_unit && dimension_unit !== '') {
            if (!['inch', 'cm', 'm'].includes(dimension_unit)) {
              throw new ValidationError('Dimension unit must be one of: inch, cm, m');
            }
            poItemData.dimension_unit = dimension_unit;
          }
          if (pieces_count !== null && pieces_count !== undefined && pieces_count !== '') {
            poItemData.pieces_count = parseInt(pieces_count);
          }
          
          await PurchaseOrderItem.create(poItemData, { transaction });
        }
      }
    }
    
    // Commit transaction
    await transaction.commit();
    
    logger.info(`Purchase order updated: ${poId}`); // Log PO update
    
    // Return updated purchase order
    return await getPurchaseOrder(poId); // Return PO with associations
  } catch (error) {
    // Rollback on error
    await transaction.rollback();
    throw error;
  }
};

/**
 * Delete purchase order
 * Deletes a purchase order (only if no GRNs are linked)
 * @param {number} poId - Purchase order ID
 * @returns {Promise<void>}
 */
const deletePurchaseOrder = async (poId) => {
  // Validate purchase order ID
  if (!poId) {
    throw new ValidationError('Purchase order ID is required'); // Throw error if ID missing
  }
  
  // Find purchase order
  const purchaseOrder = await PurchaseOrder.findByPk(poId); // Find by ID
  
  // Check if purchase order exists
  if (!purchaseOrder) {
    throw new NotFoundError(`Purchase order with ID ${poId} not found`); // Throw error if not found
  }
  
  // Check if GRNs are linked to this PO
  const grnCount = await GRN.count({
    where: { purchase_order_id: poId }, // Filter by PO ID
  });
  
  if (grnCount > 0) {
    throw new ValidationError(`Cannot delete purchase order with ${grnCount} linked GRN(s)`); // Throw error if GRNs exist
  }
  
  // Delete purchase order
  await purchaseOrder.destroy(); // Delete PO
  
  logger.info(`Purchase order deleted: ${poId}`); // Log PO deletion
};

/**
 * List purchase orders
 * Lists purchase orders with optional filters
 * @param {Object} filters - Filter options (vendor_id, status)
 * @param {Object} pagination - Pagination options (page, limit)
 * @returns {Promise<Object>} Paginated list of purchase orders
 */
const listPurchaseOrders = async (filters = {}, pagination = {}) => {
  // Extract filters
  const {
    vendor_id, // Vendor ID filter
    status, // Status filter
  } = filters; // Extract filters
  
  // Extract pagination options
  const page = parseInt(pagination.page, 10) || 1; // Current page (default 1)
  const limit = parseInt(pagination.limit, 10) || 50; // Items per page (default 50)
  const offset = (page - 1) * limit; // Calculate offset
  
  // Build where clause
  const where = {}; // Initialize where clause
  
  // Add vendor_id filter
  if (vendor_id) {
    where.vendor_id = vendor_id; // Filter by vendor ID
  }
  
  // Add status filter
  if (status) {
    where.status = status; // Filter by status
  }
  
  // Find purchase orders with pagination
  const { count, rows } = await PurchaseOrder.findAndCountAll({
    where, // Where clause
    include: [
      {
        model: Vendor, // Include vendor
        as: 'vendor', // Use vendor alias
      },
    ],
    limit, // Limit results
    offset, // Offset results
    order: [['created_at', 'DESC']], // Order by creation date descending
  });
  
  // Calculate pagination metadata
  const totalPages = Math.ceil(count / limit); // Total pages
  const hasNextPage = page < totalPages; // Has next page
  const hasPrevPage = page > 1; // Has previous page
  
  // Return paginated results
  return {
    purchaseOrders: rows, // Purchase orders array
    pagination: {
      page, // Current page
      limit, // Items per page
      total: count, // Total count
      totalPages, // Total pages
      hasNextPage, // Has next page
      hasPrevPage, // Has previous page
    },
  };
};

/**
 * Update purchase order status
 * Updates only the status of a purchase order
 * @param {number} poId - Purchase order ID
 * @param {string} status - New status
 * @returns {Promise<Object>} Updated purchase order
 */
const updatePurchaseOrderStatus = async (poId, status) => {
  // Update purchase order status
  return await updatePurchaseOrder(poId, { status }); // Update status only
};

// Export purchase order service functions
module.exports = {
  createPurchaseOrder,
  getPurchaseOrder,
  updatePurchaseOrder,
  deletePurchaseOrder,
  listPurchaseOrders,
  updatePurchaseOrderStatus,
};

