/**
 * Standalone Inventory Calculation Service Test Runner
 * Runs inventory calculation tests without Jest environment issues
 */

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

// 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 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
});

// Run inventory calculation tests
async function runInventoryCalculationTests() {
  console.log('Running Inventory Calculation Service Tests...\n');
  
  // Test 1: Basic functionality
  await mockTest('should calculate inventory summary correctly', async () => {
    const pieces = [
      createMockInventoryPiece({
        id: 1,
        length: 2.0,
        width: 1.5,
        unit: 'm',
        status: 'FULL'
      }),
      createMockInventoryPiece({
        id: 2,
        length: 3.0,
        width: 2.0,
        unit: 'm',
        status: 'USABLE',
        usable_length: 2.5,
        usable_width: 1.8
      }),
      createMockInventoryPiece({
        id: 3,
        length: 1.0,
        width: 0.8,
        unit: 'm',
        status: 'WASTE'
      })
    ];

    const result = inventoryService.calculateInventorySummary(pieces);

    expect(result.isValid).toBe(true);
    expect(result.summary.totalPieces).toBe(3);
    expect(result.summary.piecesByStatus.FULL).toBe(1);
    expect(result.summary.piecesByStatus.USABLE).toBe(1);
    expect(result.summary.piecesByStatus.WASTE).toBe(1);
    expect(result.summary.piecesByStatus.SCRAP).toBe(0);
  });

  await mockTest('should calculate piece areas correctly', async () => {
    // FULL piece
    const fullPiece = createMockInventoryPiece({
      length: 3.0,
      width: 2.0,
      unit: 'm',
      status: 'FULL'
    });

    const fullResult = inventoryService.calculatePieceAreas(fullPiece);
    expect(fullResult.isValid).toBe(true);
    expect(fullResult.totalArea).toBe(6.0);
    expect(fullResult.fullArea).toBe(6.0);
    expect(fullResult.usableArea).toBe(6.0);
    expect(fullResult.wasteArea).toBe(0);
    expect(fullResult.scrapArea).toBe(0);

    // USABLE piece
    const usablePiece = createMockInventoryPiece({
      length: 4.0,
      width: 3.0,
      unit: 'm',
      status: 'USABLE',
      usable_length: 3.5,
      usable_width: 2.5
    });

    const usableResult = inventoryService.calculatePieceAreas(usablePiece);
    expect(usableResult.isValid).toBe(true);
    expect(usableResult.totalArea).toBe(12.0);
    expect(usableResult.fullArea).toBe(0);
    expect(usableResult.usableArea).toBe(8.75); // 3.5 * 2.5
    expect(usableResult.wasteArea).toBe(3.25); // 12.0 - 8.75
    expect(usableResult.scrapArea).toBe(0);
  });

  await mockTest('should count pieces by status correctly', async () => {
    const pieces = [
      createMockInventoryPiece({ status: 'FULL' }),
      createMockInventoryPiece({ status: 'FULL' }),
      createMockInventoryPiece({ status: 'USABLE' }),
      createMockInventoryPiece({ status: 'WASTE' }),
      createMockInventoryPiece({ status: 'WASTE' }),
      createMockInventoryPiece({ status: 'WASTE' }),
      createMockInventoryPiece({ status: 'SCRAP' })
    ];

    const result = inventoryService.countPiecesByStatus(pieces);

    expect(result.isValid).toBe(true);
    expect(result.counts.FULL).toBe(2);
    expect(result.counts.USABLE).toBe(1);
    expect(result.counts.WASTE).toBe(3);
    expect(result.counts.SCRAP).toBe(1);
    expect(result.counts.total).toBe(7);
  });

  await mockTest('should generate inventory report correctly', async () => {
    const pieces = [
      createMockInventoryPiece({
        length: 2.0,
        width: 1.5,
        unit: 'm',
        status: 'FULL'
      }),
      createMockInventoryPiece({
        length: 1.0,
        width: 0.8,
        unit: 'm',
        status: 'WASTE'
      })
    ];

    const result = inventoryService.generateInventoryReport(pieces, {
      includeUtilization: true,
      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();
  });

  await mockTest('should handle unit conversion in area calculations', async () => {
    const piece = createMockInventoryPiece({
      length: 100, // 100 cm
      width: 50,   // 50 cm
      unit: 'cm',
      status: 'FULL'
    });

    // Calculate in original unit (cm)
    const cmResult = inventoryService.calculatePieceAreas(piece);
    expect(cmResult.isValid).toBe(true);
    expect(cmResult.totalArea).toBe(5000); // 100 * 50 = 5000 cm²

    // Calculate in meters
    const mResult = inventoryService.calculatePieceAreas(piece, { targetUnit: 'm' });
    expect(mResult.isValid).toBe(true);
    expect(mResult.totalArea).toBeCloseTo(0.5, 4); // 5000 cm² = 0.5 m²
    expect(mResult.unit).toBe('m');
    expect(mResult.originalUnit).toBe('cm');
  });

  // Property-based test for inventory calculation accuracy
  await mockTest('Property 11: Inventory Calculation Accuracy (100 iterations)', async () => {
    let passedIterations = 0;
    
    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
      if (!result.isValid) {
        throw new Error(`Invalid result at iteration ${i}`);
      }

      // Property 11.2: Total piece count should match
      if (result.summary.totalPieces !== expectedTotalPieces) {
        throw new Error(`Total pieces mismatch at iteration ${i}: expected ${expectedTotalPieces}, got ${result.summary.totalPieces}`);
      }

      // Property 11.3: Status counts should match
      if (result.summary.piecesByStatus.FULL !== expectedFullPieces) {
        throw new Error(`FULL pieces mismatch at iteration ${i}`);
      }
      if (result.summary.piecesByStatus.USABLE !== expectedUsablePieces) {
        throw new Error(`USABLE pieces mismatch at iteration ${i}`);
      }
      if (result.summary.piecesByStatus.WASTE !== expectedWastePieces) {
        throw new Error(`WASTE pieces mismatch at iteration ${i}`);
      }
      if (result.summary.piecesByStatus.SCRAP !== expectedScrapPieces) {
        throw new Error(`SCRAP pieces mismatch at iteration ${i}`);
      }

      // Property 11.4: Total area should be approximately correct (allowing for conversion rounding)
      const areaDiff = Math.abs(result.summary.totalAreas.totalArea - expectedTotalArea);
      if (areaDiff > expectedTotalArea * 0.01) { // Allow 1% tolerance for rounding
        throw new Error(`Total area mismatch at iteration ${i}: expected ${expectedTotalArea}, got ${result.summary.totalAreas.totalArea}`);
      }

      // Property 11.5: Utilization statistics should be valid percentages
      if (result.summary.utilizationStatistics.utilizationRate < 0 || 
          result.summary.utilizationStatistics.utilizationRate > 1) {
        throw new Error(`Invalid utilization rate at iteration ${i}`);
      }
      if (result.summary.utilizationStatistics.wasteRate < 0 || 
          result.summary.utilizationStatistics.wasteRate > 1) {
        throw new Error(`Invalid waste rate at iteration ${i}`);
      }
      if (result.summary.utilizationStatistics.scrapRate < 0 || 
          result.summary.utilizationStatistics.scrapRate > 1) {
        throw new Error(`Invalid scrap rate at iteration ${i}`);
      }

      // Property 11.6: Dimension variations should be tracked correctly
      if (!Array.isArray(result.summary.dimensionVariations)) {
        throw new Error(`Dimension variations should be array at iteration ${i}`);
      }
      if (result.summary.dimensionVariations.length === 0 && pieces.length > 0) {
        throw new Error(`Missing dimension variations at iteration ${i}`);
      }

      passedIterations++;
    }
    
    console.log(`    Passed ${passedIterations}/100 iterations`);
  });

  // Property-based test for area calculation consistency
  await mockTest('Property 3: Area Calculation Consistency (100 iterations)', async () => {
    let passedIterations = 0;
    
    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);
      if (!originalAreaResult.isValid) {
        throw new Error(`Invalid area calculation at iteration ${i}`);
      }

      // Property 3.1: Area should equal length × width
      const expectedArea = length * width;
      const areaDiff = Math.abs(originalAreaResult.totalArea - expectedArea);
      if (areaDiff > 0.000001) {
        throw new Error(`Area calculation error at iteration ${i}: expected ${expectedArea}, got ${originalAreaResult.totalArea}`);
      }

      // Property 3.2: Area components should be non-negative
      if (originalAreaResult.fullArea < 0 || originalAreaResult.usableArea < 0 || 
          originalAreaResult.wasteArea < 0 || originalAreaResult.scrapArea < 0) {
        throw new Error(`Negative area component at iteration ${i}`);
      }

      // Property 3.3: Only appropriate area types should be non-zero based on status
      switch (status) {
        case 'FULL':
          if (Math.abs(originalAreaResult.fullArea - expectedArea) > 0.000001) {
            throw new Error(`FULL area mismatch at iteration ${i}`);
          }
          if (Math.abs(originalAreaResult.usableArea - expectedArea) > 0.000001) {
            throw new Error(`FULL usable area mismatch at iteration ${i}`);
          }
          if (originalAreaResult.wasteArea !== 0 || originalAreaResult.scrapArea !== 0) {
            throw new Error(`FULL piece should not have waste/scrap area at iteration ${i}`);
          }
          break;
        case 'USABLE':
          if (originalAreaResult.fullArea !== 0 || originalAreaResult.scrapArea !== 0) {
            throw new Error(`USABLE piece should not have full/scrap area at iteration ${i}`);
          }
          if (originalAreaResult.usableArea <= 0) {
            throw new Error(`USABLE piece should have positive usable area at iteration ${i}`);
          }
          // Usable + waste should equal total (with tolerance)
          const totalUsableWaste = originalAreaResult.usableArea + originalAreaResult.wasteArea;
          if (Math.abs(totalUsableWaste - expectedArea) > 0.000001) {
            throw new Error(`USABLE area conservation violation at iteration ${i}`);
          }
          break;
        case 'WASTE':
          if (originalAreaResult.fullArea !== 0 || originalAreaResult.usableArea !== 0 || 
              originalAreaResult.scrapArea !== 0) {
            throw new Error(`WASTE piece should only have waste area at iteration ${i}`);
          }
          if (Math.abs(originalAreaResult.wasteArea - expectedArea) > 0.000001) {
            throw new Error(`WASTE area mismatch at iteration ${i}`);
          }
          break;
        case 'SCRAP':
          if (originalAreaResult.fullArea !== 0 || originalAreaResult.usableArea !== 0 || 
              originalAreaResult.wasteArea !== 0) {
            throw new Error(`SCRAP piece should only have scrap area at iteration ${i}`);
          }
          if (Math.abs(originalAreaResult.scrapArea - expectedArea) > 0.000001) {
            throw new Error(`SCRAP area mismatch at iteration ${i}`);
          }
          break;
      }

      // Property 3.4: Area calculation should be deterministic
      const secondCalculation = inventoryService.calculatePieceAreas(piece);
      if (Math.abs(secondCalculation.totalArea - originalAreaResult.totalArea) > 0.0000000001) {
        throw new Error(`Non-deterministic area calculation at iteration ${i}`);
      }

      passedIterations++;
    }
    
    console.log(`    Passed ${passedIterations}/100 iterations`);
  });

  // Test edge cases
  await mockTest('should handle empty inventory arrays', async () => {
    const result = inventoryService.calculateInventorySummary([]);
    expect(result.isValid).toBe(true);
    expect(result.summary.totalPieces).toBe(0);
    expect(result.summary.totalAreas.totalArea).toBe(0);
  });

  await mockTest('should handle invalid piece data gracefully', async () => {
    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
  });

  await mockTest('should validate piece data correctly', async () => {
    // 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);
  });

  return testResults;
}

// Run the tests
runInventoryCalculationTests().then((results) => {
  console.log('\n=== Inventory Calculation 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);
});