/**
 * Material Allocation Service
 * Implements intelligent allocation algorithm for RM pieces based on dimension requirements
 * Priority: FULL → USABLE → WASTE pieces
 */

const dimensionService = require('./dimensionValidationService');

/**
 * Piece status priority for allocation (higher number = higher priority)
 */
const STATUS_PRIORITY = {
  'FULL': 3,
  'USABLE': 2, 
  'WASTE': 1,
  'SCRAP': 0  // SCRAP pieces are not allocatable
};

/**
 * Allocation strategies
 */
const ALLOCATION_STRATEGIES = {
  BEST_FIT: 'BEST_FIT',           // Minimize waste - choose piece with smallest usable area that fits
  FIRST_FIT: 'FIRST_FIT',         // Speed - choose first piece that fits
  LARGEST_FIRST: 'LARGEST_FIRST'  // Preserve smaller pieces - choose largest piece that fits
};

/**
 * Finds available RM pieces that can satisfy dimension requirements
 * @param {Array} availablePieces - Array of RM inventory pieces
 * @param {Object} requirements - Dimension requirements {length, width, unit}
 * @param {Object} options - Allocation options
 * @returns {Object} Allocation result
 */
function findSuitablePieces(availablePieces, requirements, options = {}) {
  const {
    strategy = ALLOCATION_STRATEGIES.BEST_FIT,
    allowUnitConversion = true,
    maxResults = 10
  } = options;

  // Validate inputs
  if (!Array.isArray(availablePieces)) {
    return {
      isValid: false,
      error: 'Available pieces must be an array'
    };
  }

  const reqValidation = dimensionService.validateDimensions(
    requirements.length, 
    requirements.width, 
    requirements.unit
  );
  if (!reqValidation.isValid) {
    return {
      isValid: false,
      error: 'Invalid requirements',
      details: reqValidation.errors
    };
  }

  const suitablePieces = [];

  for (const piece of availablePieces) {
    // Skip non-allocatable pieces
    if (piece.status === 'SCRAP' || STATUS_PRIORITY[piece.status] === undefined) {
      continue;
    }

    // Get available dimensions based on piece status
    const availableDimensions = getAvailableDimensions(piece);
    if (!availableDimensions) {
      continue;
    }

    // Check if piece can accommodate requirements
    const sufficiencyResult = dimensionService.checkDimensionSufficiency(
      availableDimensions,
      requirements
    );

    if (!sufficiencyResult.isValid) {
      continue;
    }

    if (sufficiencyResult.canFit) {
      // Calculate allocation metrics
      const metrics = calculateAllocationMetrics(piece, availableDimensions, requirements, sufficiencyResult);
      
      suitablePieces.push({
        piece: piece,
        availableDimensions: availableDimensions,
        requiredDimensions: sufficiencyResult.requiredDimensions,
        remainingDimensions: sufficiencyResult.remainingDimensions,
        metrics: metrics,
        priority: STATUS_PRIORITY[piece.status]
      });
    }
  }

  // Sort pieces according to strategy
  const sortedPieces = sortPiecesByStrategy(suitablePieces, strategy);

  return {
    isValid: true,
    suitablePieces: sortedPieces.slice(0, maxResults),
    totalFound: sortedPieces.length,
    strategy: strategy,
    requirements: requirements
  };
}

/**
 * Gets available dimensions for a piece based on its status
 * @param {Object} piece - RM inventory piece
 * @returns {Object|null} Available dimensions or null if not available
 */
function getAvailableDimensions(piece) {
  if (!piece || !piece.length || !piece.width || !piece.unit) {
    return null;
  }

  switch (piece.status) {
    case 'FULL':
      return {
        length: piece.length,
        width: piece.width,
        unit: piece.unit
      };
    
    case 'USABLE':
    case 'WASTE':
      if (piece.usable_length && piece.usable_width) {
        return {
          length: piece.usable_length,
          width: piece.usable_width,
          unit: piece.unit
        };
      }
      return null;
    
    default:
      return null;
  }
}

/**
 * Calculates allocation metrics for decision making
 * @param {Object} piece - RM inventory piece
 * @param {Object} availableDimensions - Available dimensions
 * @param {Object} requirements - Required dimensions
 * @param {Object} sufficiencyResult - Sufficiency check result
 * @returns {Object} Allocation metrics
 */
function calculateAllocationMetrics(piece, availableDimensions, requirements, sufficiencyResult) {
  const availableArea = availableDimensions.length * availableDimensions.width;
  const requiredArea = sufficiencyResult.requiredDimensions.length * sufficiencyResult.requiredDimensions.width;
  const remainingArea = sufficiencyResult.remainingDimensions.length * sufficiencyResult.remainingDimensions.width;
  
  const utilizationRatio = requiredArea / availableArea;
  const wasteRatio = remainingArea / availableArea;
  
  // Efficiency score (higher is better)
  // Factors: utilization ratio, status priority, piece age (if available)
  let efficiencyScore = utilizationRatio * 100 + STATUS_PRIORITY[piece.status] * 10;
  
  // Bonus for exact fits
  if (remainingArea < 0.001) {
    efficiencyScore += 50;
  }
  
  // Penalty for very low utilization (creates too much waste)
  if (utilizationRatio < 0.3) {
    efficiencyScore -= 20;
  }

  return {
    availableArea: availableArea,
    requiredArea: requiredArea,
    remainingArea: remainingArea,
    utilizationRatio: utilizationRatio,
    wasteRatio: wasteRatio,
    efficiencyScore: efficiencyScore,
    isExactFit: remainingArea < 0.001,
    isHighUtilization: utilizationRatio >= 0.7
  };
}

/**
 * Sorts pieces according to allocation strategy
 * @param {Array} pieces - Array of suitable pieces with metrics
 * @param {string} strategy - Allocation strategy
 * @returns {Array} Sorted pieces
 */
function sortPiecesByStrategy(pieces, strategy) {
  switch (strategy) {
    case ALLOCATION_STRATEGIES.BEST_FIT:
      // Minimize waste: prefer higher utilization, then higher priority
      return pieces.sort((a, b) => {
        // First by efficiency score (includes utilization and priority)
        if (Math.abs(b.metrics.efficiencyScore - a.metrics.efficiencyScore) > 0.1) {
          return b.metrics.efficiencyScore - a.metrics.efficiencyScore;
        }
        // Then by smaller remaining area (less waste)
        return a.metrics.remainingArea - b.metrics.remainingArea;
      });
    
    case ALLOCATION_STRATEGIES.FIRST_FIT:
      // Speed: maintain original order but prioritize by status
      return pieces.sort((a, b) => {
        // First by status priority
        if (b.priority !== a.priority) {
          return b.priority - a.priority;
        }
        // Then by piece ID (original order)
        return (a.piece.id || 0) - (b.piece.id || 0);
      });
    
    case ALLOCATION_STRATEGIES.LARGEST_FIRST:
      // Preserve smaller pieces: use largest available area first
      return pieces.sort((a, b) => {
        // First by status priority
        if (b.priority !== a.priority) {
          return b.priority - a.priority;
        }
        // Then by largest available area
        return b.metrics.availableArea - a.metrics.availableArea;
      });
    
    default:
      return pieces;
  }
}

/**
 * Allocates material for a single BOM item requirement
 * @param {Array} availablePieces - Array of RM inventory pieces
 * @param {Object} bomItem - BOM item with dimension requirements
 * @param {number} productionQuantity - Number of units to produce
 * @param {Object} options - Allocation options
 * @returns {Object} Allocation result
 */
function allocateMaterialForBOMItem(availablePieces, bomItem, productionQuantity, options = {}) {
  // Validate BOM item - check if dimension-based
  const isDimensionBased = !!(
    (bomItem.required_length && bomItem.required_width && bomItem.dimension_unit) ||
    (bomItem.isDimensionBased && typeof bomItem.isDimensionBased === 'function' && bomItem.isDimensionBased())
  );
  
  if (!bomItem || !isDimensionBased) {
    return {
      isValid: false,
      error: 'BOM item must be dimension-based'
    };
  }

  if (!bomItem.required_length || !bomItem.required_width || !bomItem.dimension_unit) {
    return {
      isValid: false,
      error: 'BOM item missing dimension requirements'
    };
  }

  if (!productionQuantity || productionQuantity <= 0) {
    return {
      isValid: false,
      error: 'Production quantity must be positive'
    };
  }

  const requirements = {
    length: bomItem.required_length,
    width: bomItem.required_width,
    unit: bomItem.dimension_unit
  };

  // Find suitable pieces
  const suitableResult = findSuitablePieces(availablePieces, requirements, options);
  if (!suitableResult.isValid) {
    return suitableResult;
  }

  // Allocate pieces for the required quantity
  const allocations = [];
  let remainingQuantity = productionQuantity;
  const usedPieceIds = new Set();

  while (remainingQuantity > 0 && suitableResult.suitablePieces.length > 0) {
    // Find best available piece (not already used)
    const availablePiece = suitableResult.suitablePieces.find(sp => !usedPieceIds.has(sp.piece.id));
    
    if (!availablePiece) {
      break; // No more suitable pieces available
    }

    // Calculate how many units this piece can provide
    const unitsFromPiece = calculateUnitsFromPiece(availablePiece, requirements);
    const unitsToAllocate = Math.min(unitsFromPiece, remainingQuantity);

    allocations.push({
      piece: availablePiece.piece,
      unitsAllocated: unitsToAllocate,
      availableDimensions: availablePiece.availableDimensions,
      requiredDimensions: availablePiece.requiredDimensions,
      remainingDimensions: availablePiece.remainingDimensions,
      metrics: availablePiece.metrics,
      allocationRatio: unitsToAllocate / unitsFromPiece
    });

    usedPieceIds.add(availablePiece.piece.id);
    remainingQuantity -= unitsToAllocate;
  }

  // Calculate allocation summary
  const totalAllocatedUnits = allocations.reduce((sum, alloc) => sum + alloc.unitsAllocated, 0);
  const isFullyAllocated = remainingQuantity === 0;
  const totalWasteArea = allocations.reduce((sum, alloc) => sum + alloc.remainingDimensions.length * alloc.remainingDimensions.width, 0);

  return {
    isValid: true,
    bomItem: bomItem,
    productionQuantity: productionQuantity,
    allocations: allocations,
    totalAllocatedUnits: totalAllocatedUnits,
    remainingQuantity: remainingQuantity,
    isFullyAllocated: isFullyAllocated,
    totalWasteArea: totalWasteArea,
    utilizationEfficiency: totalAllocatedUnits / productionQuantity,
    piecesUsed: allocations.length
  };
}

/**
 * Calculates how many units a piece can provide
 * @param {Object} suitablePiece - Suitable piece with metrics
 * @param {Object} requirements - Dimension requirements
 * @returns {number} Number of units this piece can provide
 */
function calculateUnitsFromPiece(suitablePiece, requirements) {
  const available = suitablePiece.availableDimensions;
  const required = suitablePiece.requiredDimensions;
  
  // Calculate how many units can fit in each dimension
  const unitsInLength = Math.floor(available.length / required.length);
  const unitsInWidth = Math.floor(available.width / required.width);
  
  // Total units is the product (rectangular packing)
  return unitsInLength * unitsInWidth;
}

/**
 * Suggests waste piece reuse opportunities
 * @param {Array} wastePieces - Array of waste pieces
 * @param {Array} upcomingRequirements - Array of future requirements
 * @param {Object} options - Suggestion options
 * @returns {Object} Reuse suggestions
 */
function suggestWasteReuse(wastePieces, upcomingRequirements, options = {}) {
  const { maxSuggestions = 5 } = options;
  
  if (!Array.isArray(wastePieces) || !Array.isArray(upcomingRequirements)) {
    return {
      isValid: false,
      error: 'Waste pieces and requirements must be arrays'
    };
  }

  const suggestions = [];

  for (const requirement of upcomingRequirements) {
    const suitableResult = findSuitablePieces(
      wastePieces.filter(p => p.status === 'WASTE'),
      requirement,
      { strategy: ALLOCATION_STRATEGIES.BEST_FIT, maxResults: 3 }
    );

    if (suitableResult.isValid && suitableResult.suitablePieces.length > 0) {
      suggestions.push({
        requirement: requirement,
        suitableWastePieces: suitableResult.suitablePieces,
        potentialSavings: calculatePotentialSavings(suitableResult.suitablePieces, requirement)
      });
    }
  }

  // Sort by potential savings (highest first)
  suggestions.sort((a, b) => b.potentialSavings.totalSavings - a.potentialSavings.totalSavings);

  return {
    isValid: true,
    suggestions: suggestions.slice(0, maxSuggestions),
    totalSuggestions: suggestions.length
  };
}

/**
 * Calculates potential cost savings from waste reuse
 * @param {Array} suitablePieces - Suitable waste pieces
 * @param {Object} requirement - Requirement that could use waste
 * @returns {Object} Savings calculation
 */
function calculatePotentialSavings(suitablePieces, requirement) {
  if (!suitablePieces.length) {
    return { totalSavings: 0, details: [] };
  }

  const bestPiece = suitablePieces[0];
  const requiredArea = requirement.length * requirement.width;
  
  // Estimate savings (would need actual cost data in real implementation)
  const estimatedCostPerArea = 10; // Example: $10 per square unit
  const materialSavings = requiredArea * estimatedCostPerArea;
  
  // Additional savings from waste reduction
  const wasteReductionBonus = bestPiece.metrics.availableArea * estimatedCostPerArea * 0.1;
  
  return {
    totalSavings: materialSavings + wasteReductionBonus,
    materialSavings: materialSavings,
    wasteReductionBonus: wasteReductionBonus,
    requiredArea: requiredArea,
    wasteAreaReused: bestPiece.metrics.availableArea
  };
}

/**
 * Validates allocation feasibility for a complete BOM
 * @param {Array} availablePieces - Array of RM inventory pieces
 * @param {Array} bomItems - Array of BOM items
 * @param {number} productionQuantity - Number of units to produce
 * @param {Object} options - Validation options
 * @returns {Object} Feasibility result
 */
function validateBOMAllocationFeasibility(availablePieces, bomItems, productionQuantity, options = {}) {
  const { includeAllocations = false } = options;
  
  const results = [];
  let overallFeasible = true;
  let totalWasteArea = 0;

  for (const bomItem of bomItems) {
    // Check if dimension-based
    const isDimensionBased = !!(
      (bomItem.required_length && bomItem.required_width && bomItem.dimension_unit) ||
      (bomItem.isDimensionBased && typeof bomItem.isDimensionBased === 'function' && bomItem.isDimensionBased())
    );
    
    if (!isDimensionBased) {
      // Skip quantity-based items
      continue;
    }

    const allocationResult = allocateMaterialForBOMItem(
      availablePieces, 
      bomItem, 
      productionQuantity, 
      options
    );

    if (!allocationResult.isValid) {
      results.push({
        bomItem: bomItem,
        feasible: false,
        error: allocationResult.error
      });
      overallFeasible = false;
      continue;
    }

    const feasible = allocationResult.isFullyAllocated;
    if (!feasible) {
      overallFeasible = false;
    }

    totalWasteArea += allocationResult.totalWasteArea;

    results.push({
      bomItem: bomItem,
      feasible: feasible,
      allocatedUnits: allocationResult.totalAllocatedUnits,
      remainingQuantity: allocationResult.remainingQuantity,
      piecesUsed: allocationResult.piecesUsed,
      wasteArea: allocationResult.totalWasteArea,
      allocations: includeAllocations ? allocationResult.allocations : undefined
    });
  }

  return {
    isValid: true,
    overallFeasible: overallFeasible,
    productionQuantity: productionQuantity,
    bomItemResults: results,
    totalWasteArea: totalWasteArea,
    feasibleItems: results.filter(r => r.feasible).length,
    totalItems: results.length
  };
}

module.exports = {
  findSuitablePieces,
  allocateMaterialForBOMItem,
  suggestWasteReuse,
  validateBOMAllocationFeasibility,
  getAvailableDimensions,
  calculateAllocationMetrics,
  ALLOCATION_STRATEGIES,
  STATUS_PRIORITY
};