/**
 * Preservation Property Tests for Bug 2: Warehouse-Specific Available MT
 * 
 * **Validates: Requirements 6.1, 6.2, 6.3, 6.4**
 * 
 * These tests verify that the fix for Bug 2 does NOT break existing functionality.
 * They capture the CURRENT behavior on UNFIXED code for non-buggy inputs.
 * 
 * IMPORTANT: These tests should PASS on UNFIXED code and continue to PASS after the fix.
 * 
 * Preservation Requirements:
 * - 6.1: getSummary() returns totals across all warehouses
 * - 6.2: getInventory() shows per-batch records
 * - 6.3: getExpiry() aggregates across warehouses
 * - 6.4: Batches marked stockedOut: true are excluded
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { InventoryService } from './inventory.service';
import { Warehouse } from './schemas/warehouse.schema';
import { Item } from './schemas/item.schema';
import { Batch } from './schemas/batch.schema';
import { InventoryBalance } from './schemas/inventory-balance.schema';
import { InventoryTransaction } from './schemas/inventory-transaction.schema';
import { StockTransaction } from './schemas/stock-transaction.schema';
import { StockMovementSheet } from './schemas/stock-movement-sheet.schema';
import * as fc from 'fast-check';

describe('Bug 2 Preservation: Global Inventory Calculations Unchanged', () => {
  let service: InventoryService;
  let warehouseModel: Model<any>;
  let itemModel: Model<any>;
  let batchModel: Model<any>;
  let balanceModel: Model<any>;
  let transactionModel: Model<any>;
  let stockTxModel: Model<any>;
  let sheetModel: Model<any>;

  beforeEach(async () => {
    // Create mock models
    const mockWarehouseModel = {
      findById: vi.fn(),
      find: vi.fn(),
    };

    const mockItemModel = {
      findById: vi.fn(),
      find: vi.fn(),
    };

    const mockBatchModel = {
      findById: vi.fn(),
      findOne: vi.fn(),
      find: vi.fn(),
    };

    const mockBalanceModel = {
      findOne: vi.fn(),
      find: vi.fn(),
    };

    const mockTransactionModel = {
      aggregate: vi.fn(),
    };

    const mockStockTxModel = {
      countDocuments: vi.fn(),
    };

    const mockSheetModel = {
      find: vi.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        InventoryService,
        {
          provide: getModelToken(Warehouse.name),
          useValue: mockWarehouseModel,
        },
        {
          provide: getModelToken(Item.name),
          useValue: mockItemModel,
        },
        {
          provide: getModelToken(Batch.name),
          useValue: mockBatchModel,
        },
        {
          provide: getModelToken(InventoryBalance.name),
          useValue: mockBalanceModel,
        },
        {
          provide: getModelToken(InventoryTransaction.name),
          useValue: mockTransactionModel,
        },
        {
          provide: getModelToken(StockTransaction.name),
          useValue: mockStockTxModel,
        },
        {
          provide: getModelToken(StockMovementSheet.name),
          useValue: mockSheetModel,
        },
      ],
    }).compile();

    service = module.get<InventoryService>(InventoryService);
    warehouseModel = module.get<Model<any>>(getModelToken(Warehouse.name));
    itemModel = module.get<Model<any>>(getModelToken(Item.name));
    batchModel = module.get<Model<any>>(getModelToken(Batch.name));
    balanceModel = module.get<Model<any>>(getModelToken(InventoryBalance.name));
    transactionModel = module.get<Model<any>>(getModelToken(InventoryTransaction.name));
    stockTxModel = module.get<Model<any>>(getModelToken(StockTransaction.name));
    sheetModel = module.get<Model<any>>(getModelToken(StockMovementSheet.name));
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  /**
   * Property 2.1: Preservation - getSummary() returns totals across all warehouses
   * 
   * **Validates: Requirement 6.1**
   * 
   * This property verifies that getSummary() continues to aggregate inventory
   * across ALL warehouses, not just a specific warehouse.
   * 
   * For all inventory summary queries, totals aggregate across all warehouses.
   */
  describe('Property 2.1: getSummary() aggregates across all warehouses', () => {
    it('should calculate Total Available MT as sum of all inventory_balance records', async () => {
      // Setup: Create multiple inventory balance records across different warehouses
      const warehouseAId = new Types.ObjectId();
      const warehouseBId = new Types.ObjectId();
      const warehouseCId = new Types.ObjectId();
      const itemId = new Types.ObjectId();
      const batch1Id = new Types.ObjectId();
      const batch2Id = new Types.ObjectId();
      const batch3Id = new Types.ObjectId();

      const mockBalances = [
        {
          _id: new Types.ObjectId(),
          warehouseId: warehouseAId,
          itemId: itemId,
          batchId: batch1Id,
          currentQuantityMt: 5000,
          thresholdTargetMt: 26000,
        },
        {
          _id: new Types.ObjectId(),
          warehouseId: warehouseBId,
          itemId: itemId,
          batchId: batch2Id,
          currentQuantityMt: 8000,
          thresholdTargetMt: 26000,
        },
        {
          _id: new Types.ObjectId(),
          warehouseId: warehouseCId,
          itemId: itemId,
          batchId: batch3Id,
          currentQuantityMt: 3000,
          thresholdTargetMt: 26000,
        },
      ];

      (balanceModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockBalances),
        }),
      });

      // Setup: Mock approved sheets for expiry calculation
      (sheetModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([]),
        }),
      });

      // Setup: Mock pending reviews count
      (stockTxModel.countDocuments as vi.Mock).mockReturnValue({
        exec: vi.fn().mockResolvedValue(0),
      });

      // Act: Call getSummary()
      const result = await service.getSummary();

      // Assert: Total Available MT should be sum of all balances
      const expectedTotalAvailableMt = 5000 + 8000 + 3000; // 16,000 MT
      expect(result.totalAvailableMt).toBe(expectedTotalAvailableMt);

      // Assert: Total Empty MT should be calculated from global total
      const expectedTotalEmptyMt = 26000 - expectedTotalAvailableMt; // 10,000 MT
      expect(result.totalEmptyMt).toBe(expectedTotalEmptyMt);

      // Assert: Threshold should remain constant
      expect(result.thresholdTargetMt).toBe(26000);
    });

    it('should calculate Total Empty MT correctly when Total Available exceeds threshold', async () => {
      // Setup: Create balances that exceed the 26,000 MT threshold
      const mockBalances = [
        {
          _id: new Types.ObjectId(),
          warehouseId: new Types.ObjectId(),
          itemId: new Types.ObjectId(),
          batchId: new Types.ObjectId(),
          currentQuantityMt: 30000, // Exceeds threshold
          thresholdTargetMt: 26000,
        },
      ];

      (balanceModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockBalances),
        }),
      });

      (sheetModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([]),
        }),
      });

      (stockTxModel.countDocuments as vi.Mock).mockReturnValue({
        exec: vi.fn().mockResolvedValue(0),
      });

      // Act: Call getSummary()
      const result = await service.getSummary();

      // Assert: Total Empty MT should be 0 (clamped by Math.max(0, ...))
      expect(result.totalAvailableMt).toBe(30000);
      expect(result.totalEmptyMt).toBe(0); // Math.max(0, 26000 - 30000) = 0
    });

    /**
     * Property-Based Test: getSummary() aggregates correctly for any set of balances
     * 
     * This test generates random inventory balances and verifies that:
     * 1. Total Available MT = sum of all currentQuantityMt values
     * 2. Total Empty MT = Math.max(0, 26000 - Total Available MT)
     */
    it('property: getSummary() correctly aggregates any set of inventory balances', async () => {
      await fc.assert(
        fc.asyncProperty(
          // Generate 1-10 random inventory balances
          fc.array(
            fc.record({
              currentQuantityMt: fc.integer({ min: 0, max: 10000 }),
            }),
            { minLength: 1, maxLength: 10 }
          ),
          async (balances) => {
            // Setup: Mock the balances
            const mockBalances = balances.map((b) => ({
              _id: new Types.ObjectId(),
              warehouseId: new Types.ObjectId(),
              itemId: new Types.ObjectId(),
              batchId: new Types.ObjectId(),
              currentQuantityMt: b.currentQuantityMt,
              thresholdTargetMt: 26000,
            }));

            (balanceModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue(mockBalances),
              }),
            });

            (sheetModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue([]),
              }),
            });

            (stockTxModel.countDocuments as vi.Mock).mockReturnValue({
              exec: vi.fn().mockResolvedValue(0),
            });

            // Act: Call getSummary()
            const result = await service.getSummary();

            // Assert: Verify aggregation properties
            const expectedTotalAvailableMt = balances.reduce(
              (sum, b) => sum + b.currentQuantityMt,
              0
            );
            const expectedTotalEmptyMt = Math.max(0, 26000 - expectedTotalAvailableMt);

            expect(result.totalAvailableMt).toBe(
              Number(expectedTotalAvailableMt.toFixed(2))
            );
            expect(result.totalEmptyMt).toBe(Number(expectedTotalEmptyMt.toFixed(2)));
            expect(result.thresholdTargetMt).toBe(26000);
          }
        ),
        { numRuns: 50 } // Run 50 random test cases
      );
    });
  });

  /**
   * Property 2.2: Preservation - getInventory() shows per-batch records
   * 
   * **Validates: Requirement 6.2**
   * 
   * This property verifies that getInventory() continues to display
   * per-batch inventory records correctly.
   */
  describe('Property 2.2: getInventory() displays per-batch records', () => {
    it('should return one record per inventory_balance entry', async () => {
      // Setup: Create inventory balances
      const warehouseId = new Types.ObjectId();
      const itemId = new Types.ObjectId();
      const batch1Id = new Types.ObjectId();
      const batch2Id = new Types.ObjectId();

      const mockBalances = [
        {
          _id: new Types.ObjectId(),
          warehouseId: warehouseId,
          itemId: itemId,
          batchId: batch1Id,
          currentQuantityMt: 5000,
          thresholdTargetMt: 26000,
        },
        {
          _id: new Types.ObjectId(),
          warehouseId: warehouseId,
          itemId: itemId,
          batchId: batch2Id,
          currentQuantityMt: 3000,
          thresholdTargetMt: 26000,
        },
      ];

      (balanceModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockBalances),
        }),
      });

      // Setup: Mock warehouse, item, and batch lookups
      (warehouseModel.findById as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue({
            _id: warehouseId,
            name: 'Warehouse A',
          }),
        }),
      });

      (itemModel.findById as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue({
            _id: itemId,
            itemCode: 'RICE-25KG',
            itemName: 'Rice 25kg',
          }),
        }),
      });

      (batchModel.findById as vi.Mock).mockImplementation((id) => ({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue({
            _id: id,
            batchNumber: id.equals(batch1Id) ? 'BATCH-001' : 'BATCH-002',
            expiryDate: '2025-12-31',
          }),
        }),
      }));

      // Setup: Mock transaction aggregation
      (transactionModel.aggregate as vi.Mock).mockResolvedValue([]);

      // Act: Call getInventory()
      const result = await service.getInventory();

      // Assert: Should return one record per balance
      expect(result).toHaveLength(2);

      // Assert: Each record should have the correct structure
      expect(result[0]).toHaveProperty('warehouse');
      expect(result[0]).toHaveProperty('itemCode');
      expect(result[0]).toHaveProperty('itemName');
      expect(result[0]).toHaveProperty('batchNumber');
      expect(result[0]).toHaveProperty('expiryDate');
      expect(result[0]).toHaveProperty('inboundMt');
      expect(result[0]).toHaveProperty('outboundMt');
      expect(result[0]).toHaveProperty('availableMt');
      expect(result[0]).toHaveProperty('thresholdTargetMt');
      expect(result[0]).toHaveProperty('thresholdProgressPct');

      // Assert: Available MT should match balance currentQuantityMt
      expect(result[0].availableMt).toBe(5000);
      expect(result[1].availableMt).toBe(3000);
    });

    /**
     * Property-Based Test: getInventory() returns correct number of records
     * 
     * For any set of inventory balances, getInventory() should return
     * exactly one record per balance entry.
     */
    it('property: getInventory() returns one record per balance', async () => {
      await fc.assert(
        fc.asyncProperty(
          // Generate 1-5 random inventory balances
          fc.array(
            fc.record({
              currentQuantityMt: fc.integer({ min: 0, max: 10000 }),
              thresholdTargetMt: fc.constant(26000),
            }),
            { minLength: 1, maxLength: 5 }
          ),
          async (balances) => {
            // Setup: Mock the balances
            const mockBalances = balances.map((b) => ({
              _id: new Types.ObjectId(),
              warehouseId: new Types.ObjectId(),
              itemId: new Types.ObjectId(),
              batchId: new Types.ObjectId(),
              currentQuantityMt: b.currentQuantityMt,
              thresholdTargetMt: b.thresholdTargetMt,
            }));

            (balanceModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue(mockBalances),
              }),
            });

            // Setup: Mock lookups
            (warehouseModel.findById as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue({ name: 'Test Warehouse' }),
              }),
            });

            (itemModel.findById as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue({
                  itemCode: 'TEST',
                  itemName: 'Test Item',
                }),
              }),
            });

            (batchModel.findById as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue({
                  batchNumber: 'BATCH-TEST',
                  expiryDate: '2025-12-31',
                }),
              }),
            });

            (transactionModel.aggregate as vi.Mock).mockResolvedValue([]);

            // Act: Call getInventory()
            const result = await service.getInventory();

            // Assert: Should return exactly one record per balance
            expect(result).toHaveLength(balances.length);

            // Assert: Each record should have availableMt matching the balance
            result.forEach((record, index) => {
              expect(record.availableMt).toBe(balances[index].currentQuantityMt);
            });
          }
        ),
        { numRuns: 20 }
      );
    });
  });

  /**
   * Property 2.3: Preservation - getExpiry() aggregates across warehouses
   * 
   * **Validates: Requirement 6.3**
   * 
   * This property verifies that getExpiry() continues to aggregate
   * batch quantities across ALL warehouses for expiry tracking.
   */
  describe('Property 2.3: getExpiry() aggregates across warehouses', () => {
    it('should aggregate batch quantities across multiple warehouses', async () => {
      // Setup: Create approved sheets with same batch in multiple warehouses
      const warehouseAId = new Types.ObjectId();
      const warehouseBId = new Types.ObjectId();
      const itemId = new Types.ObjectId();

      const mockSheets = [
        {
          _id: new Types.ObjectId(),
          approvalStatus: 'approved',
          lineItems: [
            {
              itemId: itemId,
              itemCode: 'RICE-25KG',
              itemName: 'Rice 25kg',
              batchNumber: 'BATCH-001',
              expiryDate: '2025-06-30', // Expires within 6 months
              warehouseBags: {
                [warehouseAId.toString()]: 40000, // 1,000 MT
                [warehouseBId.toString()]: 80000, // 2,000 MT
              },
              totalBags: 120000,
              totalMt: 3000, // Total across both warehouses
            },
          ],
        },
      ];

      (sheetModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockSheets),
        }),
      });

      // Setup: Mock warehouses
      (warehouseModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([
            { _id: warehouseAId, name: 'Warehouse A' },
            { _id: warehouseBId, name: 'Warehouse B' },
          ]),
        }),
      });

      // Setup: Mock no stocked-out batches
      (batchModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([]),
        }),
      });

      // Act: Call getExpiry()
      const result = await service.getExpiry(6);

      // Assert: Should return one record with aggregated MT
      expect(result).toHaveLength(1);
      expect(result[0].batchNumber).toBe('BATCH-001');
      expect(result[0].availableMt).toBe(3000); // Total across both warehouses
      expect(result[0].warehouse).toContain('Warehouse A');
      expect(result[0].warehouse).toContain('Warehouse B');
    });

    it('should aggregate same batch from multiple sheets', async () => {
      // Setup: Create multiple approved sheets with same batch
      const warehouseId = new Types.ObjectId();
      const itemId = new Types.ObjectId();

      const mockSheets = [
        {
          _id: new Types.ObjectId(),
          approvalStatus: 'approved',
          lineItems: [
            {
              itemId: itemId,
              itemCode: 'RICE-25KG',
              itemName: 'Rice 25kg',
              batchNumber: 'BATCH-001',
              expiryDate: '2025-06-30',
              warehouseBags: {
                [warehouseId.toString()]: 40000,
              },
              totalBags: 40000,
              totalMt: 1000,
            },
          ],
        },
        {
          _id: new Types.ObjectId(),
          approvalStatus: 'approved',
          lineItems: [
            {
              itemId: itemId,
              itemCode: 'RICE-25KG',
              itemName: 'Rice 25kg',
              batchNumber: 'BATCH-001',
              expiryDate: '2025-06-30',
              warehouseBags: {
                [warehouseId.toString()]: 80000,
              },
              totalBags: 80000,
              totalMt: 2000,
            },
          ],
        },
      ];

      (sheetModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockSheets),
        }),
      });

      (warehouseModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([
            { _id: warehouseId, name: 'Warehouse A' },
          ]),
        }),
      });

      (batchModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([]),
        }),
      });

      // Act: Call getExpiry()
      const result = await service.getExpiry(6);

      // Assert: Should aggregate both sheets into one record
      expect(result).toHaveLength(1);
      expect(result[0].batchNumber).toBe('BATCH-001');
      expect(result[0].availableMt).toBe(3000); // 1000 + 2000
    });
  });

  /**
   * Property 2.4: Preservation - Stocked-out batches are excluded
   * 
   * **Validates: Requirement 6.4**
   * 
   * This property verifies that batches marked with stockedOut: true
   * are excluded from queries.
   */
  describe('Property 2.4: Stocked-out batches are excluded', () => {
    it('should exclude stocked-out batches from getExpiry()', async () => {
      // Setup: Create approved sheets with batches
      const itemId = new Types.ObjectId();
      const warehouseId = new Types.ObjectId();

      const mockSheets = [
        {
          _id: new Types.ObjectId(),
          approvalStatus: 'approved',
          lineItems: [
            {
              itemId: itemId,
              itemCode: 'RICE-25KG',
              itemName: 'Rice 25kg',
              batchNumber: 'BATCH-001',
              expiryDate: '2025-06-30',
              warehouseBags: {
                [warehouseId.toString()]: 40000,
              },
              totalBags: 40000,
              totalMt: 1000,
            },
            {
              itemId: itemId,
              itemCode: 'RICE-25KG',
              itemName: 'Rice 25kg',
              batchNumber: 'BATCH-002', // This one is stocked out
              expiryDate: '2025-06-30',
              warehouseBags: {
                [warehouseId.toString()]: 80000,
              },
              totalBags: 80000,
              totalMt: 2000,
            },
          ],
        },
      ];

      (sheetModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue(mockSheets),
        }),
      });

      (warehouseModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([
            { _id: warehouseId, name: 'Warehouse A' },
          ]),
        }),
      });

      // Setup: Mark BATCH-002 as stocked out
      (batchModel.find as vi.Mock).mockReturnValue({
        lean: vi.fn().mockReturnValue({
          exec: vi.fn().mockResolvedValue([
            {
              _id: new Types.ObjectId(),
              itemId: itemId,
              batchNumber: 'BATCH-002',
              stockedOut: true,
            },
          ]),
        }),
      });

      // Act: Call getExpiry()
      const result = await service.getExpiry(6);

      // Assert: Should only return BATCH-001, not BATCH-002
      expect(result).toHaveLength(1);
      expect(result[0].batchNumber).toBe('BATCH-001');
      expect(result[0].availableMt).toBe(1000);
    });

    /**
     * Property-Based Test: Stocked-out batches are always excluded
     * 
     * For any set of batches, those marked stockedOut: true should
     * never appear in getExpiry() results.
     */
    it('property: stocked-out batches never appear in expiry results', async () => {
      await fc.assert(
        fc.asyncProperty(
          // Generate 2-5 batches with unique batch numbers, some stocked out
          fc.uniqueArray(
            fc.record({
              batchNumber: fc.string({ minLength: 5, maxLength: 10 }),
              stockedOut: fc.boolean(),
              totalMt: fc.integer({ min: 100, max: 5000 }),
            }),
            { 
              minLength: 2, 
              maxLength: 5,
              selector: (item) => item.batchNumber // Ensure unique batch numbers
            }
          ),
          async (batches) => {
            const itemId = new Types.ObjectId();
            const warehouseId = new Types.ObjectId();

            // Setup: Create sheets with all batches
            const mockSheets = [
              {
                _id: new Types.ObjectId(),
                approvalStatus: 'approved',
                lineItems: batches.map((b) => ({
                  itemId: itemId,
                  itemCode: 'TEST',
                  itemName: 'Test Item',
                  batchNumber: b.batchNumber,
                  expiryDate: '2025-06-30',
                  warehouseBags: {
                    [warehouseId.toString()]: b.totalMt * 40, // Convert MT to bags
                  },
                  totalBags: b.totalMt * 40,
                  totalMt: b.totalMt,
                })),
              },
            ];

            (sheetModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue(mockSheets),
              }),
            });

            (warehouseModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue([
                  { _id: warehouseId, name: 'Test Warehouse' },
                ]),
              }),
            });

            // Setup: Mark stocked-out batches
            const stockedOutBatches = batches
              .filter((b) => b.stockedOut)
              .map((b) => ({
                _id: new Types.ObjectId(),
                itemId: itemId,
                batchNumber: b.batchNumber,
                stockedOut: true,
              }));

            (batchModel.find as vi.Mock).mockReturnValue({
              lean: vi.fn().mockReturnValue({
                exec: vi.fn().mockResolvedValue(stockedOutBatches),
              }),
            });

            // Act: Call getExpiry()
            const result = await service.getExpiry(6);

            // Assert: No stocked-out batches should appear in results
            const resultBatchNumbers = result.map((r) => r.batchNumber);
            const stockedOutBatchNumbers = batches
              .filter((b) => b.stockedOut)
              .map((b) => b.batchNumber);

            for (const stockedOutBatch of stockedOutBatchNumbers) {
              expect(resultBatchNumbers).not.toContain(stockedOutBatch);
            }

            // Assert: Only non-stocked-out batches should appear
            const expectedBatchNumbers = batches
              .filter((b) => !b.stockedOut)
              .map((b) => b.batchNumber);

            expect(result).toHaveLength(expectedBatchNumbers.length);
          }
        ),
        { numRuns: 20 }
      );
    });
  });
});
