/**
 * Bug Condition Exploration Test for Bug 1: Total Empty MT Not Updating After Stock-Out Approval
 * 
 * **Validates: Requirements 1.1, 1.2, 1.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: isBugCondition1(input) where input.type = "outbound" AND input.approvalStatus = "approved"
 * 
 * Expected Behavior Properties:
 * - totalAvailableAfter = totalAvailableBefore - quantityMt
 * - totalEmptyAfter = totalEmptyBefore + quantityMt
 * - totalEmptyAfter = 26000 - totalAvailableAfter
 */

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 { StockTransactionsService } from './stock-transactions.service';
import { NotificationsService } from './notifications.service';
import { StockTransaction } from './schemas/stock-transaction.schema';
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 { StockMovementSheet } from './schemas/stock-movement-sheet.schema';
import { User } from './schemas/user.schema';

describe('Bug 1: Total Empty MT Not Updating After Stock-Out Approval', () => {
  let service: StockTransactionsService;
  let txModel: Model<any>;
  let warehouseModel: Model<any>;
  let itemModel: Model<any>;
  let batchModel: Model<any>;
  let balanceModel: Model<any>;
  let sheetModel: Model<any>;
  let userModel: Model<any>;
  let notificationsService: NotificationsService;

  // Mock data
  const mockWarehouseId = new Types.ObjectId();
  const mockItemId = new Types.ObjectId();
  const mockBatchId = new Types.ObjectId();
  const mockTransactionId = new Types.ObjectId();

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

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

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

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

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

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

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

    const mockNotificationsService = {
      notifyManagers: vi.fn(),
      notifyUser: vi.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        StockTransactionsService,
        {
          provide: getModelToken(StockTransaction.name),
          useValue: mockTxModel,
        },
        {
          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(StockMovementSheet.name),
          useValue: mockSheetModel,
        },
        {
          provide: getModelToken(User.name),
          useValue: mockUserModel,
        },
        {
          provide: NotificationsService,
          useValue: mockNotificationsService,
        },
      ],
    }).compile();

    service = module.get<StockTransactionsService>(StockTransactionsService);
    txModel = module.get<Model<any>>(getModelToken(StockTransaction.name));
    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));
    sheetModel = module.get<Model<any>>(getModelToken(StockMovementSheet.name));
    userModel = module.get<Model<any>>(getModelToken(User.name));
    notificationsService = module.get<NotificationsService>(NotificationsService);
  });

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

  /**
   * Property 1: Bug Condition - Total Empty MT Not Updating After Stock-Out Approval
   * 
   * **Validates: Requirements 1.1, 1.2, 1.3**
   * 
   * This test verifies the concrete failing case from the bugfix spec:
   * - Stock-out transaction of 18,000 MT is approved
   * - Before: Total Available MT = 21,000, Total Empty MT = 5,000
   * - Expected After: Total Available MT = 3,000, Total Empty MT = 23,000
   * - Actual After (buggy): Total Available MT = 21,000 (unchanged), Total Empty MT = 5,000 (unchanged)
   * 
   * Bug Condition: type = "outbound" AND approvalStatus = "approved"
   * 
   * Expected Behavior:
   * - totalAvailableAfter = totalAvailableBefore - quantityMt (21,000 - 18,000 = 3,000)
   * - totalEmptyAfter = totalEmptyBefore + quantityMt (5,000 + 18,000 = 23,000)
   * - totalEmptyAfter = 26,000 - totalAvailableAfter (26,000 - 3,000 = 23,000)
   */
  it('should increase Total Empty MT from 5,000 to 23,000 after approving 18,000 MT stock-out', async () => {
    // Setup: Initial state with Total Available MT = 21,000
    const initialBalance = {
      _id: new Types.ObjectId(),
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      currentQuantityMt: 21000, // Initial available MT
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
    };

    // Mock balance query to return initial balance
    (balanceModel.findOne as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(initialBalance),
    });

    // Mock balance.find() for calculating total available MT
    (balanceModel.find as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue([initialBalance]),
      }),
    });

    // Mock batch
    const mockBatch = {
      _id: mockBatchId,
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchNumber: 'BATCH-001',
      expiryDate: '2025-12-31',
      stockedOut: false,
      save: vi.fn().mockResolvedValue(undefined),
    };

    (batchModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockBatch),
    });

    // Mock approved sheets (empty for this test)
    (sheetModel.find as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue([]),
    });

    // Mock stock transaction
    const mockTransaction = {
      _id: mockTransactionId,
      type: 'outbound',
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      quantityMt: 18000, // Stock-out quantity
      transactionDate: '2024-01-15',
      approvalStatus: 'pending_approval',
      submittedBy: 'user1',
      createdAt: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
      toObject: vi.fn().mockReturnThis(),
    };

    (txModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockTransaction),
    });

    // Mock user query for notifications
    (userModel.find as vi.Mock).mockReturnValue({
      lean: vi.fn().mockReturnValue({
        exec: vi.fn().mockResolvedValue([]),
      }),
    });

    // Calculate initial Total Empty MT
    const totalAvailableBefore = 21000;
    const totalEmptyBefore = 26000 - totalAvailableBefore; // 5,000 MT
    expect(totalEmptyBefore).toBe(5000);

    // Act: Approve the stock-out transaction
    await service.approve(mockTransactionId.toString(), 'manager1');

    // Assert: Verify the balance was updated correctly
    expect(initialBalance.save).toHaveBeenCalled();

    // Calculate expected values after stock-out
    const quantityMt = 18000;
    const expectedTotalAvailableAfter = totalAvailableBefore - quantityMt; // 21,000 - 18,000 = 3,000
    const expectedTotalEmptyAfter = totalEmptyBefore + quantityMt; // 5,000 + 18,000 = 23,000

    // Verify the balance was reduced by the correct amount
    expect(initialBalance.currentQuantityMt).toBe(expectedTotalAvailableAfter);

    // Verify Total Empty MT calculation
    const actualTotalEmptyAfter = 26000 - initialBalance.currentQuantityMt;
    expect(actualTotalEmptyAfter).toBe(expectedTotalEmptyAfter);
    expect(actualTotalEmptyAfter).toBe(23000);

    // Verify the three expected behavior properties:
    // 1. totalAvailableAfter = totalAvailableBefore - quantityMt
    expect(initialBalance.currentQuantityMt).toBe(totalAvailableBefore - quantityMt);

    // 2. totalEmptyAfter = totalEmptyBefore + quantityMt
    expect(actualTotalEmptyAfter).toBe(totalEmptyBefore + quantityMt);

    // 3. totalEmptyAfter = 26000 - totalAvailableAfter
    expect(actualTotalEmptyAfter).toBe(26000 - initialBalance.currentQuantityMt);

    // Verify transaction was marked as approved
    expect(mockTransaction.approvalStatus).toBe('approved');
    expect(mockTransaction.approvedBy).toBe('manager1');
    expect(mockTransaction.save).toHaveBeenCalled();

    // Verify batch was marked as stocked out (since balance reaches 3,000, not 0, it shouldn't be marked)
    // Only mark as stocked out if balance reaches 0
    if (initialBalance.currentQuantityMt === 0) {
      expect(mockBatch.stockedOut).toBe(true);
      expect(mockBatch.save).toHaveBeenCalled();
    }
  });

  /**
   * Property 1 (Edge Case): Stock-out that reduces balance to exactly 0
   * 
   * **Validates: Requirements 1.1, 1.2, 1.3**
   * 
   * This test verifies that when a stock-out reduces the balance to exactly 0:
   * - The balance is updated correctly
   * - The batch is marked as stockedOut: true
   * - Total Empty MT is calculated correctly
   */
  it('should update balance to 0 and mark batch as stocked out when stock-out equals available MT', async () => {
    // Setup: Initial state with Total Available MT = 1,000
    const initialBalance = {
      _id: new Types.ObjectId(),
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      currentQuantityMt: 1000, // Initial available MT
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
    };

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

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

    const mockBatch = {
      _id: mockBatchId,
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchNumber: 'BATCH-002',
      expiryDate: '2025-12-31',
      stockedOut: false,
      save: vi.fn().mockResolvedValue(undefined),
    };

    (batchModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockBatch),
    });

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

    const mockTransaction = {
      _id: mockTransactionId,
      type: 'outbound',
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      quantityMt: 1000, // Stock-out entire balance
      transactionDate: '2024-01-15',
      approvalStatus: 'pending_approval',
      submittedBy: 'user1',
      createdAt: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
      toObject: vi.fn().mockReturnThis(),
    };

    (txModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockTransaction),
    });

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

    // Calculate initial Total Empty MT
    const totalAvailableBefore = 1000;
    const totalEmptyBefore = 26000 - totalAvailableBefore; // 25,000 MT

    // Act: Approve the stock-out transaction
    await service.approve(mockTransactionId.toString(), 'manager1');

    // Assert: Verify balance is now 0
    expect(initialBalance.currentQuantityMt).toBe(0);

    // Verify Total Empty MT is now 26,000 (all empty)
    const actualTotalEmptyAfter = 26000 - initialBalance.currentQuantityMt;
    expect(actualTotalEmptyAfter).toBe(26000);

    // Verify the expected behavior properties
    expect(initialBalance.currentQuantityMt).toBe(totalAvailableBefore - 1000);
    expect(actualTotalEmptyAfter).toBe(totalEmptyBefore + 1000);
    expect(actualTotalEmptyAfter).toBe(26000 - initialBalance.currentQuantityMt);

    // Verify batch was marked as stocked out
    expect(mockBatch.stockedOut).toBe(true);
    expect(mockBatch.save).toHaveBeenCalled();
  });

  /**
   * Property 1 (Edge Case): Multiple stock-outs in sequence
   * 
   * **Validates: Requirements 1.1, 1.2, 1.3**
   * 
   * This test verifies that Total Empty MT increases cumulatively
   * when multiple stock-out transactions are approved in sequence.
   */
  it('should increase Total Empty MT cumulatively for multiple stock-outs', async () => {
    // Setup: Initial state with Total Available MT = 20,000
    const initialBalance = {
      _id: new Types.ObjectId(),
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      currentQuantityMt: 20000,
      thresholdTargetMt: 26000,
      lastUpdated: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
    };

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

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

    const mockBatch = {
      _id: mockBatchId,
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchNumber: 'BATCH-003',
      expiryDate: '2025-12-31',
      stockedOut: false,
      save: vi.fn().mockResolvedValue(undefined),
    };

    (batchModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockBatch),
    });

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

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

    // First stock-out: 5,000 MT
    const mockTransaction1 = {
      _id: new Types.ObjectId(),
      type: 'outbound',
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      quantityMt: 5000,
      transactionDate: '2024-01-15',
      approvalStatus: 'pending_approval',
      submittedBy: 'user1',
      createdAt: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
      toObject: vi.fn().mockReturnThis(),
    };

    (txModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockTransaction1),
    });

    // Initial: Available = 20,000, Empty = 6,000
    const totalAvailableInitial = 20000;
    const totalEmptyInitial = 26000 - totalAvailableInitial; // 6,000

    // Approve first stock-out
    await service.approve(mockTransaction1._id.toString(), 'manager1');

    // After first: Available = 15,000, Empty = 11,000
    expect(initialBalance.currentQuantityMt).toBe(15000);
    let actualTotalEmpty = 26000 - initialBalance.currentQuantityMt;
    expect(actualTotalEmpty).toBe(11000);

    // Second stock-out: 10,000 MT
    const mockTransaction2 = {
      _id: new Types.ObjectId(),
      type: 'outbound',
      warehouseId: mockWarehouseId,
      itemId: mockItemId,
      batchId: mockBatchId,
      quantityMt: 10000,
      transactionDate: '2024-01-16',
      approvalStatus: 'pending_approval',
      submittedBy: 'user1',
      createdAt: new Date().toISOString(),
      save: vi.fn().mockResolvedValue(undefined),
      toObject: vi.fn().mockReturnThis(),
    };

    (txModel.findById as vi.Mock).mockReturnValue({
      exec: vi.fn().mockResolvedValue(mockTransaction2),
    });

    // Approve second stock-out
    await service.approve(mockTransaction2._id.toString(), 'manager1');

    // After second: Available = 5,000, Empty = 21,000
    expect(initialBalance.currentQuantityMt).toBe(5000);
    actualTotalEmpty = 26000 - initialBalance.currentQuantityMt;
    expect(actualTotalEmpty).toBe(21000);

    // Verify cumulative increase: Empty increased by 15,000 total (5,000 + 10,000)
    expect(actualTotalEmpty).toBe(totalEmptyInitial + 5000 + 10000);
  });
});
