/**
 * Standalone Material Allocation Service Test Runner
 * Runs material allocation tests without Jest environment issues
 */

const allocationService = require('./services/materialAllocationService');

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

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

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

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

// Run material allocation tests
async function runMaterialAllocationTests() {
  console.log('Running Material Allocation Service Tests...\n');
  
  // Test 1: Basic functionality
  await mockTest('should find suitable pieces for requirements', async () => {
    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
  });

  await mockTest('should respect status priority in allocation', async () => {
    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');
  });

  await mockTest('should get correct dimensions for different piece statuses', async () => {
    // FULL piece
    const fullPiece = createMockRMPiece({
      length: 5.0,
      width: 3.0,
      unit: 'm',
      status: 'FULL'
    });

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

    // USABLE piece
    const usablePiece = createMockRMPiece({
      length: 5.0,
      width: 3.0,
      unit: 'm',
      status: 'USABLE',
      usable_length: 4.5,
      usable_width: 2.8
    });

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

    // SCRAP piece
    const scrapPiece = createMockRMPiece({
      length: 5.0,
      width: 3.0,
      unit: 'm',
      status: 'SCRAP'
    });

    const scrapDimensions = allocationService.getAvailableDimensions(scrapPiece);
    expect(scrapDimensions).toBeNull();
  });

  await mockTest('should allocate material for BOM item', async () => {
    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 2: Property-based test for allocation priority (simplified)
  await mockTest('Property 5: Material Allocation Priority (100 iterations)', async () => {
    let passedIterations = 0;
    
    for (let i = 0; i < 100; i++) {
      // Generate random pieces
      const numPieces = Math.floor(Math.random() * 7) + 3; // 3-10 pieces
      const pieces = [];
      
      for (let j = 0; j < numPieces; j++) {
        const status = ['FULL', 'USABLE', 'WASTE', 'SCRAP'][Math.floor(Math.random() * 4)];
        const length = Math.random() * 9 + 1; // 1-10
        const width = Math.random() * 9 + 1; // 1-10
        
        const piece = createMockRMPiece({
          id: j + 1,
          length: length,
          width: width,
          unit: 'm',
          status: status
        });

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

        pieces.push(piece);
      }

      const requirements = {
        length: Math.random() * 4.5 + 0.5, // 0.5-5.0
        width: Math.random() * 4.5 + 0.5,
        unit: 'm'
      };

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

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

      // Property 5.1: SCRAP pieces should never be included
      const scrapPieces = result.suitablePieces.filter(sp => sp.piece.status === 'SCRAP');
      if (scrapPieces.length > 0) {
        throw new Error(`SCRAP pieces found in suitable pieces at iteration ${i}`);
      }

      // Property 5.2: Pieces should be ordered by priority
      if (result.suitablePieces.length > 1) {
        for (let k = 0; k < result.suitablePieces.length - 1; k++) {
          const currentPriority = allocationService.STATUS_PRIORITY[result.suitablePieces[k].piece.status];
          const nextPriority = allocationService.STATUS_PRIORITY[result.suitablePieces[k + 1].piece.status];
          if (currentPriority < nextPriority) {
            throw new Error(`Priority order violation at iteration ${i}`);
          }
        }
      }

      // Property 5.3: All suitable pieces should actually fit the requirements
      for (const suitablePiece of result.suitablePieces) {
        const availableDims = allocationService.getAvailableDimensions(suitablePiece.piece);
        if (!availableDims) {
          throw new Error(`No available dimensions for suitable piece at iteration ${i}`);
        }
        
        // Check if it actually fits (same unit for simplicity)
        if (availableDims.unit === requirements.unit) {
          if (availableDims.length < requirements.length || availableDims.width < requirements.width) {
            throw new Error(`Piece doesn't actually fit requirements at iteration ${i}`);
          }
        }
      }

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

  // Test 3: Property-based test for dimension sufficiency (simplified)
  await mockTest('Property 6: Dimension Sufficiency Check (100 iterations)', async () => {
    let passedIterations = 0;
    
    for (let i = 0; i < 100; i++) {
      const pieceLength = Math.random() * 9 + 1; // 1-10
      const pieceWidth = Math.random() * 9 + 1;
      const reqLength = Math.random() * 9.5 + 0.5; // 0.5-10
      const reqWidth = Math.random() * 9.5 + 0.5;
      const status = ['FULL', 'USABLE', 'WASTE'][Math.floor(Math.random() * 3)];
      const usableFactor = Math.random() * 0.35 + 0.6; // 0.6-0.95

      // Create piece with appropriate dimensions
      const piece = createMockRMPiece({
        id: 1,
        length: pieceLength,
        width: pieceWidth,
        unit: 'm',
        status: status
      });

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

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

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

      // Property 6.1: Result should always be valid for valid inputs
      if (!result.isValid) {
        throw new Error(`Invalid result at iteration ${i}`);
      }

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

      // Property 6.3: Suitability should match mathematical expectation
      if (shouldFit) {
        if (result.suitablePieces.length !== 1) {
          throw new Error(`Expected 1 suitable piece but got ${result.suitablePieces.length} at iteration ${i}`);
        }
        
        const suitablePiece = result.suitablePieces[0];
        if (JSON.stringify(suitablePiece.availableDimensions) !== JSON.stringify(availableDims)) {
          throw new Error(`Available dimensions mismatch at iteration ${i}`);
        }
        
        // Property 6.4: Remaining dimensions should be calculated correctly
        const expectedRemainingLength = availableDims.length - reqLength;
        const expectedRemainingWidth = availableDims.width - reqWidth;
        
        if (Math.abs(suitablePiece.remainingDimensions.length - expectedRemainingLength) > 0.000001) {
          throw new Error(`Remaining length calculation error at iteration ${i}`);
        }
        if (Math.abs(suitablePiece.remainingDimensions.width - expectedRemainingWidth) > 0.000001) {
          throw new Error(`Remaining width calculation error at iteration ${i}`);
        }
      } else {
        if (result.suitablePieces.length !== 0) {
          throw new Error(`Expected 0 suitable pieces but got ${result.suitablePieces.length} at iteration ${i}`);
        }
      }

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

  // Test 4: BOM allocation feasibility
  await mockTest('should validate complete BOM allocation feasibility', async () => {
    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();
  });

  await mockTest('should suggest waste piece reuse opportunities', async () => {
    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);
  });

  return testResults;
}

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