/**
 * BOM Service
 * Business logic for Bill of Materials (BOM) management
 */

// Import BOM and related models (variants removed - using fg_product_id)
const { BOM, BOMItem, 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');
// Import dimension-based BOM services
const bomDimensionService = require('../../../services/bomDimensionService');

/**
 * Create BOM
 * Creates a new BOM with items (variants removed - using fg_product_id)
 * @param {Object} bomData - BOM data (fg_product_id, items)
 * @returns {Promise<Object>} Created BOM with items
 */
const createBOM = async (bomData) => {
  // Extract BOM data (category and type removed - derived from Product)
  const { fg_product_id, items = [] } = bomData;
  
  // Validate required fields
  if (!fg_product_id) {
    throw new ValidationError('Finished good product ID is required'); // Throw error if fg_product_id missing
  }
  
  // Validate items
  if (!items || items.length === 0) {
    throw new ValidationError('BOM must have at least one item'); // Throw error if no items
  }
  
  // Validate finished good product exists and is FG type
  const fgProduct = await Product.findByPk(fg_product_id); // Find product
  if (!fgProduct) {
    throw new NotFoundError(`Product with ID ${fg_product_id} not found`); // Throw error if product not found
  }
  
  if (fgProduct.product_type !== 'FG') {
    throw new ValidationError('Finished good product must be of type FG (Finished Goods)'); // Throw error if not FG
  }
  
  // Check if BOM already exists for this product
  const existingBOM = await BOM.findOne({
    where: { fg_product_id }, // Find by product ID (variants removed)
  });
  
  if (existingBOM) {
    throw new ValidationError(`BOM already exists for product ${fg_product_id}`); // Throw error if BOM exists
  }
  
  // Validate BOM items
  for (const item of items) {
    const { rm_product_id, quantity_per_unit, required_length, required_width, dimension_unit } = item;
    
    // Validate required fields
    if (!rm_product_id) {
      throw new ValidationError('BOM item must have rm_product_id');
    }
    
    // Validate raw material product exists
    const rmProduct = await Product.findByPk(rm_product_id);
    if (!rmProduct) {
      throw new NotFoundError(`Raw material product with ID ${rm_product_id} not found`);
    }
    
    // Validate that raw material is RM type
    if (rmProduct.product_type !== 'RM') {
      throw new ValidationError(`Product ${rm_product_id} must be a Raw Material (RM) product`);
    }
    
    // Derive use_dimensions from product (for validation service compatibility)
    const use_dimensions = rmProduct.track_by_dimensions === true;
    const itemForValidation = { ...item, use_dimensions };
    
    // Validate dimension requirements using dimension service
    const dimensionValidation = bomDimensionService.validateBOMItemDimensions(itemForValidation, rmProduct);
    if (!dimensionValidation.isValid) {
      throw new ValidationError(`BOM item validation failed for ${rmProduct.name}: ${dimensionValidation.error}`);
    }
    
    // Validate based on whether product tracks by dimensions (derived from product)
    if (use_dimensions) {
      // Dimension-based validation
      if (!required_length || !required_width || !dimension_unit) {
        throw new ValidationError(`Dimension-based BOM item for ${rmProduct.name} requires required_length, required_width, and dimension_unit`);
      }
      
      if (parseFloat(required_length) <= 0 || parseFloat(required_width) <= 0) {
        throw new ValidationError('Required dimensions must be greater than 0');
      }
      
      // Ensure quantity_per_unit is not set for dimension-based items
      if (quantity_per_unit !== null && quantity_per_unit !== undefined) {
        throw new ValidationError('Dimension-based BOM items should not have quantity_per_unit set');
      }
    } else {
      // Quantity-based validation
      if (!quantity_per_unit || quantity_per_unit === null || quantity_per_unit === undefined) {
        throw new ValidationError(`Quantity-based BOM item for ${rmProduct.name} requires quantity_per_unit`);
      }
      
      if (parseFloat(quantity_per_unit) <= 0) {
        throw new ValidationError('Quantity per unit must be greater than 0');
      }
      
      // Ensure dimension fields are not set for quantity-based items
      if (required_length !== null && required_length !== undefined) {
        throw new ValidationError('Quantity-based BOM items should not have dimension fields set');
      }
    }
  }
  
  // Start database transaction for ACID compliance
  const transaction = await sequelize.transaction();
  
  try {
    // Create BOM (category and type removed - derived from Product)
    const bom = await BOM.create({
      fg_product_id, // Finished good product ID
    }, { transaction });
    
    // Create BOM items
    const bomItems = []; // Array to store created BOM items
    for (const item of items) {
      const { rm_product_id, quantity_per_unit, required_length, required_width, dimension_unit, item_type } = item;
      
      // Get RM product to derive use_dimensions
      const rmProduct = await Product.findByPk(rm_product_id, { transaction });
      if (!rmProduct) {
        throw new NotFoundError(`Raw material product with ID ${rm_product_id} not found`);
      }
      
      // Prepare BOM item data (use_dimensions removed - derive from product.track_by_dimensions)
      const bomItemData = {
        bom_id: bom.id, // BOM ID
        rm_product_id, // Raw material product ID
        item_type: item_type || null, // Item type (FABRIC, BUTTONS, ZIPPER, LINING, ELASTIC)
      };
      
      // Determine if dimension-based or quantity-based from product
      const isDimensionBased = rmProduct.track_by_dimensions === true;
      
      if (isDimensionBased) {
        // Dimension-based RM (fabric, lining)
        bomItemData.required_length = parseFloat(required_length);
        bomItemData.required_width = parseFloat(required_width);
        bomItemData.dimension_unit = dimension_unit;
        bomItemData.quantity_per_unit = null; // NULL for dimension-based items
      } else {
        // Quantity-based RM (buttons, zippers, elastic)
        bomItemData.quantity_per_unit = parseFloat(quantity_per_unit);
        bomItemData.required_length = null; // NULL for quantity-based items
        bomItemData.required_width = null;
        bomItemData.dimension_unit = null;
      }
      
      // Create BOM item in transaction
      const bomItem = await BOMItem.create(bomItemData, { transaction });
      bomItems.push(bomItem);
    }
    
    // Commit transaction
    await transaction.commit();
    
    logger.info(`BOM created: ${bom.id} for product ${fg_product_id} with ${bomItems.length} items`);
    
    // Return BOM with items and associations
    return await getBOM(bom.id);
  } catch (error) {
    // Rollback transaction on error
    await transaction.rollback();
    logger.error(`BOM creation failed: ${error.message}`);
    throw error;
  }
};

/**
 * Get BOM by ID
 * Retrieves a BOM by ID with items
 * @param {number} bomId - BOM ID
 * @returns {Promise<Object>} BOM with items
 */
const getBOM = async (bomId) => {
  // Validate BOM ID
  if (!bomId) {
    throw new ValidationError('BOM ID is required'); // Throw error if ID missing
  }
  
  // Find BOM with associations
  const bom = await BOM.findByPk(bomId, {
    include: [
      {
        model: Product, // Include finished good product (variants removed)
        as: 'finishedGoodProduct', // Use finishedGoodProduct alias
      },
      {
        model: BOMItem, // Include BOM items
        as: 'items', // Use items alias
        include: [
          {
            model: Product, // Include raw material product
            as: 'rawMaterial', // Use rawMaterial alias
          },
        ],
      },
    ],
  });
  
  // Check if BOM exists
  if (!bom) {
    throw new NotFoundError(`BOM with ID ${bomId} not found`); // Throw error if not found
  }
  
  // Return BOM
  return bom;
};

/**
 * Get BOM by product ID
 * Retrieves a BOM by finished good product ID (variants removed)
 * @param {number} fgProductId - Finished good product ID
 * @returns {Promise<Object>} BOM with items
 */
const getBOMByProduct = async (fgProductId) => {
  // Validate product ID
  if (!fgProductId) {
    throw new ValidationError('Finished good product ID is required'); // Throw error if ID missing
  }
  
  // Find BOM by product ID
  const bom = await BOM.findOne({
    where: { fg_product_id: fgProductId }, // Find by product ID (variants removed)
    include: [
      {
        model: Product, // Include finished good product
        as: 'finishedGoodProduct', // Use finishedGoodProduct alias
      },
      {
        model: BOMItem, // Include BOM items
        as: 'items', // Use items alias
        include: [
          {
            model: Product, // Include raw material product
            as: 'rawMaterial', // Use rawMaterial alias
          },
        ],
      },
    ],
  });
  
  // Check if BOM exists
  if (!bom) {
    throw new NotFoundError(`BOM for product ${fgProductId} not found`); // Throw error if not found
  }
  
  // Return BOM
  return bom;
};

/**
 * Get BOM by variant ID (DEPRECATED - kept for backward compatibility)
 * Use getBOMByProduct instead
 * @deprecated
 */
const getBOMByVariant = async (fgVariantId) => {
  logger.warn('getBOMByVariant is deprecated. Use getBOMByProduct instead.');
  // For backward compatibility, we can't really map variant to product without a lookup
  // This should ideally throw an error or be removed after migration
  throw new ValidationError('getBOMByVariant is deprecated. Please use getBOMByProduct with fg_product_id instead.');
};

/**
 * Update BOM
 * Updates a BOM (can update items by replacing them)
 * @param {number} bomId - BOM ID
 * @param {Object} updateData - Update data (items to replace)
 * @returns {Promise<Object>} Updated BOM
 */
const updateBOM = async (bomId, updateData) => {
  // Validate BOM ID
  if (!bomId) {
    throw new ValidationError('BOM ID is required'); // Throw error if ID missing
  }
  
  // Find BOM
  const bom = await BOM.findByPk(bomId); // Find by ID
  
  // Check if BOM exists
  if (!bom) {
    throw new NotFoundError(`BOM with ID ${bomId} not found`); // Throw error if not found
  }
  
  // Extract update data (category and type removed - derived from Product)
  const { items } = updateData;
  
  // If items provided, replace BOM items in transaction
  if (items && Array.isArray(items)) {
    // Validate items (same validation as createBOM)
    for (const item of items) {
      const { rm_product_id, quantity_per_unit, required_length, required_width, dimension_unit } = item;
      
      if (!rm_product_id) {
        throw new ValidationError('BOM item must have rm_product_id');
      }
      
      const rmProduct = await Product.findByPk(rm_product_id);
      if (!rmProduct) {
        throw new NotFoundError(`Raw material product with ID ${rm_product_id} not found`);
      }
      
      if (rmProduct.product_type !== 'RM') {
        throw new ValidationError(`Product ${rm_product_id} must be a Raw Material (RM) product`);
      }
      
      const use_dimensions = rmProduct.track_by_dimensions === true;
      const itemForValidation = { ...item, use_dimensions };
      const dimensionValidation = bomDimensionService.validateBOMItemDimensions(itemForValidation, rmProduct);
      if (!dimensionValidation.isValid) {
        throw new ValidationError(`BOM item validation failed for ${rmProduct.name}: ${dimensionValidation.error}`);
      }
      
      if (use_dimensions) {
        if (!required_length || !required_width || !dimension_unit) {
          throw new ValidationError(`Dimension-based BOM item for ${rmProduct.name} requires required_length, required_width, and dimension_unit`);
        }
        if (parseFloat(required_length) <= 0 || parseFloat(required_width) <= 0) {
          throw new ValidationError('Required dimensions must be greater than 0');
        }
        if (quantity_per_unit !== null && quantity_per_unit !== undefined) {
          throw new ValidationError('Dimension-based BOM items should not have quantity_per_unit set');
        }
      } else {
        if (!quantity_per_unit || quantity_per_unit === null || quantity_per_unit === undefined) {
          throw new ValidationError(`Quantity-based BOM item for ${rmProduct.name} requires quantity_per_unit`);
        }
        if (parseFloat(quantity_per_unit) <= 0) {
          throw new ValidationError('Quantity per unit must be greater than 0');
        }
        if (required_length !== null && required_length !== undefined) {
          throw new ValidationError('Quantity-based BOM items should not have dimension fields set');
        }
      }
    }
    
    // Start transaction for ACID compliance
    const transaction = await sequelize.transaction();
    
    try {
      // Delete existing BOM items
      await BOMItem.destroy({
        where: { bom_id: bom.id },
        transaction,
      });
      
      // Create new BOM items
      for (const item of items) {
        const { rm_product_id, quantity_per_unit, required_length, required_width, dimension_unit, item_type } = item;
        
        // Get RM product to derive use_dimensions
        const rmProduct = await Product.findByPk(rm_product_id, { transaction });
        const isDimensionBased = rmProduct.track_by_dimensions === true;
        
        const bomItemData = {
          bom_id: bom.id,
          rm_product_id,
          item_type: item_type || null,
        };
        
        if (isDimensionBased) {
          bomItemData.required_length = parseFloat(required_length);
          bomItemData.required_width = parseFloat(required_width);
          bomItemData.dimension_unit = dimension_unit;
          bomItemData.quantity_per_unit = null;
        } else {
          bomItemData.quantity_per_unit = parseFloat(quantity_per_unit);
          bomItemData.required_length = null;
          bomItemData.required_width = null;
          bomItemData.dimension_unit = null;
        }
        
        await BOMItem.create(bomItemData, { transaction });
      }
      
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      logger.error(`BOM update failed: ${error.message}`);
      throw error;
    }
  }
  
  logger.info(`BOM updated: ${bomId}`);
  
  // Return updated BOM
  return await getBOM(bomId);
};

/**
 * Delete BOM
 * Deletes a BOM (items will be cascade deleted)
 * @param {number} bomId - BOM ID
 * @returns {Promise<void>}
 */
const deleteBOM = async (bomId) => {
  // Validate BOM ID
  if (!bomId) {
    throw new ValidationError('BOM ID is required'); // Throw error if ID missing
  }
  
  // Find BOM
  const bom = await BOM.findByPk(bomId); // Find by ID
  
  // Check if BOM exists
  if (!bom) {
    throw new NotFoundError(`BOM with ID ${bomId} not found`); // Throw error if not found
  }
  
  // Delete BOM (items will be cascade deleted)
  await bom.destroy(); // Delete BOM
  
  logger.info(`BOM deleted: ${bomId}`); // Log BOM deletion
};

/**
 * List BOMs
 * Lists BOMs with optional filters
 * @param {Object} filters - Filter options (fg_product_id)
 * @param {Object} pagination - Pagination options (page, limit)
 * @returns {Promise<Object>} Paginated list of BOMs
 */
const listBOMs = async (filters = {}, pagination = {}) => {
  // Extract filters
  const {
    fg_product_id, // Finished good product ID filter (variants removed)
  } = 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 fg_product_id filter (variants removed)
  if (fg_product_id) {
    where.fg_product_id = fg_product_id; // Filter by product ID
  }
  
  // Find BOMs with pagination
  const { count, rows } = await BOM.findAndCountAll({
    where, // Where clause
    include: [
      {
        model: Product, // Include finished good product (variants removed)
        as: 'finishedGoodProduct', // Use finishedGoodProduct alias
      },
      {
        model: BOMItem, // Include BOM items for count
        as: 'items', // Use items alias
        required: false, // Left join (BOMs may not have items yet)
        attributes: ['id'], // Only get ID for counting
      },
    ],
    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 {
    boms: rows, // BOMs array
    pagination: {
      page, // Current page
      limit, // Items per page
      total: count, // Total count
      totalPages, // Total pages
      hasNextPage, // Has next page
      hasPrevPage, // Has previous page
    },
  };
};

/**
 * Validate BOM feasibility for production
 * Checks if BOM requirements can be satisfied with available inventory
 * @param {number} bomId - BOM ID
 * @param {number} productionQuantity - Quantity to produce
 * @returns {Promise<Object>} Feasibility analysis
 */
const validateBOMFeasibility = async (bomId, productionQuantity) => {
  // Get BOM with items
  const bom = await getBOM(bomId);
  
  // Use dimension service to validate feasibility
  return await bomDimensionService.validateBOMFeasibility(bom, productionQuantity);
};

/**
 * Generate dimension requirements summary for BOM
 * @param {number} bomId - BOM ID
 * @param {number} productionQuantity - Quantity to produce
 * @returns {Promise<Object>} Requirements summary
 */
const getBOMDimensionRequirements = async (bomId, productionQuantity) => {
  // Get BOM with items
  const bom = await getBOM(bomId);
  
  // Generate requirements summary
  return bomDimensionService.generateDimensionRequirementsSummary(bom, productionQuantity);
};

/**
 * Validate mixed BOM (dimension + quantity based items)
 * @param {Array} bomItems - Array of BOM item data
 * @returns {Object} Validation result
 */
const validateMixedBOM = (bomItems) => {
  return bomDimensionService.validateMixedBOM(bomItems);
};

/**
 * Allocate materials for BOM production
 * @param {number} bomId - BOM ID
 * @param {number} productionQuantity - Quantity to produce
 * @param {string} allocationStrategy - Allocation strategy
 * @returns {Promise<Object>} Allocation result
 */
const allocateMaterialsForBOM = async (bomId, productionQuantity, allocationStrategy = 'BEST_FIT') => {
  // Get BOM with items
  const bom = await getBOM(bomId);
  
  // Use dimension service to allocate materials
  return await bomDimensionService.allocateMaterialsForBOM(bom, productionQuantity, allocationStrategy);
};

/**
 * Scale BOM requirements for different production quantities
 * @param {number} bomId - BOM ID
 * @param {number} baseQuantity - Base production quantity
 * @param {number} targetQuantity - Target production quantity
 * @returns {Promise<Object>} Scaled requirements
 */
const scaleBOMRequirements = async (bomId, baseQuantity, targetQuantity) => {
  // Get BOM with items
  const bom = await getBOM(bomId);
  
  // Use dimension service to scale requirements
  return bomDimensionService.scaleBOMRequirements(bom, baseQuantity, targetQuantity);
};

// Export BOM service functions
module.exports = {
  createBOM,
  getBOM,
  getBOMByProduct, // Variants removed - use getBOMByProduct instead of getBOMByVariant
  getBOMByVariant, // Deprecated - kept for backward compatibility
  updateBOM,
  deleteBOM,
  listBOMs,
  validateBOMFeasibility,
  getBOMDimensionRequirements,
  validateMixedBOM,
  allocateMaterialsForBOM,
  scaleBOMRequirements,
};

