/**
 * Dimension Validation Service Tests
 * Property-based tests for dimension validation and unit conversion
 */

const fc = require('fast-check');
const dimensionService = require('../../../services/dimensionValidationService');

describe('Dimension Validation Service', () => {
  describe('Basic Validation Functions', () => {
    test('should validate RM product dimension requirements', () => {
      // Valid RM product
      const rmProduct = {
        product_type: 'RM',
        track_by_dimensions: true,
        unit_of_measure: 'm'
      };
      
      const result = dimensionService.validateProductDimensionRequirement(rmProduct);
      expect(result.isValid).toBe(true);
      expect(result.requiresDimensions).toBe(true);
      expect(result.unit).toBe('m');
    });

    test('should reject RM product without dimension tracking', () => {
      const rmProduct = {
        product_type: 'RM',
        track_by_dimensions: false,
        unit_of_measure: 'm'
      };
      
      const result = dimensionService.validateProductDimensionRequirement(rmProduct);
      expect(result.isValid).toBe(false);
      expect(result.error).toContain('track_by_dimensions');
    });

    test('should allow FG products without dimension tracking', () => {
      const fgProduct = {
        product_type: 'FG',
        track_by_dimensions: false
      };
      
      const result = dimensionService.validateProductDimensionRequirement(fgProduct);
      expect(result.isValid).toBe(true);
      expect(result.requiresDimensions).toBe(false);
    });

    test('should validate positive dimensions', () => {
      const result = dimensionService.validateDimensions(5.0, 3.0, 'm');
      expect(result.isValid).toBe(true);
    });

    test('should reject negative dimensions', () => {
      const result = dimensionService.validateDimensions(-1.0, 3.0, 'm');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Length must be positive');
    });

    test('should reject invalid units', () => {
      const result = dimensionService.validateDimensions(5.0, 3.0, 'invalid');
      expect(result.isValid).toBe(false);
      expect(result.errors[0]).toContain('Invalid unit');
    });
  });

  describe('Unit Conversion Functions', () => {
    test('should convert meters to centimeters correctly', () => {
      const result = dimensionService.convertUnit(1.0, 'm', 'cm');
      expect(result.isValid).toBe(true);
      expect(result.value).toBeCloseTo(100.0, 6);
    });

    test('should convert inches to meters correctly', () => {
      const result = dimensionService.convertUnit(39.3701, 'inch', 'm');
      expect(result.isValid).toBe(true);
      expect(result.value).toBeCloseTo(1.0, 3);
    });

    test('should handle same unit conversion', () => {
      const result = dimensionService.convertUnit(5.0, 'm', 'm');
      expect(result.isValid).toBe(true);
      expect(result.value).toBe(5.0);
    });

    test('should convert dimension objects', () => {
      const dimensions = { length: 2.0, width: 1.5, unit: 'm' };
      const result = dimensionService.convertDimensions(dimensions, 'cm');
      
      expect(result.isValid).toBe(true);
      expect(result.dimensions.length).toBeCloseTo(200.0, 6);
      expect(result.dimensions.width).toBeCloseTo(150.0, 6);
      expect(result.dimensions.unit).toBe('cm');
    });
  });

  describe('Area Calculation', () => {
    test('should calculate area correctly', () => {
      const result = dimensionService.calculateArea(4.0, 3.0, 'm');
      expect(result.isValid).toBe(true);
      expect(result.area).toBe(12.0);
      expect(result.areaUnit).toBe('m²');
    });

    test('should reject invalid dimensions for area calculation', () => {
      const result = dimensionService.calculateArea(-1.0, 3.0, 'm');
      expect(result.isValid).toBe(false);
    });
  });

  describe('Dimension Sufficiency Checking', () => {
    test('should check if available dimensions can satisfy requirements', () => {
      const available = { length: 6.0, width: 4.0, unit: 'm' };
      const required = { length: 3.0, width: 2.0, unit: 'm' };
      
      const result = dimensionService.checkDimensionSufficiency(available, required);
      expect(result.isValid).toBe(true);
      expect(result.canFit).toBe(true);
      expect(result.remainingDimensions.length).toBe(3.0);
      expect(result.remainingDimensions.width).toBe(2.0);
    });

    test('should handle insufficient dimensions', () => {
      const available = { length: 2.0, width: 1.0, unit: 'm' };
      const required = { length: 3.0, width: 2.0, unit: 'm' };
      
      const result = dimensionService.checkDimensionSufficiency(available, required);
      expect(result.isValid).toBe(true);
      expect(result.canFit).toBe(false);
      expect(result.shortfall.length).toBe(1.0);
      expect(result.shortfall.width).toBe(1.0);
    });

    test('should handle unit conversion in sufficiency check', () => {
      const available = { length: 200.0, width: 150.0, unit: 'cm' };
      const required = { length: 1.0, width: 1.0, unit: 'm' };
      
      const result = dimensionService.checkDimensionSufficiency(available, required);
      expect(result.isValid).toBe(true);
      expect(result.canFit).toBe(true);
    });
  });

  /**
   * Property 1: RM Dimension Validation
   * Validates: Requirements 1.1, 1.4, 8.2
   * 
   * This property ensures that RM dimension validation works correctly
   * across all valid input ranges and handles edge cases properly.
   */
  describe('Property 1: RM Dimension Validation', () => {
    test('should maintain validation consistency for RM products', () => {
      return fc.assert(fc.property(
        fc.record({
          product_type: fc.constantFrom('RM', 'FG'),
          track_by_dimensions: fc.boolean(),
          unit_of_measure: fc.constantFrom('inch', 'cm', 'm', null, undefined)
        }),
        (productData) => {
          const result = dimensionService.validateProductDimensionRequirement(productData);
          
          // Property 1.1: RM products with proper setup should be valid
          if (productData.product_type === 'RM' && 
              productData.track_by_dimensions === true && 
              ['inch', 'cm', 'm'].includes(productData.unit_of_measure)) {
            expect(result.isValid).toBe(true);
            expect(result.requiresDimensions).toBe(true);
            expect(result.unit).toBe(productData.unit_of_measure);
          }
          
          // Property 1.2: RM products without proper setup should be invalid
          if (productData.product_type === 'RM' && 
              (productData.track_by_dimensions !== true || 
               !['inch', 'cm', 'm'].includes(productData.unit_of_measure))) {
            expect(result.isValid).toBe(false);
            expect(result.error).toBeDefined();
          }
          
          // Property 1.3: Non-RM products should not require dimensions
          if (productData.product_type !== 'RM') {
            expect(result.isValid).toBe(true);
            expect(result.requiresDimensions).toBe(false);
          }
        }
      ), { numRuns: 100 });
    });

    test('should validate dimensions correctly across all ranges', () => {
      return fc.assert(fc.property(
        fc.record({
          length: fc.float({ min: -10.0, max: 1000.0 }),
          width: fc.float({ min: -10.0, max: 1000.0 }),
          unit: fc.constantFrom('inch', 'cm', 'm', 'invalid', null)
        }),
        (dimData) => {
          const result = dimensionService.validateDimensions(dimData.length, dimData.width, dimData.unit);
          
          // Property 1.4: Valid dimensions should pass validation
          if (dimData.length > 0 && dimData.width > 0 && 
              ['inch', 'cm', 'm'].includes(dimData.unit) &&
              dimData.length <= 1000 && dimData.width <= 1000) {
            expect(result.isValid).toBe(true);
            expect(result.errors).toBeUndefined();
          }
          
          // Property 1.5: Invalid dimensions should fail validation
          if (dimData.length <= 0 || dimData.width <= 0 || 
              !['inch', 'cm', 'm'].includes(dimData.unit) ||
              dimData.length > 1000 || dimData.width > 1000) {
            expect(result.isValid).toBe(false);
            expect(result.errors).toBeDefined();
            expect(Array.isArray(result.errors)).toBe(true);
          }
        }
      ), { numRuns: 100 });
    });
  });

  /**
   * Property 7: Unit Conversion Accuracy
   * Validates: Requirements 3.5, 9.1, 9.2
   * 
   * This property ensures that unit conversions are mathematically accurate
   * and maintain precision across all supported units.
   */
  describe('Property 7: Unit Conversion Accuracy', () => {
    test('should maintain conversion accuracy and reversibility', () => {
      return fc.assert(fc.property(
        fc.record({
          value: fc.float({ min: 0.001, max: 100.0 }),
          fromUnit: fc.constantFrom('inch', 'cm', 'm'),
          toUnit: fc.constantFrom('inch', 'cm', 'm')
        }),
        (convData) => {
          const result1 = dimensionService.convertUnit(convData.value, convData.fromUnit, convData.toUnit);
          
          // Property 7.1: Valid conversions should succeed
          expect(result1.isValid).toBe(true);
          expect(result1.value).toBeGreaterThan(0);
          expect(result1.originalValue).toBe(convData.value);
          expect(result1.originalUnit).toBe(convData.fromUnit);
          expect(result1.targetUnit).toBe(convData.toUnit);
          
          // Property 7.2: Reverse conversion should return original value
          const result2 = dimensionService.convertUnit(result1.value, convData.toUnit, convData.fromUnit);
          expect(result2.isValid).toBe(true);
          expect(result2.value).toBeCloseTo(convData.value, 6);
          
          // Property 7.3: Same unit conversion should return same value
          if (convData.fromUnit === convData.toUnit) {
            expect(result1.value).toBe(convData.value);
          }
          
          // Property 7.4: Known conversion ratios should be accurate
          if (convData.fromUnit === 'm' && convData.toUnit === 'cm') {
            expect(result1.value).toBeCloseTo(convData.value * 100, 6);
          }
          if (convData.fromUnit === 'cm' && convData.toUnit === 'm') {
            expect(result1.value).toBeCloseTo(convData.value / 100, 6);
          }
          if (convData.fromUnit === 'm' && convData.toUnit === 'inch') {
            expect(result1.value).toBeCloseTo(convData.value / 0.0254, 6);
          }
        }
      ), { numRuns: 100 });
    });

    test('should handle dimension object conversions correctly', () => {
      return fc.assert(fc.property(
        fc.record({
          length: fc.float({ min: 0.1, max: 50.0 }),
          width: fc.float({ min: 0.1, max: 50.0 }),
          fromUnit: fc.constantFrom('inch', 'cm', 'm'),
          toUnit: fc.constantFrom('inch', 'cm', 'm')
        }),
        (dimData) => {
          const dimensions = {
            length: dimData.length,
            width: dimData.width,
            unit: dimData.fromUnit
          };
          
          const result = dimensionService.convertDimensions(dimensions, dimData.toUnit);
          
          // Property 7.5: Dimension object conversion should succeed
          expect(result.isValid).toBe(true);
          expect(result.dimensions.unit).toBe(dimData.toUnit);
          expect(result.originalDimensions).toEqual(dimensions);
          
          // Property 7.6: Individual dimension conversions should match
          const lengthResult = dimensionService.convertUnit(dimData.length, dimData.fromUnit, dimData.toUnit);
          const widthResult = dimensionService.convertUnit(dimData.width, dimData.fromUnit, dimData.toUnit);
          
          expect(result.dimensions.length).toBeCloseTo(lengthResult.value, 6);
          expect(result.dimensions.width).toBeCloseTo(widthResult.value, 6);
          
          // Property 7.7: Area should be preserved across unit conversions
          const originalArea = dimData.length * dimData.width;
          const convertedArea = result.dimensions.length * result.dimensions.width;
          
          // Convert areas to same unit for comparison
          const areaConversionFactor = dimensionService.UNIT_CONVERSIONS[dimData.fromUnit] / dimensionService.UNIT_CONVERSIONS[dimData.toUnit];
          const expectedConvertedArea = originalArea * (areaConversionFactor * areaConversionFactor);
          
          expect(convertedArea).toBeCloseTo(expectedConvertedArea, 6);
        }
      ), { numRuns: 100 });
    });
  });

  /**
   * Property 6: Dimension Sufficiency Check
   * Validates: Requirements 3.1, 3.4
   * 
   * This property ensures that dimension sufficiency checking works correctly
   * for material allocation decisions.
   */
  describe('Property 6: Dimension Sufficiency Check', () => {
    test('should correctly determine if dimensions can fit', () => {
      return fc.assert(fc.property(
        fc.record({
          availableLength: fc.float({ min: 0.1, max: 20.0 }),
          availableWidth: fc.float({ min: 0.1, max: 20.0 }),
          requiredLength: fc.float({ min: 0.1, max: 20.0 }),
          requiredWidth: fc.float({ min: 0.1, max: 20.0 }),
          availableUnit: fc.constantFrom('inch', 'cm', 'm'),
          requiredUnit: fc.constantFrom('inch', 'cm', 'm')
        }),
        (data) => {
          const available = {
            length: data.availableLength,
            width: data.availableWidth,
            unit: data.availableUnit
          };
          
          const required = {
            length: data.requiredLength,
            width: data.requiredWidth,
            unit: data.requiredUnit
          };
          
          const result = dimensionService.checkDimensionSufficiency(available, required);
          
          // Property 6.1: Sufficiency check should always succeed for valid inputs
          expect(result.isValid).toBe(true);
          expect(result.availableDimensions).toEqual(available);
          
          // Property 6.2: Convert required to available unit for comparison
          const convertedRequired = dimensionService.convertDimensions(required, data.availableUnit);
          expect(convertedRequired.isValid).toBe(true);
          
          const reqLength = convertedRequired.dimensions.length;
          const reqWidth = convertedRequired.dimensions.width;
          
          // Property 6.3: Fit determination should be mathematically correct
          const expectedCanFit = data.availableLength >= reqLength && data.availableWidth >= reqWidth;
          expect(result.canFit).toBe(expectedCanFit);
          expect(result.lengthSufficient).toBe(data.availableLength >= reqLength);
          expect(result.widthSufficient).toBe(data.availableWidth >= reqWidth);
          
          // Property 6.4: Remaining dimensions should be calculated correctly
          if (result.canFit) {
            expect(result.remainingDimensions).toBeDefined();
            expect(result.remainingDimensions.length).toBeCloseTo(data.availableLength - reqLength, 6);
            expect(result.remainingDimensions.width).toBeCloseTo(data.availableWidth - reqWidth, 6);
            expect(result.remainingDimensions.unit).toBe(data.availableUnit);
            expect(result.shortfall).toBeNull();
          } else {
            expect(result.remainingDimensions).toBeNull();
            expect(result.shortfall).toBeDefined();
            expect(result.shortfall.length).toBeCloseTo(Math.max(0, reqLength - data.availableLength), 6);
            expect(result.shortfall.width).toBeCloseTo(Math.max(0, reqWidth - data.availableWidth), 6);
          }
        }
      ), { numRuns: 100 });
    });

    test('should handle edge cases correctly', () => {
      return fc.assert(fc.property(
        fc.record({
          availableLength: fc.float({ min: 0.1, max: 5.0 }),
          availableWidth: fc.float({ min: 0.1, max: 5.0 }),
          unit: fc.constantFrom('inch', 'cm', 'm')
        }),
        (data) => {
          const available = {
            length: data.availableLength,
            width: data.availableWidth,
            unit: data.unit
          };
          
          // Property 6.5: Exact fit should work
          const exactRequired = { ...available };
          const exactResult = dimensionService.checkDimensionSufficiency(available, exactRequired);
          expect(exactResult.canFit).toBe(true);
          expect(exactResult.remainingDimensions.length).toBeCloseTo(0, 6);
          expect(exactResult.remainingDimensions.width).toBeCloseTo(0, 6);
          
          // Property 6.6: Slightly larger requirement should not fit
          const slightlyLargerRequired = {
            length: data.availableLength + 0.001,
            width: data.availableWidth + 0.001,
            unit: data.unit
          };
          const largerResult = dimensionService.checkDimensionSufficiency(available, slightlyLargerRequired);
          expect(largerResult.canFit).toBe(false);
          
          // Property 6.7: Smaller requirement should fit with positive remainder
          const smallerRequired = {
            length: data.availableLength * 0.5,
            width: data.availableWidth * 0.5,
            unit: data.unit
          };
          const smallerResult = dimensionService.checkDimensionSufficiency(available, smallerRequired);
          expect(smallerResult.canFit).toBe(true);
          expect(smallerResult.remainingDimensions.length).toBeGreaterThan(0);
          expect(smallerResult.remainingDimensions.width).toBeGreaterThan(0);
        }
      ), { numRuns: 100 });
    });
  });

  describe('Multiple Dimension Sets Validation', () => {
    test('should validate multiple dimension scenarios', () => {
      const dimensionSets = [
        { length: 2.0, width: 2.0, unit: 'm', quantity: 2 },
        { length: 3.0, width: 1.5, unit: 'm', quantity: 1 },
        { length: 60.0, width: 40.0, unit: 'cm', quantity: 3 }
      ];
      
      const result = dimensionService.validateMultipleDimensionSets(dimensionSets, 'm');
      expect(result.isValid).toBe(true);
      expect(result.dimensionSets).toHaveLength(3);
      expect(result.totalArea).toBeGreaterThan(0);
    });

    test('should handle unit conversion in multiple sets', () => {
      const dimensionSets = [
        { length: 100.0, width: 50.0, unit: 'cm', quantity: 1 },
        { length: 39.37, width: 19.69, unit: 'inch', quantity: 1 }
      ];
      
      const result = dimensionService.validateMultipleDimensionSets(dimensionSets, 'm');
      expect(result.isValid).toBe(true);
      
      // Both should convert to approximately 0.5 m²
      result.dimensionSets.forEach(set => {
        expect(set.area).toBeCloseTo(0.5, 1);
      });
    });
  });
});

/**
 * Test Tags for Property-Based Testing:
 * Feature: dimension-based-inventory
 * Property 1: RM Dimension Validation
 * Property 6: Dimension Sufficiency Check  
 * Property 7: Unit Conversion Accuracy
 */