import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { OrderCreationService } from './order-creation.service';
import {
  OrderCheckoutService,
  LogisticianService,
  LogisticianDTO,
} from '@generated/swagger/oms-api';
import { of, NEVER } from 'rxjs';
import { Order } from '../models';

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

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

  describe('createOrdersFromCheckout', () => {
    it('should create orders successfully', async () => {
      // Arrange
      const checkoutId = 123;
      const mockOrders: Order[] = [
        { id: 1 } as Order,
        { id: 2 } as Order,
      ];
      const mockResponse = { result: mockOrders, error: false };
      const orderCheckoutService = spectator.inject(OrderCheckoutService);
      orderCheckoutService.OrderCheckoutCreateOrderPOST.mockReturnValue(of(mockResponse));

      // Act
      const result = await spectator.service.createOrdersFromCheckout(checkoutId);

      // Assert
      expect(result).toEqual(mockOrders);
      expect(orderCheckoutService.OrderCheckoutCreateOrderPOST).toHaveBeenCalledWith({
        checkoutId,
      });
    });

    it('should throw error if checkoutId is invalid', async () => {
      // Act & Assert
      await expect(
        spectator.service.createOrdersFromCheckout(0),
      ).rejects.toThrow('Invalid checkoutId: 0');
    });

    it('should throw error if API response contains error', async () => {
      // Arrange
      const checkoutId = 123;
      const mockResponse = { error: true, result: undefined };
      const orderCheckoutService = spectator.inject(OrderCheckoutService);
      orderCheckoutService.OrderCheckoutCreateOrderPOST.mockReturnValue(of(mockResponse));

      // Act & Assert
      await expect(
        spectator.service.createOrdersFromCheckout(checkoutId),
      ).rejects.toThrow();
    });
  });

  describe('getLogistician', () => {
    it('should get default logistician (2470) successfully', async () => {
      // Arrange
      const mockLogistician: LogisticianDTO = {
        logisticianNumber: '2470',
        name: 'Default Logistician',
      } as LogisticianDTO;
      const mockResponse = {
        result: [mockLogistician],
        error: false,
      };
      const logisticianService = spectator.inject(LogisticianService);
      logisticianService.LogisticianGetLogisticians.mockReturnValue(of(mockResponse));

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

      // Assert
      expect(result).toEqual(mockLogistician);
      expect(logisticianService.LogisticianGetLogisticians).toHaveBeenCalledWith({});
    });

    it('should get specific logistician by number', async () => {
      // Arrange
      const mockLogisticians: LogisticianDTO[] = [
        { logisticianNumber: '1000', name: 'Logistician 1' } as LogisticianDTO,
        { logisticianNumber: '2000', name: 'Logistician 2' } as LogisticianDTO,
      ];
      const mockResponse = {
        result: mockLogisticians,
        error: false,
      };
      const logisticianService = spectator.inject(LogisticianService);
      logisticianService.LogisticianGetLogisticians.mockReturnValue(of(mockResponse));

      // Act
      const result = await spectator.service.getLogistician('2000');

      // Assert
      expect(result).toEqual(mockLogisticians[1]);
    });

    it('should throw error if logistician not found', async () => {
      // Arrange
      const mockResponse = {
        result: [
          { logisticianNumber: '1000', name: 'Other' } as LogisticianDTO,
        ],
        error: false,
      };
      const logisticianService = spectator.inject(LogisticianService);
      logisticianService.LogisticianGetLogisticians.mockReturnValue(of(mockResponse));

      // Act & Assert
      await expect(
        spectator.service.getLogistician('2470'),
      ).rejects.toThrow('Logistician 2470 not found');
    });

    it('should throw error if API response contains error', async () => {
      // Arrange
      const mockResponse = { error: true, result: undefined };
      const logisticianService = spectator.inject(LogisticianService);
      logisticianService.LogisticianGetLogisticians.mockReturnValue(of(mockResponse));

      // Act & Assert
      await expect(
        spectator.service.getLogistician(),
      ).rejects.toThrow();
    });

    it('should handle observable that never emits', async () => {
      // Arrange
      const logisticianService = spectator.inject(LogisticianService);
      logisticianService.LogisticianGetLogisticians.mockReturnValue(NEVER);

      // Act & Assert
      const fetchPromise = spectator.service.getLogistician();
      const timeout = new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Timeout')), 100),
      );
      await expect(Promise.race([fetchPromise, timeout])).rejects.toThrow(
        'Timeout',
      );
    });
  });
});
