import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Types } from 'mongoose';
import { describe, it, expect, beforeEach } from 'vitest';
import { ItemsService } from './items.service';
import { InventoryService } from './inventory.service';
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 { StockMovementSheet } from './schemas/stock-movement-sheet.schema';
import { StockTransaction } from './schemas/stock-transaction.schema';
import { Warehouse } from './schemas/warehouse.schema';

/**
 * Preservation Property Tests for Stock-Out UI Available MT Display Fix
 * 
 * Property 2: Preservation - Global Inventory Calculations and Other Methods Remain Unchanged
 * 
 * IMPORTANT: These tests run on UNFIXED code to capture baseline behavior
 * 
 * Goal: Ensure the fix does not break existing functionality:
 * - Global inventory metrics (getSummary) continue to show totals across all warehouses
 * - Expiry reference modal (getExpiry) continues to aggregate across warehouses
 * - Batches with stockedOut=true continue to be excluded
 * - Fallback to inventory_balance continues to work when no sheet data exists
 * - Single-warehouse batches continue to return correct MT
 */

describe('ItemsService & InventoryService - Preservation Tests', () => {
  let itemsService: ItemsService;
  let inventoryService: InventoryService;
  let itemModel: any;
  let batchModel: any;
  let balanceModel: any;
  let sheetModel: any;
  let warehouseModel: any;
  let transactionModel: any;
  let stockTxModel: any;

  // Test data IDs
  const itemId = new Types.ObjectId();
  const warehouseId = new Types.ObjectId();
  const batchId = new Types.ObjectId();

  beforeEach(async () => {
    // Mock models for ItemsService
    itemModel = {
      findById: () => ({
        lean: () => ({
          exec: async () => ({
            _id: itemId,
            itemCode: 'TEST-ITEM',
            itemName: 'Test Item',
            bagWeightKg: 25,
          }),
        }),
      }),
      find: () => ({
        lean: () => ({
          exec: async () => [],
        }),
      }),
    };

    batchModel = {
      find: (query: any) => ({
        lean: () => ({
          exec: async () => {
            // Exclude stocked-out batches
            if (query.stockedOut && query.stockedOut.$ne === true) {
              return [
                {
                  _id: batchId,
                  itemId: itemId,
                  warehouseId: warehouseId,
                  batchNumber: 'BATCH-TEST',
                  expiryDate: '2025-12-31',
                  stockedOut: false,
                },
              ];
            }
            return [];
          },
        }),
      }),
    };

    balanceModel = {
      find: () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: new Types.ObjectId(),
              warehouseId: warehouseId,
              itemId: itemId,
              batchId: batchId,
              currentQuantityMt: 100,
              thresholdTargetMt: 26000,
            },
          ],
        }),
      }),
      findOne: () => ({
        lean: () => ({
          exec: async () => ({
            _id: new Types.ObjectId(),
            batchId: batchId,
            currentQuantityMt: 50,
          }),
        }),
      }),
    };

    sheetModel = {
      find: () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: new Types.ObjectId(),
              approvalStatus: 'approved',
              lineItems: [
                {
                  itemId: itemId,
                  itemCode: 'TEST-ITEM',
                  itemName: 'Test Item',
                  batchNumber: 'BATCH-TEST',
                  expiryDate: '2025-12-31',
                  warehouseBags: {
                    [warehouseId.toString()]: 1000,
                  },
                  totalBags: 1000,
                  totalMt: 25,
                  unit: 'BAG/1x25kg',
                },
              ],
            },
          ],
        }),
      }),
    };

    warehouseModel = {
      findById: () => ({
        lean: () => ({
          exec: async () => ({
            _id: warehouseId,
            name: 'Test Warehouse',
            code: 'TEST-WH',
            active: true,
          }),
        }),
      }),
      find: () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: warehouseId,
              name: 'Test Warehouse',
              code: 'TEST-WH',
              active: true,
            },
          ],
        }),
      }),
    };

    transactionModel = {
      aggregate: () => Promise.resolve([{ total: 0 }]),
    };

    stockTxModel = {
      countDocuments: () => ({
        exec: async () => 5,
      }),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ItemsService,
        InventoryService,
        { provide: getModelToken(Item.name), useValue: itemModel },
        { provide: getModelToken(Batch.name), useValue: batchModel },
        { provide: getModelToken(InventoryBalance.name), useValue: balanceModel },
        { provide: getModelToken(StockMovementSheet.name), useValue: sheetModel },
        { provide: getModelToken(Warehouse.name), useValue: warehouseModel },
        { provide: getModelToken(InventoryTransaction.name), useValue: transactionModel },
        { provide: getModelToken(StockTransaction.name), useValue: stockTxModel },
      ],
    }).compile();

    itemsService = module.get<ItemsService>(ItemsService);
    inventoryService = module.get<InventoryService>(InventoryService);
  });

  describe('Preservation: Global Inventory Metrics', () => {
    it('should continue to show totals across all warehouses in getSummary()', async () => {
      // Act: Get inventory summary
      const summary = await inventoryService.getSummary();

      // Assert: Should return global totals
      expect(summary).toBeDefined();
      expect(summary.totalAvailableMt).toBe(100); // Sum of all balances
      expect(summary.totalEmptyMt).toBe(25900); // 26000 - 100
      expect(summary.thresholdTargetMt).toBe(26000);
      expect(summary.pendingReviews).toBe(5);

      console.log('\n=== Preservation Test: Global Inventory Summary ===');
      console.log('Total Available MT:', summary.totalAvailableMt);
      console.log('Total Empty MT:', summary.totalEmptyMt);
      console.log('Threshold Target MT:', summary.thresholdTargetMt);
      console.log('This should remain unchanged after the fix.');
      console.log('===================================================\n');
    });
  });

  describe('Preservation: Expiry Reference Modal', () => {
    it('should continue to aggregate batch quantities across warehouses in getExpiry()', async () => {
      // Act: Get expiry data
      const expiryRows = await inventoryService.getExpiry(6);

      // Assert: Should aggregate across warehouses
      expect(expiryRows).toBeDefined();
      expect(Array.isArray(expiryRows)).toBe(true);

      console.log('\n=== Preservation Test: Expiry Reference ===');
      console.log('Expiry rows returned:', expiryRows.length);
      console.log('This should continue to show aggregated quantities.');
      console.log('===========================================\n');
    });
  });

  describe('Preservation: Batch Exclusion Logic', () => {
    it('should continue to exclude batches with stockedOut=true', async () => {
      // Update batch model to return stocked-out batch
      batchModel.find = (query: any) => ({
        lean: () => ({
          exec: async () => {
            // If query excludes stocked-out batches, return empty
            if (query.stockedOut && query.stockedOut.$ne === true) {
              return [];
            }
            // Otherwise return stocked-out batch
            return [
              {
                _id: batchId,
                itemId: itemId,
                warehouseId: warehouseId,
                batchNumber: 'BATCH-STOCKED-OUT',
                expiryDate: '2025-12-31',
                stockedOut: true,
              },
            ];
          },
        }),
      });

      // Act: Call findBatches (should exclude stocked-out batches)
      const result = await itemsService.findBatches(
        itemId.toString(),
        warehouseId.toString()
      );

      // Assert: Should return empty array (stocked-out batch excluded)
      expect(result).toHaveLength(0);

      console.log('\n=== Preservation Test: Batch Exclusion ===');
      console.log('Stocked-out batches excluded:', result.length === 0);
      console.log('This behavior should be preserved.');
      console.log('==========================================\n');
    });
  });

  describe('Preservation: Fallback Logic', () => {
    it('should continue to fall back to inventory_balance when no sheet data exists', async () => {
      // Mock: No approved sheets
      sheetModel.find = () => ({
        lean: () => ({
          exec: async () => [],
        }),
      });

      // Mock: Balance exists with 50 MT
      balanceModel.findOne = () => ({
        lean: () => ({
          exec: async () => ({
            _id: new Types.ObjectId(),
            batchId: batchId,
            currentQuantityMt: 50,
          }),
        }),
      });

      // Act: Call findBatches (should fall back to balance)
      const result = await itemsService.findBatches(
        itemId.toString(),
        warehouseId.toString()
      );

      // Assert: Should return batch with MT from balance
      expect(result).toHaveLength(1);
      expect(result[0].availableMt).toBe(50);

      console.log('\n=== Preservation Test: Fallback Logic ===');
      console.log('Fallback to inventory_balance works:', result[0].availableMt === 50);
      console.log('This fallback should be preserved.');
      console.log('=========================================\n');
    });
  });

  describe('Preservation: Single-Warehouse Batches', () => {
    it('should continue to return correct MT for single-warehouse batches', async () => {
      // Mock: Single-warehouse batch
      const singleWarehouseId = new Types.ObjectId();
      const singleBatchId = new Types.ObjectId();

      batchModel.find = () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: singleBatchId,
              itemId: itemId,
              warehouseId: singleWarehouseId,
              batchNumber: 'BATCH-SINGLE',
              expiryDate: '2025-12-31',
              stockedOut: false,
            },
          ],
        }),
      });

      sheetModel.find = () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: new Types.ObjectId(),
              approvalStatus: 'approved',
              lineItems: [
                {
                  itemId: itemId,
                  itemCode: 'TEST-ITEM',
                  itemName: 'Test Item',
                  batchNumber: 'BATCH-SINGLE',
                  expiryDate: '2025-12-31',
                  warehouseBags: {
                    [singleWarehouseId.toString()]: 400, // 400 bags
                  },
                  totalBags: 400,
                  totalMt: 10, // 400 bags × 25kg / 1000 = 10 MT
                  unit: 'BAG/1x25kg',
                },
              ],
            },
          ],
        }),
      });

      // Act: Call findBatches
      const result = await itemsService.findBatches(
        itemId.toString(),
        singleWarehouseId.toString()
      );

      // Assert: Should return correct MT
      expect(result).toHaveLength(1);
      expect(result[0].availableMt).toBe(10);

      console.log('\n=== Preservation Test: Single-Warehouse Batch ===');
      console.log('Single-warehouse batch MT:', result[0].availableMt);
      console.log('This should continue to work correctly.');
      console.log('=================================================\n');
    });
  });

  describe('Preservation: Other Inventory Service Methods', () => {
    it('should ensure getBatches() in inventory.service.ts continues to work correctly', async () => {
      // Mock: Multi-warehouse batch for inventory service
      const multiWarehouseId1 = new Types.ObjectId();
      const multiWarehouseId2 = new Types.ObjectId();
      const multiBatchId = new Types.ObjectId();

      batchModel.find = () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: multiBatchId,
              itemId: itemId,
              warehouseId: multiWarehouseId1,
              batchNumber: 'BATCH-MULTI',
              expiryDate: '2025-12-31',
              stockedOut: false,
            },
          ],
        }),
      });

      sheetModel.find = () => ({
        lean: () => ({
          exec: async () => [
            {
              _id: new Types.ObjectId(),
              approvalStatus: 'approved',
              lineItems: [
                {
                  itemId: itemId,
                  itemCode: 'TEST-ITEM',
                  itemName: 'Test Item',
                  batchNumber: 'BATCH-MULTI',
                  expiryDate: '2025-12-31',
                  warehouseBags: {
                    [multiWarehouseId1.toString()]: 1000, // 1000 bags in warehouse 1
                    [multiWarehouseId2.toString()]: 2000, // 2000 bags in warehouse 2
                  },
                  totalBags: 3000,
                  totalMt: 75, // 3000 bags × 25kg / 1000 = 75 MT
                  unit: 'BAG/1x25kg',
                },
              ],
            },
          ],
        }),
      });

      // Act: Call getBatches from inventory service (already uses warehouse-specific calculation)
      const result = await inventoryService.getBatches(
        itemId.toString(),
        multiWarehouseId1.toString()
      );

      // Assert: Should return warehouse-specific MT (25 MT for warehouse 1)
      expect(result).toHaveLength(1);
      expect(result[0].availableMt).toBe(25); // 1000 bags × 25kg / 1000

      console.log('\n=== Preservation Test: InventoryService.getBatches() ===');
      console.log('Warehouse-specific MT from inventory service:', result[0].availableMt);
      console.log('This method already works correctly and should remain unchanged.');
      console.log('========================================================\n');
    });
  });
});
