import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { ReturnDetailsService } from './return-details.service';
import {
  ReceiptService,
  ResponseArgsOfReceiptDTO,
} from '@generated/swagger/oms-api';
import { of, NEVER } from 'rxjs';
import { FetchReturnDetails } from '../schemas';
import { Receipt } from '../models';

describe('ReturnDetailsService', () => {
  let spectator: SpectatorService<ReturnDetailsService>;
  const createService = createServiceFactory({
    service: ReturnDetailsService,
    mocks: [ReceiptService],
  });

  beforeEach(() => {
    spectator = createService();
  });

  it('should fetch return details successfully', async () => {
    // Arrange
    const mockParams: FetchReturnDetails = { receiptId: 123 };
    const mockResponse: any = { result: { id: 123, data: 'mockData' } };
    const receiptService = spectator.inject(ReceiptService);
    receiptService.ReceiptGetReceipt.mockReturnValue(of(mockResponse));

    // Act
    const result = await spectator.service.fetchReturnDetails(mockParams);

    expect(result).toEqual(mockResponse.result as Receipt);
  });

  it('should throw an error if API response contains an error', async () => {
    // Arrange
    const mockParams: FetchReturnDetails = { receiptId: 123 };
    const mockResponse: any = { error: 'API error' };
    const receiptService = spectator.inject(ReceiptService);
    receiptService.ReceiptGetReceipt.mockReturnValue(of(mockResponse));

    // Act
    try {
      await spectator.service.fetchReturnDetails(mockParams);
    } catch (err) {
      expect((err as Error).message).toBe('Failed to fetch return details');
    }
  });

  /**
   * Should return undefined or throw if API returns an empty object.
   */
  it('should handle empty API response gracefully', async () => {
    // Arrange
    const mockParams: FetchReturnDetails = { receiptId: 123 };
    const mockResponse: ResponseArgsOfReceiptDTO = {
      error: false,
      result: undefined,
    };
    const receiptService = spectator.inject(ReceiptService);
    receiptService.ReceiptGetReceipt.mockReturnValue(of(mockResponse));

    // Act & Assert
    await expect(
      spectator.service.fetchReturnDetails(mockParams),
    ).rejects.toThrow('Failed to fetch return details');
  });

  /**
   * Should call ReceiptGetReceipt with correct parameters.
   */
  it('should call ReceiptGetReceipt with correct params', async () => {
    // Arrange
    const mockParams: FetchReturnDetails = { receiptId: 456 };
    const mockResponse: ResponseArgsOfReceiptDTO = {
      error: false,
      result: { id: 456, data: 'mockData' } as unknown as Receipt,
    };
    const receiptService = spectator.inject(ReceiptService);
    const spy = jest.spyOn(receiptService, 'ReceiptGetReceipt');
    spy.mockReturnValue(of(mockResponse));

    // Act
    await spectator.service.fetchReturnDetails(mockParams);

    // Assert
    expect(spy).toHaveBeenCalledWith({ ...mockParams, eagerLoading: 2 });
  });

  /**
   * Should handle observable that never emits (simulate hanging request).
   */
  it('should handle observable that never emits', async () => {
    // Arrange
    const mockParams: FetchReturnDetails = { receiptId: 789 };
    const receiptService = spectator.inject(ReceiptService);
    // Simulate never emitting observable
    receiptService.ReceiptGetReceipt.mockReturnValue(NEVER);

    // Act & Assert
    // Should timeout or hang, so we expect the promise not to resolve
    // For test safety, wrap in a Promise.race with a timeout
    const fetchPromise = spectator.service.fetchReturnDetails(mockParams);
    const timeout = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), 100),
    );
    await expect(Promise.race([fetchPromise, timeout])).rejects.toThrow(
      'Timeout',
    );
  });
});
