/**
 * RMCuttingOperation Model Tests
 * Tests for cutting operation audit trail and waste tracking
 */

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 RMCuttingOperation model
const mockRMCuttingOperation = {
  sequelize: mockSequelize,
  build: jest.fn(),
  create: jest.fn(),
  findByPk: jest.fn(),
  findAll: jest.fn(),
  findByProductionOrder: jest.fn(),
  findByRMPiece: jest.fn(),
  getProductionMaterialUsage: jest.fn()
};

// Mock related models
const mockProductionOrder = { id: 1, order_number: 'PO-001' };
const mockRMPiece = { id: 1, length: 6.0, width: 4.0, unit: 'm', status: 'FULL' };
const mockBOMItem = { id: 1, required_length: 2.0, required_width: 1.5 };
const mockUser = { id: 1, name: 'Test User' };

// Create mock instance with validation
const createMockRMCuttingOperationInstance = (data) => {
  const instance = { ...data };
  
  // Add validation logic
  const validate = () => {
    if (!instance.production_order_id || !instance.rm_piece_id || !instance.bom_item_id) {
      throw new Error('Missing required foreign keys');
    }
    
    if (!instance.cut_length || !instance.cut_width || !instance.unit) {
      throw new Error('Missing required cut dimensions');
    }
    
    if (instance.cut_length <= 0 || instance.cut_width <= 0) {
      throw new Error('Cut dimensions must be positive');
    }
    
    if (!['inch', 'cm', 'm'].includes(instance.unit)) {
      throw new Error('Invalid unit');
    }
    
    // Validate waste_pieces if provided
    if (instance.waste_pieces !== null && instance.waste_pieces !== undefined) {
      if (!Array.isArray(instance.waste_pieces)) {
        throw new Error('waste_pieces must be an array');
      }
      for (const piece of instance.waste_pieces) {
        if (!piece.length || !piece.width || !piece.status) {
          throw new Error('Each waste piece must have length, width, and status');
        }
        if (!['WASTE', 'SCRAP'].includes(piece.status)) {
          throw new Error('Waste piece status must be WASTE or SCRAP');
        }
        if (piece.length <= 0 || piece.width <= 0) {
          throw new Error('Waste piece dimensions must be positive');
        }
      }
    }
    
    // Validate scrap_dimensions if provided
    if (instance.scrap_dimensions !== null && instance.scrap_dimensions !== undefined) {
      if (typeof instance.scrap_dimensions !== 'object' || Array.isArray(instance.scrap_dimensions)) {
        throw new Error('scrap_dimensions must be an object');
      }
      if (instance.scrap_dimensions.length !== undefined && instance.scrap_dimensions.length < 0) {
        throw new Error('Scrap length cannot be negative');
      }
      if (instance.scrap_dimensions.width !== undefined && instance.scrap_dimensions.width < 0) {
        throw new Error('Scrap width cannot be negative');
      }
    }
  };
  
  // Add instance methods
  instance.getCutArea = function() {
    return parseFloat(this.cut_length) * parseFloat(this.cut_width);
  };
  
  instance.getTotalWasteArea = function() {
    if (!this.waste_pieces || !Array.isArray(this.waste_pieces)) {
      return 0;
    }
    return this.waste_pieces.reduce((total, piece) => {
      return total + (parseFloat(piece.length) * parseFloat(piece.width));
    }, 0);
  };
  
  instance.getScrapArea = function() {
    if (!this.scrap_dimensions) {
      return 0;
    }
    const scrap = this.scrap_dimensions;
    return (parseFloat(scrap.length) || 0) * (parseFloat(scrap.width) || 0);
  };
  
  instance.getWastePiecesByStatus = function(status) {
    if (!this.waste_pieces || !Array.isArray(this.waste_pieces)) {
      return [];
    }
    return this.waste_pieces.filter(piece => piece.status === status);
  };
  
  instance.hasRemainingPiece = function() {
    return this.remaining_piece_id !== null && this.remaining_piece_id !== undefined;
  };
  
  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();
  instance.cut_at = instance.cut_at || new Date();
  
  return instance;
};

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

mockRMCuttingOperation.findByProductionOrder.mockImplementation(async (productionOrderId) => {
  // Mock implementation returning sample operations
  return [
    createMockRMCuttingOperationInstance({
      production_order_id: productionOrderId,
      rm_piece_id: 1,
      bom_item_id: 1,
      cut_length: 2.0,
      cut_width: 1.5,
      unit: 'm'
    }),
    createMockRMCuttingOperationInstance({
      production_order_id: productionOrderId,
      rm_piece_id: 2,
      bom_item_id: 1,
      cut_length: 1.5,
      cut_width: 1.0,
      unit: 'm'
    })
  ];
});

mockRMCuttingOperation.findByRMPiece.mockImplementation(async (rmPieceId) => {
  // Mock implementation returning operations for a specific piece
  return [
    createMockRMCuttingOperationInstance({
      production_order_id: 1,
      rm_piece_id: rmPieceId,
      bom_item_id: 1,
      cut_length: 2.0,
      cut_width: 1.5,
      unit: 'm'
    })
  ];
});

describe('RMCuttingOperation Model', () => {
  let testProductionOrder;
  let testRMPiece;
  let testBOMItem;
  let testUser;

  beforeAll(async () => {
    testProductionOrder = mockProductionOrder;
    testRMPiece = mockRMPiece;
    testBOMItem = mockBOMItem;
    testUser = mockUser;
  });

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

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

  describe('Basic Model Operations', () => {
    test('should create cutting operation with valid data', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        cut_by_user_id: testUser.id
      });

      expect(operation).toBeDefined();
      expect(operation.production_order_id).toBe(testProductionOrder.id);
      expect(operation.rm_piece_id).toBe(testRMPiece.id);
      expect(operation.bom_item_id).toBe(testBOMItem.id);
      expect(operation.cut_length).toBe(2.0);
      expect(operation.cut_width).toBe(1.5);
      expect(operation.unit).toBe('m');
    });

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

    test('should enforce positive cut dimensions', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: -1.0, // Invalid negative length
        cut_width: 1.5,
        unit: 'm'
      })).rejects.toThrow();
    });

    test('should enforce valid unit enum', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'invalid_unit'
      })).rejects.toThrow();
    });
  });

  describe('Waste Pieces Validation', () => {
    test('should validate waste pieces structure', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: 'invalid' // Should be array
      })).rejects.toThrow('waste_pieces must be an array');
    });

    test('should validate waste piece properties', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.0, width: 0.5 } // Missing status
        ]
      })).rejects.toThrow('Each waste piece must have length, width, and status');
    });

    test('should validate waste piece status', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.0, width: 0.5, status: 'INVALID' }
        ]
      })).rejects.toThrow('Waste piece status must be WASTE or SCRAP');
    });

    test('should create operation with valid waste pieces', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.0, width: 0.5, status: 'WASTE' },
          { length: 0.5, width: 0.3, status: 'SCRAP' }
        ]
      });

      expect(operation.waste_pieces).toHaveLength(2);
      expect(operation.waste_pieces[0].status).toBe('WASTE');
      expect(operation.waste_pieces[1].status).toBe('SCRAP');
    });
  });

  describe('Scrap Dimensions Validation', () => {
    test('should validate scrap dimensions structure', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        scrap_dimensions: [] // Should be object, not array
      })).rejects.toThrow('scrap_dimensions must be an object');
    });

    test('should validate scrap dimensions values', async () => {
      await expect(mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        scrap_dimensions: { length: -1.0, width: 0.5 }
      })).rejects.toThrow('Scrap length cannot be negative');
    });

    test('should create operation with valid scrap dimensions', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        scrap_dimensions: { length: 0.5, width: 0.3 }
      });

      expect(operation.scrap_dimensions.length).toBe(0.5);
      expect(operation.scrap_dimensions.width).toBe(0.3);
    });
  });

  describe('Instance Methods', () => {
    test('getCutArea should calculate cut area correctly', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm'
      });

      expect(operation.getCutArea()).toBe(3.0);
    });

    test('getTotalWasteArea should calculate total waste area', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.0, width: 0.5, status: 'WASTE' },
          { length: 0.5, width: 0.3, status: 'SCRAP' }
        ]
      });

      expect(operation.getTotalWasteArea()).toBe(0.65); // 1.0*0.5 + 0.5*0.3
    });

    test('getScrapArea should calculate scrap area correctly', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        scrap_dimensions: { length: 0.5, width: 0.3 }
      });

      expect(operation.getScrapArea()).toBe(0.15);
    });

    test('getWastePiecesByStatus should filter waste pieces by status', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.0, width: 0.5, status: 'WASTE' },
          { length: 0.5, width: 0.3, status: 'SCRAP' },
          { length: 0.8, width: 0.4, status: 'WASTE' }
        ]
      });

      const wastePieces = operation.getWastePiecesByStatus('WASTE');
      const scrapPieces = operation.getWastePiecesByStatus('SCRAP');

      expect(wastePieces).toHaveLength(2);
      expect(scrapPieces).toHaveLength(1);
    });

    test('hasRemainingPiece should check for remaining piece', async () => {
      const operationWithRemaining = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        remaining_piece_id: 2
      });

      const operationWithoutRemaining = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm'
      });

      expect(operationWithRemaining.hasRemainingPiece()).toBe(true);
      expect(operationWithoutRemaining.hasRemainingPiece()).toBe(false);
    });
  });

  /**
   * Property 8: Cutting Operation Audit Trail
   * Validates: Requirements 4.1, 11.1
   * 
   * This property ensures that cutting operations maintain a complete audit trail
   * with proper waste tracking and material accountability.
   */
  describe('Property 8: Cutting Operation Audit Trail', () => {
    test('should maintain complete audit trail for all cutting operations', () => {
      return fc.assert(fc.asyncProperty(
        fc.record({
          cut_length: fc.float({ min: 0.1, max: 10.0 }),
          cut_width: fc.float({ min: 0.1, max: 10.0 }),
          unit: fc.constantFrom('inch', 'cm', 'm'),
          has_waste: fc.boolean(),
          has_scrap: fc.boolean(),
          has_remaining: fc.boolean(),
          waste_count: fc.integer({ min: 0, max: 5 }),
          notes: fc.option(fc.string({ maxLength: 100 }))
        }),
        async (data) => {
          // Generate waste pieces if needed
          let waste_pieces = null;
          if (data.has_waste && data.waste_count > 0) {
            waste_pieces = [];
            for (let i = 0; i < data.waste_count; i++) {
              waste_pieces.push({
                length: Math.random() * 2.0 + 0.1,
                width: Math.random() * 2.0 + 0.1,
                status: Math.random() > 0.5 ? 'WASTE' : 'SCRAP'
              });
            }
          }

          // Generate scrap dimensions if needed
          let scrap_dimensions = null;
          if (data.has_scrap) {
            scrap_dimensions = {
              length: Math.random() * 1.0 + 0.1,
              width: Math.random() * 1.0 + 0.1
            };
          }

          const operationData = {
            production_order_id: testProductionOrder.id,
            rm_piece_id: testRMPiece.id,
            bom_item_id: testBOMItem.id,
            cut_length: data.cut_length,
            cut_width: data.cut_width,
            unit: data.unit,
            waste_pieces,
            scrap_dimensions,
            remaining_piece_id: data.has_remaining ? 2 : null,
            cut_by_user_id: testUser.id,
            notes: data.notes
          };

          const operation = await mockRMCuttingOperation.create(operationData);

          // Property 8.1: Operation is created with complete audit information
          expect(operation.production_order_id).toBe(testProductionOrder.id);
          expect(operation.rm_piece_id).toBe(testRMPiece.id);
          expect(operation.bom_item_id).toBe(testBOMItem.id);
          expect(operation.cut_by_user_id).toBe(testUser.id);
          expect(operation.cut_at).toBeDefined();

          // Property 8.2: Cut dimensions are accurately recorded
          expect(operation.cut_length).toBeCloseTo(data.cut_length, 3);
          expect(operation.cut_width).toBeCloseTo(data.cut_width, 3);
          expect(operation.unit).toBe(data.unit);

          // Property 8.3: Cut area calculation is consistent
          const expectedCutArea = data.cut_length * data.cut_width;
          expect(operation.getCutArea()).toBeCloseTo(expectedCutArea, 6);

          // Property 8.4: Waste tracking is complete and accurate
          if (data.has_waste && data.waste_count > 0) {
            expect(operation.waste_pieces).toBeDefined();
            expect(Array.isArray(operation.waste_pieces)).toBe(true);
            expect(operation.waste_pieces.length).toBe(data.waste_count);
            
            // Verify each waste piece has required properties
            operation.waste_pieces.forEach(piece => {
              expect(piece.length).toBeGreaterThan(0);
              expect(piece.width).toBeGreaterThan(0);
              expect(['WASTE', 'SCRAP']).toContain(piece.status);
            });

            // Verify waste area calculation
            const calculatedWasteArea = operation.getTotalWasteArea();
            expect(calculatedWasteArea).toBeGreaterThanOrEqual(0);
          } else {
            expect(operation.getTotalWasteArea()).toBe(0);
          }

          // Property 8.5: Scrap tracking is accurate
          if (data.has_scrap) {
            expect(operation.scrap_dimensions).toBeDefined();
            expect(operation.getScrapArea()).toBeGreaterThan(0);
          } else {
            expect(operation.getScrapArea()).toBe(0);
          }

          // Property 8.6: Remaining piece tracking is consistent
          expect(operation.hasRemainingPiece()).toBe(data.has_remaining);

          // Property 8.7: Audit trail completeness
          expect(operation.id).toBeDefined();
          expect(operation.created_at).toBeDefined();
          expect(operation.updated_at).toBeDefined();

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

    test('should maintain material balance in cutting operations', () => {
      return fc.assert(fc.asyncProperty(
        fc.record({
          original_length: fc.float({ min: 2.0, max: 10.0 }),
          original_width: fc.float({ min: 2.0, max: 10.0 }),
          cut_length: fc.float({ min: 0.5, max: 2.0 }),
          cut_width: fc.float({ min: 0.5, max: 2.0 })
        }),
        async (data) => {
          // Ensure cut dimensions don't exceed original dimensions
          const cut_length = Math.min(data.cut_length, data.original_length);
          const cut_width = Math.min(data.cut_width, data.original_width);

          const operation = await mockRMCuttingOperation.create({
            production_order_id: testProductionOrder.id,
            rm_piece_id: testRMPiece.id,
            bom_item_id: testBOMItem.id,
            cut_length,
            cut_width,
            unit: 'm'
          });

          // Property 8.8: Cut area never exceeds reasonable bounds
          const cutArea = operation.getCutArea();
          const originalArea = data.original_length * data.original_width;
          
          expect(cutArea).toBeGreaterThan(0);
          expect(cutArea).toBeLessThanOrEqual(originalArea);

          // Property 8.9: Dimensions are within valid ranges
          expect(operation.cut_length).toBeGreaterThan(0);
          expect(operation.cut_width).toBeGreaterThan(0);
          expect(operation.cut_length).toBeLessThanOrEqual(data.original_length);
          expect(operation.cut_width).toBeLessThanOrEqual(data.original_width);

          // Clean up
          await operation.destroy();
        }
      ), { numRuns: 50 });
    });
  });

  describe('Unit Tests for specific scenarios', () => {
    test('should handle complex cutting operation with multiple waste types', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 2.0,
        cut_width: 1.5,
        unit: 'm',
        waste_pieces: [
          { length: 1.5, width: 1.0, status: 'WASTE' },
          { length: 1.0, width: 0.8, status: 'WASTE' },
          { length: 0.5, width: 0.3, status: 'SCRAP' }
        ],
        scrap_dimensions: { length: 0.3, width: 0.2 },
        remaining_piece_id: 2,
        notes: 'Complex cutting with multiple waste pieces'
      });

      // Verify all aspects of the operation
      expect(operation.getCutArea()).toBe(3.0);
      expect(operation.getTotalWasteArea()).toBe(2.45); // 1.5 + 0.8 + 0.15
      expect(operation.getScrapArea()).toBe(0.06);
      expect(operation.getWastePiecesByStatus('WASTE')).toHaveLength(2);
      expect(operation.getWastePiecesByStatus('SCRAP')).toHaveLength(1);
      expect(operation.hasRemainingPiece()).toBe(true);
    });

    test('should handle cutting operation with no waste', async () => {
      const operation = await mockRMCuttingOperation.create({
        production_order_id: testProductionOrder.id,
        rm_piece_id: testRMPiece.id,
        bom_item_id: testBOMItem.id,
        cut_length: 6.0,
        cut_width: 4.0,
        unit: 'm',
        notes: 'Full piece consumption'
      });

      // Verify no waste scenario
      expect(operation.getCutArea()).toBe(24.0);
      expect(operation.getTotalWasteArea()).toBe(0);
      expect(operation.getScrapArea()).toBe(0);
      expect(operation.getWastePiecesByStatus('WASTE')).toHaveLength(0);
      expect(operation.hasRemainingPiece()).toBe(false);
    });
  });
});

/**
 * Test Tags for Property-Based Testing:
 * Feature: dimension-based-inventory, Property 8: Cutting Operation Audit Trail
 */