/**
 * Unit tests for Purchase Receipts
 *
 * Tests receipt creation, confirmation, and quantity validation
 */
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import {
  createMockRepository,
  createMockQueryBuilder,
  createMockPurchaseOrder,
  createMockPurchaseOrderLine,
} from '../../../__tests__/helpers.js';

// Types for receipt entities
interface PurchaseReceipt {
  id: string;
  tenantId: string;
  orderId: string;
  receiptNumber: string;
  receiptDate: Date;
  receivedBy?: string;
  warehouseId?: string;
  locationId?: string;
  supplierDeliveryNote?: string;
  supplierInvoiceNumber?: string;
  status: 'draft' | 'confirmed' | 'cancelled';
  notes?: string;
  createdAt: Date;
  createdBy?: string;
  updatedAt: Date;
  items?: PurchaseReceiptItem[];
}

interface PurchaseReceiptItem {
  id: string;
  receiptId: string;
  orderItemId?: string;
  productId?: string;
  quantityExpected?: number;
  quantityReceived: number;
  quantityRejected: number;
  lotNumber?: string;
  serialNumber?: string;
  expiryDate?: Date;
  locationId?: string;
  qualityStatus: 'pending' | 'approved' | 'rejected' | 'quarantine';
  qualityNotes?: string;
  createdAt: Date;
  updatedAt: Date;
}

// Factory functions for receipts
function createMockReceipt(overrides: Partial<PurchaseReceipt> = {}): PurchaseReceipt {
  return {
    id: 'receipt-uuid-1',
    tenantId: 'test-tenant-uuid',
    orderId: 'order-uuid-1',
    receiptNumber: 'REC-000001',
    receiptDate: new Date(),
    receivedBy: 'user-uuid-1',
    warehouseId: 'warehouse-uuid-1',
    locationId: 'location-uuid-1',
    supplierDeliveryNote: null,
    supplierInvoiceNumber: null,
    status: 'draft',
    notes: null,
    createdAt: new Date(),
    createdBy: 'user-uuid-1',
    updatedAt: new Date(),
    items: [],
    ...overrides,
  };
}

function createMockReceiptItem(overrides: Partial<PurchaseReceiptItem> = {}): PurchaseReceiptItem {
  return {
    id: 'receipt-item-uuid-1',
    receiptId: 'receipt-uuid-1',
    orderItemId: 'order-item-uuid-1',
    productId: 'product-uuid-1',
    quantityExpected: 100,
    quantityReceived: 100,
    quantityRejected: 0,
    lotNumber: null,
    serialNumber: null,
    expiryDate: null,
    locationId: 'location-uuid-1',
    qualityStatus: 'pending',
    qualityNotes: null,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

describe('ReceiptsService', () => {
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';
  const orderId = 'order-uuid-1';

  let mockReceiptRepository: ReturnType<typeof createMockRepository>;
  let mockReceiptItemRepository: ReturnType<typeof createMockRepository>;
  let mockOrderRepository: ReturnType<typeof createMockRepository>;
  let mockOrderItemRepository: ReturnType<typeof createMockRepository>;

  beforeEach(() => {
    jest.clearAllMocks();
    mockReceiptRepository = createMockRepository<PurchaseReceipt>();
    mockReceiptItemRepository = createMockRepository<PurchaseReceiptItem>();
    mockOrderRepository = createMockRepository();
    mockOrderItemRepository = createMockRepository();
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  describe('Receipt Creation', () => {
    describe('createReceipt', () => {
      it('should create receipt for confirmed order', async () => {
        const confirmedOrder = createMockPurchaseOrder({
          id: orderId,
          status: 'confirmed',
          lines: [createMockPurchaseOrderLine({ quantity: 100, qty_received: 0 })],
        });

        const newReceipt = createMockReceipt({
          orderId: confirmedOrder.id,
          status: 'draft',
        });

        mockOrderRepository.findOne.mockResolvedValue(confirmedOrder);
        mockReceiptRepository.create.mockReturnValue(newReceipt);
        mockReceiptRepository.save.mockResolvedValue(newReceipt);

        const result = await mockReceiptRepository.save(
          mockReceiptRepository.create({
            tenantId,
            orderId: confirmedOrder.id,
            status: 'draft',
          })
        );

        expect(result).toBeDefined();
        expect(result.status).toBe('draft');
        expect(result.orderId).toBe(orderId);
      });

      it('should not create receipt for draft order', async () => {
        const draftOrder = createMockPurchaseOrder({
          id: orderId,
          status: 'draft',
        });

        mockOrderRepository.findOne.mockResolvedValue(draftOrder);

        // Validation logic
        const canCreateReceipt = draftOrder.status === 'confirmed' || draftOrder.status === 'partial';

        expect(canCreateReceipt).toBe(false);
      });

      it('should not create receipt for cancelled order', async () => {
        const cancelledOrder = createMockPurchaseOrder({
          id: orderId,
          status: 'cancelled',
        });

        mockOrderRepository.findOne.mockResolvedValue(cancelledOrder);

        const canCreateReceipt = cancelledOrder.status === 'confirmed' || cancelledOrder.status === 'partial';

        expect(canCreateReceipt).toBe(false);
      });
    });

    describe('addReceiptItem', () => {
      it('should add item to receipt', async () => {
        const receipt = createMockReceipt({ status: 'draft' });
        const newItem = createMockReceiptItem({
          receiptId: receipt.id,
          quantityReceived: 50,
        });

        mockReceiptRepository.findOne.mockResolvedValue(receipt);
        mockReceiptItemRepository.create.mockReturnValue(newItem);
        mockReceiptItemRepository.save.mockResolvedValue(newItem);

        const result = await mockReceiptItemRepository.save(
          mockReceiptItemRepository.create(newItem)
        );

        expect(result).toBeDefined();
        expect(result.quantityReceived).toBe(50);
        expect(result.receiptId).toBe(receipt.id);
      });

      it('should validate quantity does not exceed remaining', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 80,
        });

        const remainingQty = orderLine.quantity - orderLine.qty_received;
        const requestedQty = 30; // Exceeds remaining

        expect(requestedQty).toBeGreaterThan(remainingQty);
        expect(remainingQty).toBe(20);
      });

      it('should not add item to confirmed receipt', async () => {
        const confirmedReceipt = createMockReceipt({ status: 'confirmed' });

        mockReceiptRepository.findOne.mockResolvedValue(confirmedReceipt);

        const canAddItem = confirmedReceipt.status === 'draft';

        expect(canAddItem).toBe(false);
      });
    });
  });

  describe('Receipt Confirmation', () => {
    describe('confirmReceipt', () => {
      it('should confirm receipt and update order quantities', async () => {
        const receipt = createMockReceipt({
          status: 'draft',
          items: [
            createMockReceiptItem({
              orderItemId: 'line-1',
              quantityReceived: 50,
            }),
          ],
        });

        const orderLine = createMockPurchaseOrderLine({
          id: 'line-1',
          quantity: 100,
          qty_received: 0,
        });

        mockReceiptRepository.findOne.mockResolvedValue(receipt);
        mockOrderItemRepository.findOne.mockResolvedValue(orderLine);

        // Simulate confirmation
        receipt.status = 'confirmed';
        orderLine.qty_received = 50;

        expect(receipt.status).toBe('confirmed');
        expect(orderLine.qty_received).toBe(50);
      });

      it('should not confirm empty receipt', async () => {
        const emptyReceipt = createMockReceipt({
          status: 'draft',
          items: [],
        });

        mockReceiptRepository.findOne.mockResolvedValue(emptyReceipt);

        const canConfirm = emptyReceipt.items && emptyReceipt.items.length > 0;

        expect(canConfirm).toBe(false);
      });

      it('should update order status to partial for incomplete receipt', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        // Receive 50 of 100
        orderLine.qty_received = 50;

        const isComplete = orderLine.qty_received >= orderLine.quantity;
        const newStatus = isComplete ? 'received' : 'partial';

        expect(newStatus).toBe('partial');
      });

      it('should update order status to received for complete receipt', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        // Receive all 100
        orderLine.qty_received = 100;

        const isComplete = orderLine.qty_received >= orderLine.quantity;
        const newStatus = isComplete ? 'received' : 'partial';

        expect(newStatus).toBe('received');
      });
    });

    describe('cancelReceipt', () => {
      it('should cancel draft receipt', async () => {
        const draftReceipt = createMockReceipt({ status: 'draft' });

        mockReceiptRepository.findOne.mockResolvedValue(draftReceipt);

        const canCancel = draftReceipt.status === 'draft';

        expect(canCancel).toBe(true);
        draftReceipt.status = 'cancelled';
        expect(draftReceipt.status).toBe('cancelled');
      });

      it('should not cancel confirmed receipt without reversal', async () => {
        const confirmedReceipt = createMockReceipt({ status: 'confirmed' });

        mockReceiptRepository.findOne.mockResolvedValue(confirmedReceipt);

        const canCancel = confirmedReceipt.status === 'draft';

        expect(canCancel).toBe(false);
      });
    });
  });

  describe('Quantity Validation', () => {
    describe('validateReceiptQuantities', () => {
      it('should accept exact quantity', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        const receiveQty = 100;
        const remainingQty = orderLine.quantity - orderLine.qty_received;
        const isValid = receiveQty <= remainingQty;

        expect(isValid).toBe(true);
      });

      it('should accept partial quantity', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        const receiveQty = 50;
        const remainingQty = orderLine.quantity - orderLine.qty_received;
        const isValid = receiveQty <= remainingQty;

        expect(isValid).toBe(true);
      });

      it('should reject quantity exceeding remaining', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 80,
        });

        const receiveQty = 30; // Only 20 remaining
        const remainingQty = orderLine.quantity - orderLine.qty_received;
        const isValid = receiveQty <= remainingQty;

        expect(isValid).toBe(false);
        expect(remainingQty).toBe(20);
      });

      it('should handle over-receipt with tolerance', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        const TOLERANCE_PERCENT = 5; // 5% over-receipt allowed
        const maxAllowed = orderLine.quantity * (1 + TOLERANCE_PERCENT / 100);
        const receiveQty = 103; // 3% over

        const isWithinTolerance = receiveQty <= maxAllowed;

        expect(isWithinTolerance).toBe(true);
        expect(maxAllowed).toBe(105);
      });

      it('should reject over-receipt exceeding tolerance', async () => {
        const orderLine = createMockPurchaseOrderLine({
          quantity: 100,
          qty_received: 0,
        });

        const TOLERANCE_PERCENT = 5;
        const maxAllowed = orderLine.quantity * (1 + TOLERANCE_PERCENT / 100);
        const receiveQty = 110; // 10% over, exceeds tolerance

        const isWithinTolerance = receiveQty <= maxAllowed;

        expect(isWithinTolerance).toBe(false);
      });
    });

    describe('handleRejectedQuantities', () => {
      it('should track rejected quantities', async () => {
        const receiptItem = createMockReceiptItem({
          quantityExpected: 100,
          quantityReceived: 90,
          quantityRejected: 10,
        });

        const totalProcessed = receiptItem.quantityReceived + receiptItem.quantityRejected;

        expect(totalProcessed).toBe(100);
        expect(receiptItem.quantityRejected).toBe(10);
      });

      it('should update quality status for rejected items', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 90,
          quantityRejected: 10,
          qualityStatus: 'pending',
        });

        // If items are rejected, mark for quality review
        if (receiptItem.quantityRejected > 0) {
          receiptItem.qualityStatus = 'rejected';
          receiptItem.qualityNotes = 'Items damaged during shipping';
        }

        expect(receiptItem.qualityStatus).toBe('rejected');
        expect(receiptItem.qualityNotes).toBeDefined();
      });
    });
  });

  describe('Quality Control', () => {
    describe('qualityInspection', () => {
      it('should put items in quarantine for inspection', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 100,
          qualityStatus: 'pending',
        });

        // Move to quarantine for inspection
        receiptItem.qualityStatus = 'quarantine';

        expect(receiptItem.qualityStatus).toBe('quarantine');
      });

      it('should approve items after inspection', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 100,
          qualityStatus: 'quarantine',
        });

        // Approve after inspection
        receiptItem.qualityStatus = 'approved';

        expect(receiptItem.qualityStatus).toBe('approved');
      });

      it('should reject items failing inspection', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 100,
          qualityStatus: 'quarantine',
        });

        // Reject after inspection
        receiptItem.qualityStatus = 'rejected';
        receiptItem.qualityNotes = 'Failed quality standards';

        expect(receiptItem.qualityStatus).toBe('rejected');
      });
    });
  });

  describe('Lot and Serial Number Tracking', () => {
    describe('lotTracking', () => {
      it('should track lot number for received items', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 100,
          lotNumber: 'LOT-2024-001',
          expiryDate: new Date('2025-06-15'),
        });

        expect(receiptItem.lotNumber).toBe('LOT-2024-001');
        expect(receiptItem.expiryDate).toBeDefined();
      });

      it('should track serial number for serialized items', async () => {
        const receiptItem = createMockReceiptItem({
          quantityReceived: 1,
          serialNumber: 'SN-ABC123',
        });

        expect(receiptItem.serialNumber).toBe('SN-ABC123');
        expect(receiptItem.quantityReceived).toBe(1); // Serialized items must be qty 1
      });

      it('should validate expiry date is in future', async () => {
        const today = new Date();
        const futureDate = new Date(today.getFullYear() + 1, today.getMonth(), today.getDate());
        const pastDate = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate());

        const isFutureValid = futureDate > today;
        const isPastValid = pastDate > today;

        expect(isFutureValid).toBe(true);
        expect(isPastValid).toBe(false);
      });
    });
  });

  describe('Multi-Item Receipt', () => {
    describe('receiveMultipleItems', () => {
      it('should process receipt with multiple items', async () => {
        const receipt = createMockReceipt({
          status: 'draft',
          items: [
            createMockReceiptItem({
              id: 'item-1',
              orderItemId: 'line-1',
              quantityReceived: 100,
            }),
            createMockReceiptItem({
              id: 'item-2',
              orderItemId: 'line-2',
              quantityReceived: 50,
            }),
            createMockReceiptItem({
              id: 'item-3',
              orderItemId: 'line-3',
              quantityReceived: 25,
            }),
          ],
        });

        expect(receipt.items).toHaveLength(3);

        const totalReceived = receipt.items!.reduce(
          (sum, item) => sum + item.quantityReceived,
          0
        );

        expect(totalReceived).toBe(175);
      });

      it('should handle mixed quality statuses', async () => {
        const receipt = createMockReceipt({
          status: 'draft',
          items: [
            createMockReceiptItem({
              id: 'item-1',
              quantityReceived: 100,
              qualityStatus: 'approved',
            }),
            createMockReceiptItem({
              id: 'item-2',
              quantityReceived: 50,
              qualityStatus: 'quarantine',
            }),
            createMockReceiptItem({
              id: 'item-3',
              quantityReceived: 25,
              qualityStatus: 'rejected',
            }),
          ],
        });

        const approvedItems = receipt.items!.filter(
          (i) => i.qualityStatus === 'approved'
        );
        const pendingQcItems = receipt.items!.filter(
          (i) => i.qualityStatus === 'quarantine'
        );
        const rejectedItems = receipt.items!.filter(
          (i) => i.qualityStatus === 'rejected'
        );

        expect(approvedItems).toHaveLength(1);
        expect(pendingQcItems).toHaveLength(1);
        expect(rejectedItems).toHaveLength(1);
      });
    });
  });

  describe('Warehouse Location Assignment', () => {
    describe('assignLocation', () => {
      it('should assign default warehouse location', async () => {
        const receiptItem = createMockReceiptItem({
          locationId: 'default-location-uuid',
        });

        expect(receiptItem.locationId).toBe('default-location-uuid');
      });

      it('should allow custom location assignment', async () => {
        const receiptItem = createMockReceiptItem({
          locationId: 'custom-location-uuid',
        });

        expect(receiptItem.locationId).toBe('custom-location-uuid');
      });

      it('should validate location belongs to warehouse', async () => {
        const warehouseId = 'warehouse-uuid-1';
        const validLocation = { id: 'loc-1', warehouseId: 'warehouse-uuid-1' };
        const invalidLocation = { id: 'loc-2', warehouseId: 'warehouse-uuid-2' };

        const isValidLocation = validLocation.warehouseId === warehouseId;
        const isInvalidLocation = invalidLocation.warehouseId === warehouseId;

        expect(isValidLocation).toBe(true);
        expect(isInvalidLocation).toBe(false);
      });
    });
  });

  describe('Supplier Reference Tracking', () => {
    describe('supplierDocuments', () => {
      it('should track supplier delivery note', async () => {
        const receipt = createMockReceipt({
          supplierDeliveryNote: 'DN-SUP-2024-001',
        });

        expect(receipt.supplierDeliveryNote).toBe('DN-SUP-2024-001');
      });

      it('should track supplier invoice number', async () => {
        const receipt = createMockReceipt({
          supplierInvoiceNumber: 'INV-SUP-2024-001',
        });

        expect(receipt.supplierInvoiceNumber).toBe('INV-SUP-2024-001');
      });
    });
  });

  describe('Edge Cases', () => {
    it('should handle zero quantity receipt item', async () => {
      const receiptItem = createMockReceiptItem({
        quantityExpected: 100,
        quantityReceived: 0,
        quantityRejected: 0,
      });

      const wasReceived = receiptItem.quantityReceived > 0;

      expect(wasReceived).toBe(false);
    });

    it('should handle receipt with all items rejected', async () => {
      const receipt = createMockReceipt({
        items: [
          createMockReceiptItem({
            quantityReceived: 0,
            quantityRejected: 100,
            qualityStatus: 'rejected',
          }),
        ],
      });

      const totalReceived = receipt.items!.reduce(
        (sum, i) => sum + i.quantityReceived,
        0
      );
      const totalRejected = receipt.items!.reduce(
        (sum, i) => sum + i.quantityRejected,
        0
      );

      expect(totalReceived).toBe(0);
      expect(totalRejected).toBe(100);
    });

    it('should handle decimal quantities', async () => {
      const receiptItem = createMockReceiptItem({
        quantityExpected: 100.5,
        quantityReceived: 100.25,
        quantityRejected: 0.25,
      });

      const total = receiptItem.quantityReceived + receiptItem.quantityRejected;

      expect(total).toBeCloseTo(100.5, 2);
    });
  });
});
