/**
 * BOMItem Model Tests
 * Tests for dimension-based BOM requirements
 */

const fc = require('fast-check');

// Mock Sequelize for testing
const mockSequelize = {
  authenticate: jest.fn().mockResolvedValue(true),
  close: jest.fn().mockResolvedValue(true),
  sync: jest.fn().mockResolvedValue(true)
};

// Mock BOMItem model
const mockBOMItem = {
  sequelize: mockSequelize,
  build: jest.fn(),
  create: jest.fn(),
  findByPk: jest.fn(),
  findAll: jest.fn()
};

// Mock related models
const mockBOM = { id: 1, fg_product_id: 1 };
const mockRMProduct = { id: 2, product_type: 'RM', track_by_dimensions: true, unit_of_measure: 'm' };

// Create mock instance with validation
const createMockBOMItemInstance = (data) => {
  const instance = { ...data };
  
  // Add validation logic
  const validate = () => {
    if (!instance.bom_id || !instance.rm_product_id || !instance.quantity_per_unit) {
      throw new Error('Missing required fields');
    }
    
    if (instance.quantity_per_unit <= 0) {
      throw new Error('Quantity per unit must be positive');
    }
    
    // Dimension-based validation
    if (instance.use_dimensions === true) {
      if (!instance.required_length || !instance.required_width || !instance.dimension_unit) {
        throw new Error('Dimension-based BOM items must have required_length, required_width, and dimension_unit');
      }
      
      if (instance.required_length <= 0 || instance.required_width <= 0) {
        throw new Error('Required dimensions must be positive');
      }
      
      if (!['inch', 'cm', 'm'].includes(instance.dimension_unit)) {
        throw new Error('Invalid dimension unit');
      }
    }
    
    // Validate dimension requirements consistency
    if ((instance.required_length !== null && instance.required_length !== undefined) || 
        (instance.required_width !== null && instance.required_width !== undefined) || 
        (instance.dimension_unit !== null && instance.dimension_unit !== undefined)) {
      if (!instance.use_dimensions) {
        throw new Error('Dimension fields require use_dimensions to be TRUE');
      }
    }
  };
  
  // Add instance methods
  instance.getRequiredArea = function() {
    if (!this.use_dimensions || !this.required_length || !this.required_width) {
      return null;
    }
    return parseFloat(this.required_length) * parseFloat(this.required_width);
  };
  
  instance.calculateTotalAreaRequired = function(productionQuantity) {
    const areaPerUnit = this.getRequiredArea();
    if (areaPerUnit === null) {
      return null;
    }
    return areaPerUnit * productionQuantity;
  };
  
  instance.isDimensionBased = function() {
    return this.use_dimensions === true;
  };
  
  instance.isQuantityBased = function() {
    return this.use_dimensions === false || this.use_dimensions === null;
  };
  
  instance.canFitInPiece = function(pieceLength, pieceWidth, pieceUnit = 'm') {
    if (!this.use_dimensions) {
      return null; // Cannot determine for quantity-based items
    }
    
    // For simplicity, assume same unit (unit conversion would be added later)
    if (pieceUnit !== this.dimension_unit) {
      return null; // Unit conversion needed
    }
    
    return pieceLength >= this.required_length && pieceWidth >= this.required_width;
  };
  
  instance.validate = jest.fn().mockImplementation(validate);
  instance.save = jest.fn().mockResolvedValue(instance);
  instance.destroy = jest.fn().mockResolvedValue(true);
  instance.id = Math.floor(Math.random() * 1000);
  instance.created_at = new Date();
  instance.updated_at = new Date();
  
  // Set defaults
  instance.use_dimensions = instance.use_dimensions || false;
  
  return instance;
};

// Setup mock implementations
mockBOMItem.create.mockImplementation(async (data) => {
  const instance = createMockBOMItemInstance(data);
  await instance.validate();
  return instance;
});

describe('BOMItem Model - Dimension Requirements', () => {
  let testBOM;
  let testRMProduct;

  beforeAll(async () => {
    testBOM = mockBOM;
    testRMProduct = mockRMProduct;
  });

  afterAll(async () => {
    // No cleanup needed for mocks
  });

  beforeEach(async () => {
    // Reset mocks before each test
    jest.clearAllMocks();
  });

  describe('Basic Model Operations', () => {
    test('should create quantity-based BOM item', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 2.5,
        use_dimensions: false
      });

      expect(bomItem).toBeDefined();
      expect(bomItem.bom_id).toBe(testBOM.id);
      expect(bomItem.rm_product_id).toBe(testRMProduct.id);
      expect(bomItem.quantity_per_unit).toBe(2.5);
      expect(bomItem.use_dimensions).toBe(false);
      expect(bomItem.isQuantityBased()).toBe(true);
      expect(bomItem.isDimensionBased()).toBe(false);
    });

    test('should create dimension-based BOM item', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0, // Still needed for compatibility
        use_dimensions: true,
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      expect(bomItem).toBeDefined();
      expect(bomItem.use_dimensions).toBe(true);
      expect(bomItem.required_length).toBe(2.0);
      expect(bomItem.required_width).toBe(1.5);
      expect(bomItem.dimension_unit).toBe('m');
      expect(bomItem.isDimensionBased()).toBe(true);
      expect(bomItem.isQuantityBased()).toBe(false);
    });

    test('should enforce required fields', async () => {
      await expect(mockBOMItem.create({
        // Missing required fields
      })).rejects.toThrow();
    });

    test('should enforce positive quantity per unit', async () => {
      await expect(mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: -1.0 // Invalid negative quantity
      })).rejects.toThrow();
    });
  });

  describe('Dimension-Based Validation', () => {
    test('should require dimension fields when use_dimensions is true', async () => {
      await expect(mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true
        // Missing required_length, required_width, dimension_unit
      })).rejects.toThrow('Dimension-based BOM items must have required_length, required_width, and dimension_unit');
    });

    test('should enforce positive required dimensions', async () => {
      await expect(mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: -1.0, // Invalid negative length
        required_width: 1.5,
        dimension_unit: 'm'
      })).rejects.toThrow('Required dimensions must be positive');
    });

    test('should enforce valid dimension unit', async () => {
      await expect(mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'invalid_unit'
      })).rejects.toThrow('Invalid dimension unit');
    });

    test('should require use_dimensions=true when dimension fields are provided', async () => {
      await expect(mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: false,
        required_length: 2.0, // Dimension field provided but use_dimensions is false
        required_width: 1.5,
        dimension_unit: 'm'
      })).rejects.toThrow('Dimension fields require use_dimensions to be TRUE');
    });
  });

  describe('Instance Methods', () => {
    test('getRequiredArea should calculate area for dimension-based items', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      expect(bomItem.getRequiredArea()).toBe(3.0);
    });

    test('getRequiredArea should return null for quantity-based items', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 2.5,
        use_dimensions: false
      });

      expect(bomItem.getRequiredArea()).toBeNull();
    });

    test('calculateTotalAreaRequired should scale by production quantity', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      expect(bomItem.calculateTotalAreaRequired(10)).toBe(30.0); // 3.0 * 10
      expect(bomItem.calculateTotalAreaRequired(5)).toBe(15.0);  // 3.0 * 5
    });

    test('canFitInPiece should check if piece can accommodate requirements', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: 2.0,
        required_width: 1.5,
        dimension_unit: 'm'
      });

      expect(bomItem.canFitInPiece(3.0, 2.0, 'm')).toBe(true);  // Piece is larger
      expect(bomItem.canFitInPiece(2.0, 1.5, 'm')).toBe(true);  // Piece is exact size
      expect(bomItem.canFitInPiece(1.5, 1.0, 'm')).toBe(false); // Piece is too small
      expect(bomItem.canFitInPiece(3.0, 1.0, 'm')).toBe(false); // Width too small
    });

    test('canFitInPiece should return null for quantity-based items', async () => {
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 2.5,
        use_dimensions: false
      });

      expect(bomItem.canFitInPiece(3.0, 2.0, 'm')).toBeNull();
    });
  });

  /**
   * Property 4: BOM Dimension Requirements
   * Validates: Requirements 2.1, 2.2, 8.3
   * 
   * This property ensures that BOM items correctly handle dimension-based
   * requirements for RM products with proper validation and calculations.
   */
  describe('Property 4: BOM Dimension Requirements', () => {
    test('should maintain consistency between dimension flags and requirements', () => {
      return fc.assert(fc.asyncProperty(
        fc.record({
          quantity_per_unit: fc.float({ min: 0.1, max: 100.0 }),
          use_dimensions: fc.boolean(),
          required_length: fc.float({ min: 0.1, max: 10.0 }),
          required_width: fc.float({ min: 0.1, max: 10.0 }),
          dimension_unit: fc.constantFrom('inch', 'cm', 'm')
        }),
        async (data) => {
          const bomItemData = {
            bom_id: testBOM.id,
            rm_product_id: testRMProduct.id,
            quantity_per_unit: data.quantity_per_unit,
            use_dimensions: data.use_dimensions
          };

          // Add dimension fields only if use_dimensions is true
          if (data.use_dimensions) {
            bomItemData.required_length = data.required_length;
            bomItemData.required_width = data.required_width;
            bomItemData.dimension_unit = data.dimension_unit;
          }

          const bomItem = await mockBOMItem.create(bomItemData);

          // Property 4.1: Dimension flag consistency
          expect(bomItem.isDimensionBased()).toBe(data.use_dimensions);
          expect(bomItem.isQuantityBased()).toBe(!data.use_dimensions);

          // Property 4.2: Dimension requirements are set correctly
          if (data.use_dimensions) {
            expect(bomItem.required_length).toBeCloseTo(data.required_length, 3);
            expect(bomItem.required_width).toBeCloseTo(data.required_width, 3);
            expect(bomItem.dimension_unit).toBe(data.dimension_unit);
            
            // Property 4.3: Area calculation is consistent
            const expectedArea = data.required_length * data.required_width;
            expect(bomItem.getRequiredArea()).toBeCloseTo(expectedArea, 6);
          } else {
            expect(bomItem.getRequiredArea()).toBeNull();
          }

          // Property 4.4: Production scaling works correctly
          if (data.use_dimensions) {
            const productionQuantity = Math.floor(Math.random() * 20) + 1;
            const expectedTotalArea = bomItem.getRequiredArea() * productionQuantity;
            expect(bomItem.calculateTotalAreaRequired(productionQuantity)).toBeCloseTo(expectedTotalArea, 6);
          }

          // Clean up
          await bomItem.destroy();
        }
      ), { numRuns: 100 });
    });

    test('should correctly validate piece fitting for dimension-based items', () => {
      return fc.assert(fc.asyncProperty(
        fc.record({
          required_length: fc.float({ min: 1.0, max: 5.0 }),
          required_width: fc.float({ min: 1.0, max: 5.0 }),
          piece_length: fc.float({ min: 0.5, max: 10.0 }),
          piece_width: fc.float({ min: 0.5, max: 10.0 }),
          dimension_unit: fc.constantFrom('inch', 'cm', 'm')
        }),
        async (data) => {
          const bomItem = await mockBOMItem.create({
            bom_id: testBOM.id,
            rm_product_id: testRMProduct.id,
            quantity_per_unit: 1.0,
            use_dimensions: true,
            required_length: data.required_length,
            required_width: data.required_width,
            dimension_unit: data.dimension_unit
          });

          const canFit = bomItem.canFitInPiece(data.piece_length, data.piece_width, data.dimension_unit);

          // Property 4.5: Piece fitting logic is correct
          const expectedCanFit = data.piece_length >= data.required_length && data.piece_width >= data.required_width;
          expect(canFit).toBe(expectedCanFit);

          // Property 4.6: Edge cases are handled correctly
          const exactFit = bomItem.canFitInPiece(data.required_length, data.required_width, data.dimension_unit);
          expect(exactFit).toBe(true);

          const tooSmall = bomItem.canFitInPiece(data.required_length - 0.1, data.required_width - 0.1, data.dimension_unit);
          expect(tooSmall).toBe(false);

          // Clean up
          await bomItem.destroy();
        }
      ), { numRuns: 100 });
    });

    test('should handle mixed BOM scenarios (dimension + quantity based)', () => {
      return fc.assert(fc.asyncProperty(
        fc.array(
          fc.record({
            use_dimensions: fc.boolean(),
            quantity_per_unit: fc.float({ min: 0.1, max: 10.0 }),
            required_length: fc.float({ min: 0.5, max: 5.0 }),
            required_width: fc.float({ min: 0.5, max: 5.0 }),
            dimension_unit: fc.constantFrom('inch', 'cm', 'm')
          }),
          { minLength: 1, maxLength: 5 }
        ),
        async (bomItemsData) => {
          const bomItems = [];

          for (const data of bomItemsData) {
            const bomItemData = {
              bom_id: testBOM.id,
              rm_product_id: testRMProduct.id,
              quantity_per_unit: data.quantity_per_unit,
              use_dimensions: data.use_dimensions
            };

            if (data.use_dimensions) {
              bomItemData.required_length = data.required_length;
              bomItemData.required_width = data.required_width;
              bomItemData.dimension_unit = data.dimension_unit;
            }

            const bomItem = await mockBOMItem.create(bomItemData);
            bomItems.push(bomItem);
          }

          // Property 4.7: Mixed BOM support works correctly
          const dimensionBasedItems = bomItems.filter(item => item.isDimensionBased());
          const quantityBasedItems = bomItems.filter(item => item.isQuantityBased());

          expect(dimensionBasedItems.length + quantityBasedItems.length).toBe(bomItems.length);

          // Property 4.8: Each item type behaves correctly
          dimensionBasedItems.forEach(item => {
            expect(item.getRequiredArea()).toBeGreaterThan(0);
            expect(item.required_length).toBeGreaterThan(0);
            expect(item.required_width).toBeGreaterThan(0);
            expect(['inch', 'cm', 'm']).toContain(item.dimension_unit);
          });

          quantityBasedItems.forEach(item => {
            expect(item.getRequiredArea()).toBeNull();
            expect(item.canFitInPiece(5.0, 5.0, 'm')).toBeNull();
          });

          // Clean up
          for (const bomItem of bomItems) {
            await bomItem.destroy();
          }
        }
      ), { numRuns: 50 });
    });
  });

  describe('Unit Tests for specific scenarios', () => {
    test('should handle clothing manufacturing BOM scenario', async () => {
      // Scenario: T-shirt requiring cotton fabric
      const bomItem = await mockBOMItem.create({
        bom_id: testBOM.id,
        rm_product_id: testRMProduct.id,
        quantity_per_unit: 1.0,
        use_dimensions: true,
        required_length: 1.2, // 1.2m length for t-shirt
        required_width: 0.8,   // 0.8m width for t-shirt
        dimension_unit: 'm'
      });

      // Verify BOM item properties
      expect(bomItem.getRequiredArea()).toBe(0.96); // 1.2 * 0.8
      expect(bomItem.calculateTotalAreaRequired(100)).toBe(96.0); // 100 t-shirts

      // Test piece fitting scenarios
      expect(bomItem.canFitInPiece(2.0, 1.5, 'm')).toBe(true);  // Large fabric piece
      expect(bomItem.canFitInPiece(1.2, 0.8, 'm')).toBe(true);  // Exact size
      expect(bomItem.canFitInPiece(1.0, 0.6, 'm')).toBe(false); // Too small
    });

    test('should handle multiple dimension scenarios', async () => {
      const scenarios = [
        { length: 2.0, width: 2.0, unit: 'm', description: '2m × 2m square' },
        { length: 3.0, width: 1.5, unit: 'm', description: '3m × 1.5m rectangle' },
        { length: 60.0, width: 40.0, unit: 'cm', description: '60cm × 40cm in cm' },
        { length: 24.0, width: 18.0, unit: 'inch', description: '24" × 18" in inches' }
      ];

      for (const scenario of scenarios) {
        const bomItem = await mockBOMItem.create({
          bom_id: testBOM.id,
          rm_product_id: testRMProduct.id,
          quantity_per_unit: 1.0,
          use_dimensions: true,
          required_length: scenario.length,
          required_width: scenario.width,
          dimension_unit: scenario.unit
        });

        expect(bomItem.required_length).toBe(scenario.length);
        expect(bomItem.required_width).toBe(scenario.width);
        expect(bomItem.dimension_unit).toBe(scenario.unit);
        expect(bomItem.getRequiredArea()).toBe(scenario.length * scenario.width);
      }
    });
  });
});

/**
 * Test Tags for Property-Based Testing:
 * Feature: dimension-based-inventory, Property 4: BOM Dimension Requirements
 */