/**
 * Standalone Material Utilization Service Test Runner
 * Runs material utilization tests without Jest environment issues
 */

const utilizationService = require('./services/materialUtilizationService');

// Mock console for test output
const testResults = [];

function mockTest(name, testFn) {
  return new Promise(async (resolve) => {
    try {
      await testFn();
      testResults.push({ name, status: 'PASS' });
      console.log(`✓ ${name}`);
      resolve();
    } catch (error) {
      testResults.push({ name, status: 'FAIL', error: error.message });
      console.log(`✗ ${name}: ${error.message}`);
      resolve();
    }
  });
}

function mockDescribe(name, describeFn) {
  console.log(`\n${name}`);
  return describeFn();
}

function mockExpected(actual) {
  const expectObj = {
    toBe: (expected) => {
      if (actual !== expected) {
        throw new Error(`Expected ${expected}, got ${actual}`);
      }
    },
    toBeCloseTo: (expected, precision = 2) => {
      const diff = Math.abs(actual - expected);
      const threshold = Math.pow(10, -precision) / 2;
      if (diff >= threshold) {
        throw new Error(`Expected ${actual} to be close to ${expected} (precision: ${precision})`);
      }
    },
    toBeDefined: () => {
      if (actual === undefined) {
        throw new Error('Expected value to be defined');
      }
    },
    toBeNull: () => {
      if (actual !== null) {
        throw new Error(`Expected null, got ${actual}`);
      }
    },
    toBeGreaterThan: (expected) => {
      if (actual <= expected) {
        throw new Error(`Expected ${actual} to be greater than ${expected}`);
      }
    },
    toBeGreaterThanOrEqual: (expected) => {
      if (actual < expected) {
        throw new Error(`Expected ${actual} to be greater than or equal to ${expected}`);
      }
    },
    toBeLessThanOrEqual: (expected) => {
      if (actual > expected) {
        throw new Error(`Expected ${actual} to be less than or equal to ${expected}`);
      }
    },
    toContain: (expected) => {
      if (Array.isArray(actual)) {
        if (!actual.includes(expected)) {
          throw new Error(`Expected array to contain ${expected}`);
        }
      } else if (typeof actual === 'string') {
        if (!actual.includes(expected)) {
          throw new Error(`Expected string to contain "${expected}"`);
        }
      } else {
        throw new Error('toContain can only be used with arrays or strings');
      }
    },
    toEqual: (expected) => {
      if (JSON.stringify(actual) !== JSON.stringify(expected)) {
        throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
      }
    },
    toBeInstanceOf: (expectedClass) => {
      if (!(actual instanceof expectedClass)) {
        throw new Error(`Expected instance of ${expectedClass.name}, got ${typeof actual}`);
      }
    }
  };
  
  return expectObj;
}

// Set up global test functions
global.describe = mockDescribe;
global.test = mockTest;
global.expect = mockExpected;

// Mock factories
const createMockCuttingOperation = (data) => ({
  id: data.id || Math.floor(Math.random() * 1000),
  production_order_id: data.production_order_id || 1,
  rm_piece_id: data.rm_piece_id || 1,
  bom_item_id: data.bom_item_id || 1,
  cut_length: data.cut_length || 2.0,
  cut_width: data.cut_width || 1.5,
  unit: data.unit || 'm',
  cut_area: data.cut_area || (data.cut_length || 2.0) * (data.cut_width || 1.5),
  waste_pieces: data.waste_pieces || null,
  cut_at: data.cut_at || new Date(),
  ...data
});

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 || 3.0,
  width: data.width || 2.0,
  unit: data.unit || 'm',
  status: data.status || 'FULL',
  usable_length: data.usable_length || null,
  usable_width: data.usable_width || null,
  created_at: data.created_at || new Date(),
  ...data
});

const createMockWastePiece = (data) => ({
  length: data.length || 1.0,
  width: data.width || 0.8,
  unit: data.unit || 'm',
  status: data.status || 'WASTE',
  area: data.area || (data.length || 1.0) * (data.width || 0.8),
  ...data
});

// Run material utilization tests
async function runMaterialUtilizationTests() {
  console.log('Running Material Utilization Service Tests...\n');
  
  // Test 1: Basic functionality
  await mockTest('should calculate utilization efficiency correctly', async () => {
    const inventoryPieces = [
      createMockInventoryPiece({
        id: 1,
        product_id: 1,
        length: 4.0,
        width: 3.0,
        unit: 'm'
      }),
      createMockInventoryPiece({
        id: 2,
        product_id: 1,
        length: 5.0,
        width: 2.0,
        unit: 'm'
      })
    ];

    const cuttingOperations = [
      createMockCuttingOperation({
        id: 1,
        rm_piece_id: 1,
        cut_area: 6.0, // 2.0 * 3.0
        waste_pieces: [
          createMockWastePiece({
            length: 2.0,
            width: 1.5,
            status: 'WASTE'
          })
        ]
      }),
      createMockCuttingOperation({
        id: 2,
        rm_piece_id: 2,
        cut_area: 4.0, // 2.0 * 2.0
        waste_pieces: [
          createMockWastePiece({
            length: 0.5,
            width: 0.3,
            status: 'SCRAP'
          })
        ]
      })
    ];

    const result = utilizationService.calculateUtilizationEfficiency(
      cuttingOperations,
      inventoryPieces
    );

    expect(result.isValid).toBe(true);
    expect(result.utilizationReport).toBeDefined();
    expect(result.utilizationReport.overallMetrics).toBeDefined();
    expect(result.utilizationReport.overallMetrics.totalOperations).toBe(2);
    expect(result.utilizationReport.overallMetrics.totalCutArea).toBe(10.0);
  });

  await mockTest('should calculate overall utilization metrics correctly', async () => {
    const inventoryPieces = [
      createMockInventoryPiece({
        id: 1,
        length: 6.0,
        width: 4.0,
        unit: 'm'
      })
    ];

    const cuttingOperations = [
      createMockCuttingOperation({
        rm_piece_id: 1,
        cut_area: 12.0, // Half of the 24 m² piece
        waste_pieces: [
          createMockWastePiece({
            length: 3.0,
            width: 2.0,
            status: 'WASTE'
          })
        ]
      })
    ];

    const result = utilizationService.calculateOverallUtilization(cuttingOperations, inventoryPieces);

    expect(result.isValid).toBe(true);
    expect(result.metrics.totalCutArea).toBe(12.0);
    expect(result.metrics.totalSourceArea).toBe(24.0);
    expect(result.metrics.materialUtilizationRate).toBeCloseTo(0.5, 2); // 12/24
    expect(result.metrics.operationsWithWastePercentage).toBe(1.0); // 100% of operations have waste
  });

  await mockTest('should calculate waste analysis correctly', async () => {
    const cuttingOperations = [
      createMockCuttingOperation({
        waste_pieces: [
          createMockWastePiece({
            length: 2.0,
            width: 1.5,
            unit: 'm',
            status: 'WASTE'
          }),
          createMockWastePiece({
            length: 0.5,
            width: 0.3,
            unit: 'm',
            status: 'SCRAP'
          })
        ]
      }),
      createMockCuttingOperation({
        waste_pieces: [
          createMockWastePiece({
            length: 1.0,
            width: 0.8,
            unit: 'm',
            status: 'WASTE'
          })
        ]
      })
    ];

    const result = utilizationService.calculateWasteAnalysis(cuttingOperations, []);

    expect(result.totalWastePieces).toBe(3);
    expect(result.reusableWastePieces).toBe(2);
    expect(result.scrapPieces).toBe(1);
    expect(result.reusabilityRate).toBeCloseTo(0.667, 2); // 2/3
    expect(result.totalWasteArea).toBeCloseTo(3.95, 2); // 3.0 + 0.15 + 0.8
  });

  await mockTest('should calculate cost savings correctly', async () => {
    const wasteReusageOperations = [
      {
        reused_waste_area: 2.0,
        product_id: 1
      },
      {
        reused_waste_area: 1.5,
        product_id: 1
      },
      {
        reused_waste_area: 1.0,
        product_id: 2
      }
    ];

    const materialCosts = {
      1: { costPerUnit: 10.0 }, // $10 per m²
      2: { costPerUnit: 15.0 }  // $15 per m²
    };

    const result = utilizationService.calculateCostSavings(
      wasteReusageOperations,
      materialCosts
    );

    expect(result.isValid).toBe(true);
    expect(result.costSavingsReport.totalAreaReused).toBe(4.5);
    expect(result.costSavingsReport.totalCostSaved).toBe(50.0); // (2.0 + 1.5) * 10 + 1.0 * 15
    expect(result.costSavingsReport.operationsCount).toBe(3);
    expect(result.costSavingsReport.savingsByProduct.length).toBe(2);
  });

  await mockTest('should generate utilization recommendations correctly', async () => {
    const utilizationReport = {
      overallMetrics: {
        materialUtilizationRate: 0.6, // Below 70% threshold
        wasteGenerationRate: 0.35,    // Above 30% threshold
        scrapPercentage: 0.25,        // Above 20% threshold
        operationsWithWastePercentage: 0.85
      },
      wasteAnalysis: {
        reusabilityRate: 0.5,         // Below 60% threshold
        reusableWastePieces: 10
      }
    };

    const recommendations = utilizationService.generateUtilizationRecommendations(
      utilizationReport,
      []
    );

    expect(Array.isArray(recommendations)).toBe(true);
    expect(recommendations.length).toBeGreaterThan(0);
    
    // Should have high priority recommendations for low utilization and high waste
    const highPriorityRecs = recommendations.filter(r => r.priority === 'HIGH');
    expect(highPriorityRecs.length).toBeGreaterThanOrEqual(2);
  });

  await mockTest('should calculate utilization by product correctly', async () => {
    const inventoryPieces = [
      createMockInventoryPiece({ id: 1, product_id: 1, length: 4.0, width: 3.0 }),
      createMockInventoryPiece({ id: 2, product_id: 2, length: 5.0, width: 2.0 })
    ];

    const cuttingOperations = [
      createMockCuttingOperation({ rm_piece_id: 1, cut_area: 6.0 }),
      createMockCuttingOperation({ rm_piece_id: 1, cut_area: 4.0 }),
      createMockCuttingOperation({ rm_piece_id: 2, cut_area: 5.0 })
    ];

    const result = utilizationService.calculateUtilizationByProduct(cuttingOperations, inventoryPieces);

    expect(result.productMetrics).toBeDefined();
    expect(result.totalProducts).toBe(2);
    expect(result.productMetrics[1]).toBeDefined();
    expect(result.productMetrics[2]).toBeDefined();
    expect(result.productMetrics[1].operationsCount).toBe(2);
    expect(result.productMetrics[2].operationsCount).toBe(1);
  });

  await mockTest('should calculate utilization by time period correctly', async () => {
    const cuttingOperations = [
      createMockCuttingOperation({
        cut_area: 3.0,
        cut_at: new Date('2024-01-01'),
        waste_pieces: [createMockWastePiece({ length: 1.0, width: 0.5 })]
      }),
      createMockCuttingOperation({
        cut_area: 4.0,
        cut_at: new Date('2024-01-01'),
        waste_pieces: [createMockWastePiece({ length: 0.8, width: 0.6 })]
      }),
      createMockCuttingOperation({
        cut_area: 2.0,
        cut_at: new Date('2024-01-02'),
        waste_pieces: [createMockWastePiece({ length: 0.5, width: 0.4 })]
      })
    ];

    const result = utilizationService.calculateUtilizationByPeriod(cuttingOperations, 'day');

    expect(result.periodMetrics).toBeDefined();
    expect(result.totalPeriods).toBe(2);
    expect(result.periodType).toBe('day');
    
    // First day should have 2 operations
    const firstDay = result.periodMetrics.find(p => p.period === '2024-01-01');
    expect(firstDay).toBeDefined();
    expect(firstDay.operationsCount).toBe(2);
    expect(firstDay.totalCutArea).toBe(7.0);
  });

  // Property-based test for material utilization calculation
  await mockTest('Property 18: Material Utilization Calculation (100 iterations)', async () => {
    let passedIterations = 0;
    
    for (let i = 0; i < 100; i++) {
      const numOperations = Math.floor(Math.random() * 20) + 1; // 1-20 operations
      const numPieces = Math.floor(Math.random() * 10) + 1;     // 1-10 pieces
      
      const inventoryPieces = [];
      const cuttingOperations = [];
      
      // Create inventory pieces
      for (let j = 0; j < numPieces; j++) {
        const piece = createMockInventoryPiece({
          id: j + 1,
          product_id: Math.floor(Math.random() * 3) + 1, // 1-3 products
          length: Math.random() * 5 + 1, // 1-6m
          width: Math.random() * 4 + 1,  // 1-5m
          unit: 'm'
        });
        inventoryPieces.push(piece);
      }
      
      let totalExpectedCutArea = 0;
      let totalExpectedWasteArea = 0;
      
      // Create cutting operations
      for (let j = 0; j < numOperations; j++) {
        const pieceId = Math.floor(Math.random() * numPieces) + 1;
        const sourcePiece = inventoryPieces.find(p => p.id === pieceId);
        
        if (!sourcePiece) continue;
        
        const maxCutLength = sourcePiece.length * 0.8; // Up to 80% of piece
        const maxCutWidth = sourcePiece.width * 0.8;
        
        const cutLength = Math.random() * maxCutLength + 0.1;
        const cutWidth = Math.random() * maxCutWidth + 0.1;
        const cutArea = cutLength * cutWidth;
        
        totalExpectedCutArea += cutArea;
        
        // Generate waste pieces
        const wastePieces = [];
        const numWastePieces = Math.floor(Math.random() * 3); // 0-2 waste pieces
        
        for (let k = 0; k < numWastePieces; k++) {
          const wasteLength = Math.random() * 2 + 0.1;
          const wasteWidth = Math.random() * 1.5 + 0.1;
          const wasteArea = wasteLength * wasteWidth;
          totalExpectedWasteArea += wasteArea;
          
          wastePieces.push(createMockWastePiece({
            length: wasteLength,
            width: wasteWidth,
            unit: 'm',
            status: Math.random() > 0.7 ? 'SCRAP' : 'WASTE'
          }));
        }
        
        const operation = createMockCuttingOperation({
          id: j + 1,
          rm_piece_id: pieceId,
          cut_area: cutArea,
          waste_pieces: wastePieces.length > 0 ? wastePieces : null,
          cut_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000) // Last 30 days
        });
        
        cuttingOperations.push(operation);
      }
      
      const result = utilizationService.calculateUtilizationEfficiency(
        cuttingOperations,
        inventoryPieces
      );
      
      // Property 18.1: Result should be valid
      if (!result.isValid) {
        throw new Error(`Invalid result at iteration ${i}`);
      }
      
      // Property 18.2: Total cut area should match expected
      const actualCutArea = result.utilizationReport.overallMetrics.totalCutArea;
      if (Math.abs(actualCutArea - totalExpectedCutArea) > 0.001) {
        throw new Error(`Cut area mismatch at iteration ${i}: expected ${totalExpectedCutArea}, got ${actualCutArea}`);
      }
      
      // Property 18.3: Utilization rate should be between 0 and 1
      const utilizationRate = result.utilizationReport.overallMetrics.materialUtilizationRate;
      if (utilizationRate < 0 || utilizationRate > 1) {
        throw new Error(`Invalid utilization rate at iteration ${i}: ${utilizationRate}`);
      }
      
      // Property 18.4: Waste generation rate should be between 0 and 1
      const wasteRate = result.utilizationReport.overallMetrics.wasteGenerationRate;
      if (wasteRate < 0 || wasteRate > 1) {
        throw new Error(`Invalid waste rate at iteration ${i}: ${wasteRate}`);
      }
      
      // Property 18.5: Operations count should match
      if (result.utilizationReport.overallMetrics.totalOperations !== cuttingOperations.length) {
        throw new Error(`Operations count mismatch at iteration ${i}`);
      }
      
      // Property 18.6: Waste analysis should be consistent if included
      if (result.utilizationReport.wasteAnalysis) {
        const wasteAnalysis = result.utilizationReport.wasteAnalysis;
        
        if (wasteAnalysis.reusabilityRate < 0 || wasteAnalysis.reusabilityRate > 1) {
          throw new Error(`Invalid reusability rate at iteration ${i}`);
        }
        
        if (wasteAnalysis.totalWastePieces !== 
            wasteAnalysis.reusableWastePieces + wasteAnalysis.scrapPieces) {
          throw new Error(`Waste pieces count inconsistency at iteration ${i}`);
        }
      }
      
      // Property 18.7: Product utilization should be consistent if grouped
      const productResult = utilizationService.calculateUtilizationByProduct(
        cuttingOperations,
        inventoryPieces
      );
      
      if (productResult.totalProducts > 0) {
        let totalProductOperations = 0;
        for (const productMetrics of Object.values(productResult.productMetrics)) {
          totalProductOperations += productMetrics.operationsCount;
        }
        
        if (totalProductOperations !== cuttingOperations.length) {
          throw new Error(`Product operations count mismatch at iteration ${i}`);
        }
      }
      
      passedIterations++;
    }
    
    console.log(`    Passed ${passedIterations}/100 iterations`);
  });

  // Test edge cases
  await mockTest('should handle empty arrays gracefully', async () => {
    const result = utilizationService.calculateUtilizationEfficiency([], []);
    expect(result.isValid).toBe(true);
    expect(result.utilizationReport.overallMetrics.totalOperations).toBe(0);
  });

  await mockTest('should handle invalid input gracefully', async () => {
    const result1 = utilizationService.calculateUtilizationEfficiency(null, []);
    expect(result1.isValid).toBe(false);

    const result2 = utilizationService.calculateUtilizationEfficiency([], null);
    expect(result2.isValid).toBe(false);
  });

  await mockTest('should calculate waste reusability score correctly', async () => {
    // Perfect reusability (no scrap)
    const score1 = utilizationService.calculateWasteReusabilityScore(10.0, 0.0);
    expect(score1).toBe(1.0);

    // No reusability (all scrap)
    const score2 = utilizationService.calculateWasteReusabilityScore(0.0, 10.0);
    expect(score2).toBe(0.0);

    // Mixed reusability
    const score3 = utilizationService.calculateWasteReusabilityScore(7.0, 3.0);
    expect(score3).toBe(0.7);

    // No waste at all
    const score4 = utilizationService.calculateWasteReusabilityScore(0.0, 0.0);
    expect(score4).toBe(1.0);
  });

  return testResults;
}

// Run the tests
runMaterialUtilizationTests().then((results) => {
  console.log('\n=== Material Utilization Service Test Results ===');
  const passed = results.filter(r => r.status === 'PASS').length;
  const failed = results.filter(r => r.status === 'FAIL').length;
  
  console.log(`Passed: ${passed}`);
  console.log(`Failed: ${failed}`);
  console.log(`Total: ${results.length}`);
  
  if (failed > 0) {
    console.log('\nFailed tests:');
    results.filter(r => r.status === 'FAIL').forEach(r => {
      console.log(`- ${r.name}: ${r.error}`);
    });
  }
  
  process.exit(failed === 0 ? 0 : 1);
}).catch(error => {
  console.error('Test runner error:', error);
  process.exit(1);
});