/**
 * Bug Condition Exploration Test for Bug 2: Available MT Showing Total Across All Warehouses
 * 
 * **Validates: Requirements 2.1, 2.2, 2.3**
 * 
 * This test encodes the EXPECTED behavior and will FAIL on unfixed code.
 * When it fails, it proves the bug exists.
 * When it passes (after the fix), it confirms the bug is resolved.
 * 
 * CRITICAL: This test MUST FAIL on unfixed code - failure confirms the bug exists.
 * DO NOT attempt to fix the test or the code when it fails.
 * 
 * Bug Condition: isBugCondition2(input) where input.warehouseId IS NOT NULL AND input.itemId IS NOT NULL
 * 
 * Expected Behavior Properties:
 * - batch.availableMt = warehouseSpecificMt (calculated from stock_movement_sheets)
 * - batch.availableMt <= totalMtForBatchAcrossAllWarehouses
 */

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';

describe('Bug 2: Available MT Showing Total Across All Warehouses', () => {
  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>;

  // Mock IDs
  const warehouseAId = new Types.ObjectId();
  const warehouseBId = new Types.ObjectId();
  const warehouseCId = new Types.ObjectId();
  const itemId = new Types.ObjectId();
  const batchId = new Types.ObjectId();

  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 1: Bug Condition - Available MT Showing Total Across All Warehouses
   * 
   * **Validates: Requirements 2.1, 2.2, 2.3**
   * 
   * This test verifies the concrete failing case from the bugfix spec:
   * - Item "Rice 25kg" exists in 3 warehouses with batch "BATCH-001"
   * - Warehouse A: 1,900 MT (76 bags * 25kg = 1,900 kg = 1.9 MT... wait, let me recalculate)
   * - Actually: 1,900 MT = 1,900,000 kg / 25 kg per bag = 76,000 bags
   * - Warehouse B: 8,100 MT = 324,000 bags
   * - Warehouse C: 8,000 MT = 320,000 bags
   * - Total: 18,000 MT = 720,000 bags
   * 
   * When getBatches is called for Warehouse A:
   * - Expected: Returns 1,900 MT (warehouse-specific)
   * - Actual (buggy): Returns 18,000 MT (total across all warehouses)
   * 
   * Bug Condition: warehouseId IS NOT NULL AND itemId IS NOT NULL
   * 
   * Expected Behavior:
   * - batch.availableMt = warehouseSpecificMt (1,900 MT for Warehouse A)
   * - batch.availableMt <= totalMtForBatchAcrossAllWarehouses (1,900 <= 18,000)
   */
  it('should return warehouse-specific MT (1,900) not total across all warehouses (18,000)', async () => {
    // Setup: Create an item with bagWeightKg = 25
    const mockItem = {
      _id: itemId,
      itemCode: 'RICE-25KG',
      itemName: 'Rice 25kg',
      bagWeightKg: 25,
    };

    (itemModel.findById as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue(mockItem),
      }),
    });

    // Setup: Create batch for Warehouse A
    const mockBatch = {
      _id: batchId,
      warehouseId: warehouseAId,
      itemId: itemId,
      batchNumber: 'BATCH-001',
      expiryDate: '2025-12-31',
      stockedOut: false,
    };

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

    // Setup: Create inventory balance with TOTAL across all warehouses (18,000 MT)
    // This is the bug - the balance stores the total, not warehouse-specific
    const mockBalance = {
      _id: new Types.ObjectId(),
      warehouseId: warehouseAId,
      itemId: itemId,
      batchId: batchId,
      currentQuantityMt: 18000, // BUG: This is the total across all warehouses
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
    };

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

    // Setup: Create approved stock movement sheets with warehouse-specific data
    // This is the correct source of truth for warehouse-specific quantities
    const mockSheet = {
      _id: new Types.ObjectId(),
      approvalStatus: 'approved',
      lineItems: [
        {
          lineNumber: 1,
          itemId: itemId,
          itemCode: 'RICE-25KG',
          itemName: 'Rice 25kg',
          batchNumber: 'BATCH-001',
          expiryDate: '2025-12-31',
          // Warehouse-specific bag counts
          warehouseBags: {
            [warehouseAId.toString()]: 76000,  // 76,000 bags * 25kg = 1,900,000 kg = 1,900 MT
            [warehouseBId.toString()]: 324000, // 324,000 bags * 25kg = 8,100,000 kg = 8,100 MT
            [warehouseCId.toString()]: 320000, // 320,000 bags * 25kg = 8,000,000 kg = 8,000 MT
          },
          totalBags: 720000, // Total: 720,000 bags
          totalMt: 18000,    // Total: 18,000 MT
        },
      ],
      totalBagsAll: 720000,
      totalMtAll: 18000,
      createdAt: new Date().toISOString(),
    };

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

    // Act: Call getBatches for Warehouse A
    const result = await service.getBatches(itemId.toString(), warehouseAId.toString());

    // Assert: Verify the bug condition is met
    // Bug Condition: warehouseId IS NOT NULL AND itemId IS NOT NULL
    expect(warehouseAId.toString()).toBeTruthy();
    expect(itemId.toString()).toBeTruthy();

    // Assert: Expected behavior - should return warehouse-specific MT
    expect(result).toHaveLength(1);
    const batch = result[0];

    // Expected Behavior Property 1: batch.availableMt = warehouseSpecificMt
    // Warehouse A should have 1,900 MT (76,000 bags * 25kg / 1000)
    const expectedWarehouseSpecificMt = 1900;
    expect(batch.availableMt).toBe(expectedWarehouseSpecificMt);

    // Expected Behavior Property 2: batch.availableMt <= totalMtForBatchAcrossAllWarehouses
    const totalMtAcrossAllWarehouses = 18000;
    expect(batch.availableMt).toBeLessThanOrEqual(totalMtAcrossAllWarehouses);

    // Additional assertions
    expect(batch.batchNumber).toBe('BATCH-001');
    expect(batch.expiryDate).toBe('2025-12-31');

    // CRITICAL: On unfixed code, this test will FAIL because:
    // - batch.availableMt will be 18,000 (from inventory_balance.currentQuantityMt)
    // - Expected: 1,900 (warehouse-specific from stock_movement_sheets)
    // This failure proves the bug exists!
  });

  /**
   * Property 1 (Edge Case): Item exists in only one warehouse
   * 
   * **Validates: Requirements 2.1, 2.2, 2.3**
   * 
   * This test verifies that when an item exists in only one warehouse,
   * the system returns the correct MT (which happens to equal the total by coincidence).
   * This edge case may pass on unfixed code by coincidence.
   */
  it('should return correct MT when item exists in only one warehouse', async () => {
    // Setup: Create an item with bagWeightKg = 25
    const mockItem = {
      _id: itemId,
      itemCode: 'WHEAT-50KG',
      itemName: 'Wheat 50kg',
      bagWeightKg: 50,
    };

    (itemModel.findById as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue(mockItem),
      }),
    });

    // Setup: Create batch for Warehouse A only
    const mockBatch = {
      _id: batchId,
      warehouseId: warehouseAId,
      itemId: itemId,
      batchNumber: 'BATCH-002',
      expiryDate: '2025-12-31',
      stockedOut: false,
    };

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

    // Setup: Create inventory balance with 3,000 MT (only in Warehouse A)
    const mockBalance = {
      _id: new Types.ObjectId(),
      warehouseId: warehouseAId,
      itemId: itemId,
      batchId: batchId,
      currentQuantityMt: 3000,
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
    };

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

    // Setup: Create approved stock movement sheet with warehouse-specific data
    const mockSheet = {
      _id: new Types.ObjectId(),
      approvalStatus: 'approved',
      lineItems: [
        {
          lineNumber: 1,
          itemId: itemId,
          itemCode: 'WHEAT-50KG',
          itemName: 'Wheat 50kg',
          batchNumber: 'BATCH-002',
          expiryDate: '2025-12-31',
          // Only Warehouse A has this item
          warehouseBags: {
            [warehouseAId.toString()]: 60000, // 60,000 bags * 50kg = 3,000,000 kg = 3,000 MT
          },
          totalBags: 60000,
          totalMt: 3000,
        },
      ],
      totalBagsAll: 60000,
      totalMtAll: 3000,
      createdAt: new Date().toISOString(),
    };

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

    // Act: Call getBatches for Warehouse A
    const result = await service.getBatches(itemId.toString(), warehouseAId.toString());

    // Assert: Expected behavior - should return 3,000 MT
    expect(result).toHaveLength(1);
    const batch = result[0];

    // Expected: 3,000 MT (warehouse-specific, which equals total in this case)
    expect(batch.availableMt).toBe(3000);
    expect(batch.batchNumber).toBe('BATCH-002');

    // This test may pass on unfixed code by coincidence because
    // the warehouse-specific MT equals the total MT when item is in only one warehouse
  });

  /**
   * Property 1 (Edge Case): Batch has been partially stocked out from one warehouse
   * 
   * **Validates: Requirements 2.1, 2.2, 2.3**
   * 
   * This test verifies that when a batch has been partially stocked out from one warehouse,
   * the system returns the correct remaining warehouse-specific MT.
   */
  it('should return correct remaining MT after partial stock-out from one warehouse', async () => {
    // Setup: Create an item with bagWeightKg = 25
    const mockItem = {
      _id: itemId,
      itemCode: 'RICE-25KG',
      itemName: 'Rice 25kg',
      bagWeightKg: 25,
    };

    (itemModel.findById as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue(mockItem),
      }),
    });

    // Setup: Create batch for Warehouse A
    const mockBatch = {
      _id: batchId,
      warehouseId: warehouseAId,
      itemId: itemId,
      batchNumber: 'BATCH-003',
      expiryDate: '2025-12-31',
      stockedOut: false,
    };

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

    // Setup: Create inventory balance
    // Originally: Warehouse A had 2,000 MT, stocked out 500 MT
    // Remaining: 1,500 MT in Warehouse A
    // Warehouse B still has 3,000 MT
    // Total remaining: 4,500 MT
    const mockBalance = {
      _id: new Types.ObjectId(),
      warehouseId: warehouseAId,
      itemId: itemId,
      batchId: batchId,
      currentQuantityMt: 4500, // BUG: This is the total remaining across all warehouses
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
    };

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

    // Setup: Create approved stock movement sheets
    // Note: In reality, stock-out transactions would reduce the warehouse-specific bags
    // For this test, we simulate the state after partial stock-out
    const mockSheet = {
      _id: new Types.ObjectId(),
      approvalStatus: 'approved',
      lineItems: [
        {
          lineNumber: 1,
          itemId: itemId,
          itemCode: 'RICE-25KG',
          itemName: 'Rice 25kg',
          batchNumber: 'BATCH-003',
          expiryDate: '2025-12-31',
          // After partial stock-out from Warehouse A
          warehouseBags: {
            [warehouseAId.toString()]: 60000,  // 1,500 MT (originally 2,000, stocked out 500)
            [warehouseBId.toString()]: 120000, // 3,000 MT (unchanged)
          },
          totalBags: 180000,
          totalMt: 4500,
        },
      ],
      totalBagsAll: 180000,
      totalMtAll: 4500,
      createdAt: new Date().toISOString(),
    };

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

    // Act: Call getBatches for Warehouse A
    const result = await service.getBatches(itemId.toString(), warehouseAId.toString());

    // Assert: Expected behavior - should return warehouse-specific remaining MT
    expect(result).toHaveLength(1);
    const batch = result[0];

    // Expected: 1,500 MT (warehouse-specific remaining after partial stock-out)
    const expectedWarehouseSpecificMt = 1500;
    expect(batch.availableMt).toBe(expectedWarehouseSpecificMt);

    // Verify it's less than the total across all warehouses
    const totalMtAcrossAllWarehouses = 4500;
    expect(batch.availableMt).toBeLessThanOrEqual(totalMtAcrossAllWarehouses);

    // CRITICAL: On unfixed code, this test will FAIL because:
    // - batch.availableMt will be 4,500 (from inventory_balance.currentQuantityMt)
    // - Expected: 1,500 (warehouse-specific from stock_movement_sheets)
  });

  /**
   * Property 1 (Edge Case): Batch exists in multiple warehouses, query for warehouse with 0 MT
   * 
   * **Validates: Requirements 2.1, 2.2, 2.3**
   * 
   * This test verifies that when a batch exists in multiple warehouses but
   * the queried warehouse has 0 MT (or no bags), the system returns 0 MT or empty array.
   */
  it('should return 0 MT or empty array when warehouse has no stock for the batch', async () => {
    // Setup: Create an item with bagWeightKg = 25
    const mockItem = {
      _id: itemId,
      itemCode: 'RICE-25KG',
      itemName: 'Rice 25kg',
      bagWeightKg: 25,
    };

    (itemModel.findById as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue(mockItem),
      }),
    });

    // Setup: Create batch for Warehouse A (but it has no stock)
    const mockBatch = {
      _id: batchId,
      warehouseId: warehouseAId,
      itemId: itemId,
      batchNumber: 'BATCH-004',
      expiryDate: '2025-12-31',
      stockedOut: false,
    };

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

    // Setup: Create inventory balance with total across warehouses B and C only
    const mockBalance = {
      _id: new Types.ObjectId(),
      warehouseId: warehouseAId,
      itemId: itemId,
      batchId: batchId,
      currentQuantityMt: 10000, // BUG: This is the total in Warehouses B and C, not A
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
    };

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

    // Setup: Create approved stock movement sheet
    // Warehouse A has 0 bags, Warehouses B and C have stock
    const mockSheet = {
      _id: new Types.ObjectId(),
      approvalStatus: 'approved',
      lineItems: [
        {
          lineNumber: 1,
          itemId: itemId,
          itemCode: 'RICE-25KG',
          itemName: 'Rice 25kg',
          batchNumber: 'BATCH-004',
          expiryDate: '2025-12-31',
          // Warehouse A has 0 bags
          warehouseBags: {
            [warehouseAId.toString()]: 0,      // 0 MT
            [warehouseBId.toString()]: 200000, // 5,000 MT
            [warehouseCId.toString()]: 200000, // 5,000 MT
          },
          totalBags: 400000,
          totalMt: 10000,
        },
      ],
      totalBagsAll: 400000,
      totalMtAll: 10000,
      createdAt: new Date().toISOString(),
    };

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

    // Act: Call getBatches for Warehouse A
    const result = await service.getBatches(itemId.toString(), warehouseAId.toString());

    // Assert: Expected behavior - should return 0 MT or empty array
    // The fix should filter out batches with 0 MT
    if (result.length > 0) {
      const batch = result[0];
      expect(batch.availableMt).toBe(0);
    } else {
      // Or the fix might return an empty array (preferred)
      expect(result).toHaveLength(0);
    }

    // CRITICAL: On unfixed code, this test will FAIL because:
    // - batch.availableMt will be 10,000 (from inventory_balance.currentQuantityMt)
    // - Expected: 0 (warehouse-specific from stock_movement_sheets)
  });
});
