/**
 * Material Allocation Service Tests
 * Property-based tests for intelligent material allocation algorithms
 */

const fc = require('fast-check');
const allocationService = require('../../../services/materialAllocationService');

describe('Material Allocation Service', () => {
  // Mock BOM item factory
  const createMockBOMItem = (data) => ({
    id: data.id || 1,
    required_length: data.required_length,
    required_width: data.required_width,
    dimension_unit: data.dimension_unit,
    use_dimensions: data.use_dimensions !== false,
    isDimensionBased: function() { return this.use_dimensions; },
    ...data
  });

  // Mock RM piece factory
  const createMockRMPiece = (data) => ({
    id: data.id || Math.floor(Math.random() * 1000),
    product_id: data.product_id || 1,
    piece_number: data.piece_number || 1,
    length: data.length,
    width: data.width,
    unit: data.unit,
    status: data.status || 'FULL',
    usable_length: data.usable_length || null,
    usable_width: data.usable_width || null,
    ...data
  });

  describe('Basic Allocation Functions', () => {
    test('should find suitable pieces for requirements', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 6.0, width: 4.0, unit: 'm', status: 'FULL' }),
        createMockRMPiece({ id: 2, length: 3.0, width: 2.0, unit: 'm', status: 'USABLE', usable_length: 2.5, usable_width: 1.8 }),
        createMockRMPiece({ id: 3, length: 1.0, width: 1.0, unit: 'm', status: 'WASTE', usable_length: 0.8, usable_width: 0.8 })
      ];

      const requirements = { length: 2.0, width: 1.5, unit: 'm' };
      const result = allocationService.findSuitablePieces(pieces, requirements);

      expect(result.isValid).toBe(true);
      expect(result.suitablePieces.length).toBeGreaterThan(0);
      
      // Should find the FULL piece and USABLE piece, but not the WASTE piece (too small)
      const suitableIds = result.suitablePieces.map(sp => sp.piece.id);
      expect(suitableIds).toContain(1); // FULL piece
      expect(suitableIds).toContain(2); // USABLE piece
    });

    test('should respect status priority in allocation', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 3.0, width: 2.0, unit: 'm', status: 'WASTE', usable_length: 2.5, usable_width: 1.8 }),
        createMockRMPiece({ id: 2, length: 3.0, width: 2.0, unit: 'm', status: 'FULL' }),
        createMockRMPiece({ id: 3, length: 3.0, width: 2.0, unit: 'm', status: 'USABLE', usable_length: 2.8, usable_width: 1.9 })
      ];

      const requirements = { length: 2.0, width: 1.5, unit: 'm' };
      const result = allocationService.findSuitablePieces(pieces, requirements, {
        strategy: allocationService.ALLOCATION_STRATEGIES.FIRST_FIT
      });

      expect(result.isValid).toBe(true);
      expect(result.suitablePieces.length).toBe(3);
      
      // Should be ordered by priority: FULL (3) > USABLE (2) > WASTE (1)
      expect(result.suitablePieces[0].piece.status).toBe('FULL');
      expect(result.suitablePieces[1].piece.status).toBe('USABLE');
      expect(result.suitablePieces[2].piece.status).toBe('WASTE');
    });

    test('should allocate material for BOM item', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 6.0, width: 4.0, unit: 'm', status: 'FULL' }),
        createMockRMPiece({ id: 2, length: 4.0, width: 3.0, unit: 'm', status: 'FULL' })
      ];

      const bomItem = createMockBOMItem({
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      const result = allocationService.allocateMaterialForBOMItem(pieces, bomItem, 10);

      expect(result.isValid).toBe(true);
      expect(result.totalAllocatedUnits).toBeGreaterThan(0);
      expect(result.allocations.length).toBeGreaterThan(0);
    });

    test('should handle insufficient material', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 1.0, width: 1.0, unit: 'm', status: 'FULL' })
      ];

      const bomItem = createMockBOMItem({
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      const result = allocationService.allocateMaterialForBOMItem(pieces, bomItem, 10);

      expect(result.isValid).toBe(true);
      expect(result.isFullyAllocated).toBe(false);
      expect(result.remainingQuantity).toBeGreaterThan(0);
    });
  });

  describe('Available Dimensions Logic', () => {
    test('should get correct dimensions for FULL pieces', () => {
      const piece = createMockRMPiece({
        length: 5.0,
        width: 3.0,
        unit: 'm',
        status: 'FULL'
      });

      const dimensions = allocationService.getAvailableDimensions(piece);
      expect(dimensions).toEqual({
        length: 5.0,
        width: 3.0,
        unit: 'm'
      });
    });

    test('should get usable dimensions for USABLE pieces', () => {
      const piece = createMockRMPiece({
        length: 5.0,
        width: 3.0,
        unit: 'm',
        status: 'USABLE',
        usable_length: 4.5,
        usable_width: 2.8
      });

      const dimensions = allocationService.getAvailableDimensions(piece);
      expect(dimensions).toEqual({
        length: 4.5,
        width: 2.8,
        unit: 'm'
      });
    });

    test('should return null for SCRAP pieces', () => {
      const piece = createMockRMPiece({
        length: 5.0,
        width: 3.0,
        unit: 'm',
        status: 'SCRAP'
      });

      const dimensions = allocationService.getAvailableDimensions(piece);
      expect(dimensions).toBeNull();
    });
  });

  describe('Waste Reuse Suggestions', () => {
    test('should suggest waste piece reuse opportunities', () => {
      const wastePieces = [
        createMockRMPiece({ id: 1, length: 2.0, width: 1.5, unit: 'm', status: 'WASTE', usable_length: 1.8, usable_width: 1.3 }),
        createMockRMPiece({ id: 2, length: 1.0, width: 0.8, unit: 'm', status: 'WASTE', usable_length: 0.9, usable_width: 0.7 })
      ];

      const upcomingRequirements = [
        { length: 1.5, width: 1.0, unit: 'm' },
        { length: 0.8, width: 0.6, unit: 'm' }
      ];

      const result = allocationService.suggestWasteReuse(wastePieces, upcomingRequirements);

      expect(result.isValid).toBe(true);
      expect(result.suggestions.length).toBeGreaterThan(0);
      expect(result.suggestions[0].potentialSavings.totalSavings).toBeGreaterThan(0);
    });
  });

  /**
   * Property 5: Material Allocation Priority
   * Validates: Requirements 3.2, 5.1
   * 
   * This property ensures that material allocation follows the correct priority
   * order (FULL → USABLE → WASTE) and makes optimal allocation decisions.
   */
  describe('Property 5: Material Allocation Priority', () => {
    test('should maintain correct allocation priority across all scenarios', () => {
      return fc.assert(fc.property(
        fc.record({
          pieces: fc.array(
            fc.record({
              length: fc.float({ min: 1.0, max: 10.0 }),
              width: fc.float({ min: 1.0, max: 10.0 }),
              unit: fc.constantFrom('m', 'cm', 'inch'),
              status: fc.constantFrom('FULL', 'USABLE', 'WASTE', 'SCRAP'),
              usable_factor: fc.float({ min: 0.5, max: 0.95 })
            }),
            { minLength: 3, maxLength: 10 }
          ),
          requirements: fc.record({
            length: fc.float({ min: 0.5, max: 5.0 }),
            width: fc.float({ min: 0.5, max: 5.0 }),
            unit: fc.constantFrom('m', 'cm', 'inch')
          })
        }),
        (data) => {
          // Create mock pieces with proper usable dimensions
          const pieces = data.pieces.map((p, i) => {
            const piece = createMockRMPiece({
              id: i + 1,
              length: p.length,
              width: p.width,
              unit: p.unit,
              status: p.status
            });

            // Set usable dimensions for non-FULL pieces
            if (p.status === 'USABLE' || p.status === 'WASTE') {
              piece.usable_length = p.length * p.usable_factor;
              piece.usable_width = p.width * p.usable_factor;
            }

            return piece;
          });

          const result = allocationService.findSuitablePieces(pieces, data.requirements, {
            strategy: allocationService.ALLOCATION_STRATEGIES.FIRST_FIT
          });

          if (!result.isValid) {
            return; // Skip invalid test cases
          }

          // Property 5.1: SCRAP pieces should never be included
          const scrapPieces = result.suitablePieces.filter(sp => sp.piece.status === 'SCRAP');
          expect(scrapPieces.length).toBe(0);

          // Property 5.2: Pieces should be ordered by priority
          if (result.suitablePieces.length > 1) {
            for (let i = 0; i < result.suitablePieces.length - 1; i++) {
              const currentPriority = allocationService.STATUS_PRIORITY[result.suitablePieces[i].piece.status];
              const nextPriority = allocationService.STATUS_PRIORITY[result.suitablePieces[i + 1].piece.status];
              expect(currentPriority).toBeGreaterThanOrEqual(nextPriority);
            }
          }

          // Property 5.3: All suitable pieces should actually fit the requirements
          for (const suitablePiece of result.suitablePieces) {
            const availableDims = allocationService.getAvailableDimensions(suitablePiece.piece);
            expect(availableDims).toBeDefined();
            
            // Convert to same unit for comparison (simplified - assume same unit for this test)
            if (availableDims.unit === data.requirements.unit) {
              expect(availableDims.length).toBeGreaterThanOrEqual(data.requirements.length);
              expect(availableDims.width).toBeGreaterThanOrEqual(data.requirements.width);
            }
          }

          // Property 5.4: Priority values should be consistent
          for (const suitablePiece of result.suitablePieces) {
            const expectedPriority = allocationService.STATUS_PRIORITY[suitablePiece.piece.status];
            expect(suitablePiece.priority).toBe(expectedPriority);
          }
        }
      ), { numRuns: 100 });
    });

    test('should optimize allocation efficiency across different strategies', () => {
      return fc.assert(fc.property(
        fc.record({
          pieces: fc.array(
            fc.record({
              length: fc.float({ min: 2.0, max: 8.0 }),
              width: fc.float({ min: 2.0, max: 8.0 }),
              status: fc.constantFrom('FULL', 'USABLE', 'WASTE')
            }),
            { minLength: 2, maxLength: 6 }
          ),
          requirements: fc.record({
            length: fc.float({ min: 1.0, max: 4.0 }),
            width: fc.float({ min: 1.0, max: 4.0 })
          }),
          strategy: fc.constantFrom(
            allocationService.ALLOCATION_STRATEGIES.BEST_FIT,
            allocationService.ALLOCATION_STRATEGIES.FIRST_FIT,
            allocationService.ALLOCATION_STRATEGIES.LARGEST_FIRST
          )
        }),
        (data) => {
          // Create mock pieces
          const pieces = data.pieces.map((p, i) => {
            const piece = createMockRMPiece({
              id: i + 1,
              length: p.length,
              width: p.width,
              unit: 'm',
              status: p.status
            });

            if (p.status !== 'FULL') {
              piece.usable_length = p.length * 0.9;
              piece.usable_width = p.width * 0.9;
            }

            return piece;
          });

          const requirements = { ...data.requirements, unit: 'm' };
          const result = allocationService.findSuitablePieces(pieces, requirements, {
            strategy: data.strategy
          });

          if (!result.isValid || result.suitablePieces.length === 0) {
            return; // Skip cases with no suitable pieces
          }

          // Property 5.5: Strategy should be applied correctly
          expect(result.strategy).toBe(data.strategy);

          // Property 5.6: All pieces should have valid metrics
          for (const suitablePiece of result.suitablePieces) {
            expect(suitablePiece.metrics).toBeDefined();
            expect(suitablePiece.metrics.availableArea).toBeGreaterThan(0);
            expect(suitablePiece.metrics.requiredArea).toBeGreaterThan(0);
            expect(suitablePiece.metrics.utilizationRatio).toBeGreaterThan(0);
            expect(suitablePiece.metrics.utilizationRatio).toBeLessThanOrEqual(1);
            expect(suitablePiece.metrics.efficiencyScore).toBeDefined();
          }

          // Property 5.7: BEST_FIT should prefer higher efficiency
          if (data.strategy === allocationService.ALLOCATION_STRATEGIES.BEST_FIT && result.suitablePieces.length > 1) {
            for (let i = 0; i < result.suitablePieces.length - 1; i++) {
              const current = result.suitablePieces[i];
              const next = result.suitablePieces[i + 1];
              
              // Should be ordered by efficiency (allowing for small differences due to priority)
              if (current.priority === next.priority) {
                expect(current.metrics.efficiencyScore).toBeGreaterThanOrEqual(next.metrics.efficiencyScore - 0.1);
              }
            }
          }
        }
      ), { numRuns: 100 });
    });
  });

  /**
   * Property 6: Dimension Sufficiency Check (Extended for Allocation)
   * Validates: Requirements 3.1, 3.4
   * 
   * This property ensures that allocation decisions correctly determine
   * whether pieces can satisfy dimensional requirements.
   */
  describe('Property 6: Dimension Sufficiency Check (Allocation)', () => {
    test('should correctly determine piece suitability for all dimension combinations', () => {
      return fc.assert(fc.property(
        fc.record({
          pieceLength: fc.float({ min: 1.0, max: 10.0 }),
          pieceWidth: fc.float({ min: 1.0, max: 10.0 }),
          reqLength: fc.float({ min: 0.5, max: 10.0 }),
          reqWidth: fc.float({ min: 0.5, max: 10.0 }),
          status: fc.constantFrom('FULL', 'USABLE', 'WASTE'),
          usableFactor: fc.float({ min: 0.6, max: 0.95 })
        }),
        (data) => {
          // Create piece with appropriate dimensions
          const piece = createMockRMPiece({
            id: 1,
            length: data.pieceLength,
            width: data.pieceWidth,
            unit: 'm',
            status: data.status
          });

          if (data.status !== 'FULL') {
            piece.usable_length = data.pieceLength * data.usableFactor;
            piece.usable_width = data.pieceWidth * data.usableFactor;
          }

          const requirements = {
            length: data.reqLength,
            width: data.reqWidth,
            unit: 'm'
          };

          const result = allocationService.findSuitablePieces([piece], requirements);

          // Property 6.1: Result should always be valid for valid inputs
          expect(result.isValid).toBe(true);

          // Property 6.2: Determine expected suitability
          const availableDims = allocationService.getAvailableDimensions(piece);
          const shouldFit = availableDims && 
                           availableDims.length >= data.reqLength && 
                           availableDims.width >= data.reqWidth;

          // Property 6.3: Suitability should match mathematical expectation
          if (shouldFit) {
            expect(result.suitablePieces.length).toBe(1);
            
            const suitablePiece = result.suitablePieces[0];
            expect(suitablePiece.availableDimensions).toEqual(availableDims);
            
            // Property 6.4: Remaining dimensions should be calculated correctly
            const expectedRemainingLength = availableDims.length - data.reqLength;
            const expectedRemainingWidth = availableDims.width - data.reqWidth;
            
            expect(suitablePiece.remainingDimensions.length).toBeCloseTo(expectedRemainingLength, 6);
            expect(suitablePiece.remainingDimensions.width).toBeCloseTo(expectedRemainingWidth, 6);
          } else {
            expect(result.suitablePieces.length).toBe(0);
          }

          // Property 6.5: Metrics should be consistent with dimensions
          if (result.suitablePieces.length > 0) {
            const metrics = result.suitablePieces[0].metrics;
            const expectedAvailableArea = availableDims.length * availableDims.width;
            const expectedRequiredArea = data.reqLength * data.reqWidth;
            
            expect(metrics.availableArea).toBeCloseTo(expectedAvailableArea, 6);
            expect(metrics.requiredArea).toBeCloseTo(expectedRequiredArea, 6);
            expect(metrics.utilizationRatio).toBeCloseTo(expectedRequiredArea / expectedAvailableArea, 6);
          }
        }
      ), { numRuns: 100 });
    });
  });

  describe('BOM Allocation Feasibility', () => {
    test('should validate complete BOM allocation feasibility', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 10.0, width: 8.0, unit: 'm', status: 'FULL' }),
        createMockRMPiece({ id: 2, length: 6.0, width: 4.0, unit: 'm', status: 'FULL' })
      ];

      const bomItems = [
        createMockBOMItem({ id: 1, required_length: 2.0, required_width: 1.5, dimension_unit: 'm' }),
        createMockBOMItem({ id: 2, required_length: 1.0, required_width: 1.0, dimension_unit: 'm' })
      ];

      const result = allocationService.validateBOMAllocationFeasibility(pieces, bomItems, 5);

      expect(result.isValid).toBe(true);
      expect(result.bomItemResults.length).toBe(2);
      expect(result.overallFeasible).toBeDefined();
    });

    test('should handle mixed dimension and quantity-based BOM items', () => {
      const pieces = [
        createMockRMPiece({ id: 1, length: 5.0, width: 3.0, unit: 'm', status: 'FULL' })
      ];

      const bomItems = [
        createMockBOMItem({ id: 1, required_length: 2.0, required_width: 1.5, dimension_unit: 'm', use_dimensions: true }),
        createMockBOMItem({ id: 2, use_dimensions: false }) // Quantity-based item
      ];

      const result = allocationService.validateBOMAllocationFeasibility(pieces, bomItems, 3);

      expect(result.isValid).toBe(true);
      expect(result.bomItemResults.length).toBe(1); // Only dimension-based item should be processed
    });
  });
});

/**
 * Test Tags for Property-Based Testing:
 * Feature: dimension-based-inventory
 * Property 5: Material Allocation Priority
 * Property 6: Dimension Sufficiency Check (Extended)
 */