/**
 * Property-based tests for Inventory Calculation Service
 * Tests universal correctness properties with 100+ iterations each
 */

const inventoryService = require('../../../services/inventoryCalculationService');

describe('Inventory Calculation Service - Property Tests', () => {
  
  // Mock factory for creating inventory pieces
  const createMockInventoryPiece = (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,
    created_at: data.created_at || new Date(),
    updated_at: data.updated_at || new Date(),
    ...data
  });

  /**
   * Property 11: Inventory Calculation Accuracy
   * Validates: Requirements 6.1, 6.2
   */
  test('Property 11: Inventory Calculation Accuracy (100 iterations)', () => {
    for (let i = 0; i < 100; i++) {
      const numPieces = Math.floor(Math.random() * 20) + 1; // 1-20 pieces
      const pieces = [];
      
      let expectedTotalPieces = 0;
      let expectedFullPieces = 0;
      let expectedUsablePieces = 0;
      let expectedWastePieces = 0;
      let expectedScrapPieces = 0;
      let expectedTotalArea = 0;

      for (let j = 0; j < numPieces; j++) {
        const length = Math.random() * 5 + 0.5; // 0.5-5.5m
        const width = Math.random() * 4 + 0.5;  // 0.5-4.5m
        const unit = ['m', 'cm', 'inch'][Math.floor(Math.random() * 3)];
        const status = ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)];

        const piece = createMockInventoryPiece({
          id: j + 1,
          length: length,
          width: width,
          unit: unit,
          status: status
        });

        // Set usable dimensions for non-FULL pieces
        if (status === 'USABLE') {
          const usableFactor = Math.random() * 0.4 + 0.6; // 0.6-1.0
          piece.usable_length = length * usableFactor;
          piece.usable_width = width * usableFactor;
        }

        pieces.push(piece);

        // Track expected counts
        expectedTotalPieces++;
        switch (status) {
          case 'FULL': expectedFullPieces++; break;
          case 'USABLE': expectedUsablePieces++; break;
          case 'WASTE': expectedWastePieces++; break;
          case 'SCRAP': expectedScrapPieces++; break;
        }

        // Calculate expected area (convert to meters for consistency)
        let areaInMeters = length * width;
        if (unit === 'cm') {
          areaInMeters = areaInMeters / 10000; // cm² to m²
        } else if (unit === 'inch') {
          areaInMeters = areaInMeters * 0.00064516; // inch² to m²
        }
        expectedTotalArea += areaInMeters;
      }

      const result = inventoryService.calculateInventorySummary(pieces, { targetUnit: 'm' });

      // Property 11.1: Result should be valid
      expect(result.isValid).toBe(true);

      // Property 11.2: Total piece count should match
      expect(result.summary.totalPieces).toBe(expectedTotalPieces);

      // Property 11.3: Status counts should match
      expect(result.summary.piecesByStatus.FULL).toBe(expectedFullPieces);
      expect(result.summary.piecesByStatus.USABLE).toBe(expectedUsablePieces);
      expect(result.summary.piecesByStatus.WASTE).toBe(expectedWastePieces);
      expect(result.summary.piecesByStatus.SCRAP).toBe(expectedScrapPieces);

      // Property 11.4: Total area should be approximately correct (allowing for conversion rounding)
      expect(result.summary.totalAreas.totalArea).toBeCloseTo(expectedTotalArea, 2);

      // Property 11.5: Area components should sum to total
      const calculatedTotal = result.summary.totalAreas.fullArea + 
                             result.summary.totalAreas.usableArea + 
                             result.summary.totalAreas.wasteArea + 
                             result.summary.totalAreas.scrapArea;
      
      // Note: usableArea includes fullArea for FULL pieces, so we need to adjust
      const adjustedTotal = result.summary.totalAreas.usableArea + 
                           result.summary.totalAreas.wasteArea + 
                           result.summary.totalAreas.scrapArea;
      expect(adjustedTotal).toBeCloseTo(result.summary.totalAreas.totalArea, 2);

      // Property 11.6: Utilization statistics should be valid percentages
      expect(result.summary.utilizationStatistics.utilizationRate).toBeGreaterThanOrEqual(0);
      expect(result.summary.utilizationStatistics.utilizationRate).toBeLessThanOrEqual(1);
      expect(result.summary.utilizationStatistics.wasteRate).toBeGreaterThanOrEqual(0);
      expect(result.summary.utilizationStatistics.wasteRate).toBeLessThanOrEqual(1);
      expect(result.summary.utilizationStatistics.scrapRate).toBeGreaterThanOrEqual(0);
      expect(result.summary.utilizationStatistics.scrapRate).toBeLessThanOrEqual(1);

      // Property 11.7: Dimension variations should be tracked correctly
      expect(Array.isArray(result.summary.dimensionVariations)).toBe(true);
      expect(result.summary.dimensionVariations.length).toBeGreaterThan(0);
    }
  });

  /**
   * Property 3: Area Calculation Consistency
   * Validates: Requirements 1.6
   */
  test('Property 3: Area Calculation Consistency (100 iterations)', () => {
    for (let i = 0; i < 100; i++) {
      const length = Math.random() * 10 + 0.1; // 0.1-10.1m
      const width = Math.random() * 8 + 0.1;   // 0.1-8.1m
      const unit = ['m', 'cm', 'inch'][Math.floor(Math.random() * 3)];
      const status = ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)];

      const piece = createMockInventoryPiece({
        id: i + 1,
        length: length,
        width: width,
        unit: unit,
        status: status
      });

      // Set usable dimensions for USABLE pieces
      if (status === 'USABLE') {
        const usableFactor = Math.random() * 0.5 + 0.5; // 0.5-1.0
        piece.usable_length = length * usableFactor;
        piece.usable_width = width * usableFactor;
      }

      // Test area calculation in original unit
      const originalAreaResult = inventoryService.calculatePieceAreas(piece);
      expect(originalAreaResult.isValid).toBe(true);

      // Property 3.1: Area should equal length × width
      const expectedArea = length * width;
      expect(originalAreaResult.totalArea).toBeCloseTo(expectedArea, 6);

      // Property 3.2: Area components should be non-negative
      expect(originalAreaResult.fullArea).toBeGreaterThanOrEqual(0);
      expect(originalAreaResult.usableArea).toBeGreaterThanOrEqual(0);
      expect(originalAreaResult.wasteArea).toBeGreaterThanOrEqual(0);
      expect(originalAreaResult.scrapArea).toBeGreaterThanOrEqual(0);

      // Property 3.3: Only one area type should be non-zero based on status
      switch (status) {
        case 'FULL':
          expect(originalAreaResult.fullArea).toBeCloseTo(expectedArea, 6);
          expect(originalAreaResult.usableArea).toBeCloseTo(expectedArea, 6);
          expect(originalAreaResult.wasteArea).toBe(0);
          expect(originalAreaResult.scrapArea).toBe(0);
          break;
        case 'USABLE':
          expect(originalAreaResult.fullArea).toBe(0);
          expect(originalAreaResult.usableArea).toBeGreaterThan(0);
          expect(originalAreaResult.wasteArea).toBeGreaterThanOrEqual(0);
          expect(originalAreaResult.scrapArea).toBe(0);
          // Usable + waste should equal total
          expect(originalAreaResult.usableArea + originalAreaResult.wasteArea)
            .toBeCloseTo(expectedArea, 6);
          break;
        case 'WASTE':
          expect(originalAreaResult.fullArea).toBe(0);
          expect(originalAreaResult.usableArea).toBe(0);
          expect(originalAreaResult.wasteArea).toBeCloseTo(expectedArea, 6);
          expect(originalAreaResult.scrapArea).toBe(0);
          break;
        case 'SCRAP':
          expect(originalAreaResult.fullArea).toBe(0);
          expect(originalAreaResult.usableArea).toBe(0);
          expect(originalAreaResult.wasteArea).toBe(0);
          expect(originalAreaResult.scrapArea).toBeCloseTo(expectedArea, 6);
          break;
      }

      // Property 3.4: Unit conversion should preserve area ratios
      const targetUnits = ['m', 'cm', 'inch'].filter(u => u !== unit);
      for (const targetUnit of targetUnits) {
        const convertedAreaResult = inventoryService.calculatePieceAreas(piece, { targetUnit });
        expect(convertedAreaResult.isValid).toBe(true);

        // Calculate conversion factor
        let conversionFactor = 1;
        if (unit === 'm' && targetUnit === 'cm') {
          conversionFactor = 10000; // m² to cm²
        } else if (unit === 'm' && targetUnit === 'inch') {
          conversionFactor = 1550.0031; // m² to inch²
        } else if (unit === 'cm' && targetUnit === 'm') {
          conversionFactor = 0.0001; // cm² to m²
        } else if (unit === 'cm' && targetUnit === 'inch') {
          conversionFactor = 0.15500031; // cm² to inch²
        } else if (unit === 'inch' && targetUnit === 'm') {
          conversionFactor = 0.00064516; // inch² to m²
        } else if (unit === 'inch' && targetUnit === 'cm') {
          conversionFactor = 6.4516; // inch² to cm²
        }

        // Areas should be proportionally converted
        expect(convertedAreaResult.totalArea)
          .toBeCloseTo(originalAreaResult.totalArea * conversionFactor, 2);
      }

      // Property 3.5: Area calculation should be deterministic
      const secondCalculation = inventoryService.calculatePieceAreas(piece);
      expect(secondCalculation.totalArea).toBeCloseTo(originalAreaResult.totalArea, 10);
      expect(secondCalculation.fullArea).toBeCloseTo(originalAreaResult.fullArea, 10);
      expect(secondCalculation.usableArea).toBeCloseTo(originalAreaResult.usableArea, 10);
      expect(secondCalculation.wasteArea).toBeCloseTo(originalAreaResult.wasteArea, 10);
      expect(secondCalculation.scrapArea).toBeCloseTo(originalAreaResult.scrapArea, 10);
    }
  });

  /**
   * Property Test: Piece Counting Accuracy
   */
  test('Property: Piece Counting Accuracy (100 iterations)', () => {
    for (let i = 0; i < 100; i++) {
      const numPieces = Math.floor(Math.random() * 50) + 1; // 1-50 pieces
      const pieces = [];
      const expectedCounts = {
        FULL: 0,
        USABLE: 0,
        WASTE: 0,
        SCRAP: 0,
        total: 0
      };

      for (let j = 0; j < numPieces; j++) {
        const status = ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)];
        const piece = createMockInventoryPiece({
          id: j + 1,
          length: Math.random() * 5 + 0.5,
          width: Math.random() * 4 + 0.5,
          unit: 'm',
          status: status
        });

        pieces.push(piece);
        expectedCounts[status]++;
        expectedCounts.total++;
      }

      const result = inventoryService.countPiecesByStatus(pieces);

      expect(result.isValid).toBe(true);
      expect(result.counts.FULL).toBe(expectedCounts.FULL);
      expect(result.counts.USABLE).toBe(expectedCounts.USABLE);
      expect(result.counts.WASTE).toBe(expectedCounts.WASTE);
      expect(result.counts.SCRAP).toBe(expectedCounts.SCRAP);
      expect(result.counts.total).toBe(expectedCounts.total);
      expect(result.filteredCount).toBe(numPieces);
      expect(result.originalCount).toBe(numPieces);
    }
  });

  /**
   * Property Test: Inventory Report Generation
   */
  test('Property: Inventory Report Generation (100 iterations)', () => {
    for (let i = 0; i < 100; i++) {
      const numPieces = Math.floor(Math.random() * 30) + 1; // 1-30 pieces
      const pieces = [];

      for (let j = 0; j < numPieces; j++) {
        const piece = createMockInventoryPiece({
          id: j + 1,
          product_id: Math.floor(Math.random() * 5) + 1, // 1-5 products
          length: Math.random() * 5 + 0.5,
          width: Math.random() * 4 + 0.5,
          unit: ['m', 'cm', 'inch'][Math.floor(Math.random() * 3)],
          status: ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)]
        });

        pieces.push(piece);
      }

      const result = inventoryService.generateInventoryReport(pieces, {
        includeUtilization: true,
        includeAging: false,
        includeSuggestions: true
      });

      expect(result.isValid).toBe(true);
      expect(result.report).toBeDefined();
      expect(result.report.summary).toBeDefined();
      expect(result.report.statusCounts).toBeDefined();
      expect(result.report.dimensionBreakdown).toBeDefined();
      expect(result.report.utilizationAnalysis).toBeDefined();
      expect(result.report.optimizationSuggestions).toBeDefined();
      expect(result.report.generatedAt).toBeInstanceOf(Date);

      // Verify report consistency
      expect(result.report.summary.totalPieces).toBe(numPieces);
      expect(result.report.statusCounts.total).toBe(numPieces);
    }
  });

  /**
   * Property Test: Product Grouping Accuracy
   */
  test('Property: Product Grouping Accuracy (100 iterations)', () => {
    for (let i = 0; i < 100; i++) {
      const numProducts = Math.floor(Math.random() * 10) + 1; // 1-10 products
      const pieces = [];
      const expectedProductCounts = new Map();

      for (let productId = 1; productId <= numProducts; productId++) {
        const piecesPerProduct = Math.floor(Math.random() * 10) + 1; // 1-10 pieces per product
        expectedProductCounts.set(productId, piecesPerProduct);

        for (let j = 0; j < piecesPerProduct; j++) {
          const piece = createMockInventoryPiece({
            id: pieces.length + 1,
            product_id: productId,
            length: Math.random() * 5 + 0.5,
            width: Math.random() * 4 + 0.5,
            unit: 'm',
            status: ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)]
          });

          pieces.push(piece);
        }
      }

      const result = inventoryService.calculateInventorySummary(pieces, { groupByProduct: true });

      expect(result.isValid).toBe(true);
      expect(result.productSummaries).toBeDefined();
      expect(result.overallSummary).toBeDefined();

      // Verify product grouping
      expect(Object.keys(result.productSummaries).length).toBe(numProducts);
      expect(result.overallSummary.totalProducts).toBe(numProducts);
      expect(result.overallSummary.totalPieces).toBe(pieces.length);

      // Verify each product summary
      for (const [productId, expectedCount] of expectedProductCounts) {
        const productSummary = result.productSummaries[productId];
        expect(productSummary).toBeDefined();
        expect(productSummary.totalPieces).toBe(expectedCount);
      }
    }
  });

  /**
   * Edge Cases and Error Handling
   */
  test('should handle empty inventory arrays', () => {
    const result = inventoryService.calculateInventorySummary([]);
    expect(result.isValid).toBe(true);
    expect(result.summary.totalPieces).toBe(0);
    expect(result.summary.totalAreas.totalArea).toBe(0);
  });

  test('should handle invalid piece data gracefully', () => {
    const pieces = [
      createMockInventoryPiece({ length: 2, width: 3, unit: 'm', status: 'FULL' }),
      { invalid: 'piece' }, // Invalid piece
      createMockInventoryPiece({ length: 1, width: 2, unit: 'cm', status: 'WASTE' })
    ];

    const result = inventoryService.calculateInventorySummary(pieces);
    expect(result.isValid).toBe(true);
    expect(result.summary.totalPieces).toBe(2); // Only valid pieces counted
  });

  test('should validate piece data correctly', () => {
    // Valid piece
    const validPiece = createMockInventoryPiece({
      length: 2,
      width: 3,
      unit: 'm',
      status: 'FULL'
    });
    expect(inventoryService.validateInventoryPiece(validPiece).isValid).toBe(true);

    // Invalid pieces
    expect(inventoryService.validateInventoryPiece(null).isValid).toBe(false);
    expect(inventoryService.validateInventoryPiece({}).isValid).toBe(false);
    expect(inventoryService.validateInventoryPiece({ length: 2 }).isValid).toBe(false);
    expect(inventoryService.validateInventoryPiece({ 
      length: 2, width: 3, unit: 'm', status: 'INVALID' 
    }).isValid).toBe(false);
  });
});