import { TestBed } from '@angular/core/testing';
import { of, throwError, NEVER } from 'rxjs';
import { RemissionStockService } from './remission-stock.service';
import { StockService } from '@generated/swagger/inventory-api';
import { ResponseArgsError } from '@isa/common/data-access';
import { Stock, StockInfo } from '../models';

/**
 * Unit tests for RemissionStockService.
 * Tests the service's ability to fetch and cache stock information.
 * These tests are isolated and do not depend on each other.
 *
 * @group unit
 * @group services
 */
describe('RemissionStockService', () => {
  let service: RemissionStockService;
  let mockStockService: jest.Mocked<StockService>;

  const mockStock: Stock = {
    id: 123,
    name: 'Test Stock',
    description: 'Test Description',
  } as Stock;

  const mockStockInfo: StockInfo[] = [
    {
      id: 1,
      itemId: 100,
      quantity: 10,
      stockId: 123,
    } as StockInfo,
    {
      id: 2,
      itemId: 101,
      quantity: 5,
      stockId: 123,
    } as StockInfo,
  ];

  beforeEach(() => {
    // Create a fresh mock for each test to ensure isolation
    mockStockService = {
      StockCurrentStock: jest.fn(),
      StockInStock: jest.fn(),
    } as unknown as jest.Mocked<StockService>;

    TestBed.configureTestingModule({
      providers: [
        RemissionStockService,
        { provide: StockService, useValue: mockStockService },
      ],
    });

    // Create a fresh service instance for each test
    service = TestBed.inject(RemissionStockService);
  });

  afterEach(() => {
    // Clear all mocks after each test to prevent interference
    jest.clearAllMocks();
    jest.restoreAllMocks();
  });

  /**
   * Tests for fetchAssignedStock method.
   * Verifies caching behavior and API interaction.
   */
  describe('fetchAssignedStock', () => {
    it('should fetch stock from API', async () => {
      mockStockService.StockCurrentStock.mockReturnValue(
        of({ result: mockStock, error: false }),
      );

      const result = await service.fetchAssignedStock();

      expect(result).toEqual(mockStock);
      expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
    });

    it('should throw ResponseArgsError when API returns error', async () => {
      const errorResponse = { error: true, result: undefined };
      mockStockService.StockCurrentStock.mockReturnValue(of(errorResponse));

      await expect(service.fetchAssignedStock()).rejects.toThrow(
        ResponseArgsError,
      );
    });

    it('should throw ResponseArgsError when API returns no result', async () => {
      mockStockService.StockCurrentStock.mockReturnValue(
        of({ error: false, result: undefined }),
      );

      await expect(service.fetchAssignedStock()).rejects.toThrow(
        ResponseArgsError,
      );
    });

    it('should handle abort signal by configuring the observable pipeline', async () => {
      // Arrange
      const abortController = new AbortController();
      const pipeSpy = jest
        .fn()
        .mockReturnValue(of({ result: mockStock, error: false }));

      mockStockService.StockCurrentStock.mockReturnValue({
        pipe: pipeSpy,
      } as any);

      // Act
      const result = await service.fetchAssignedStock(abortController.signal);

      // Assert
      expect(result).toEqual(mockStock);
      expect(pipeSpy).toHaveBeenCalled();
      expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
    });

    it('should handle API observable errors', async () => {
      mockStockService.StockCurrentStock.mockReturnValue(
        throwError(() => new Error('Network error')),
      );

      await expect(service.fetchAssignedStock()).rejects.toThrow(
        'Network error',
      );
    });
  });

  /**
   * Tests for fetchStock method.
   * Verifies stock information fetching with item IDs.
   */
  describe('fetchStock', () => {
    /**
     * Valid test parameters for stock fetching.
     * Note: The schema expects 'stockId', not 'assignedStockId'
     */
    const validParams = {
      stockId: 123,
      itemIds: [100, 101],
    };

    it('should fetch stock info successfully with stockId provided', async () => {
      // Arrange
      mockStockService.StockInStock.mockReturnValue(
        of({ result: mockStockInfo, error: false }),
      );

      // Act
      const result = await service.fetchStockInfos(validParams);

      // Assert
      expect(result).toEqual(mockStockInfo);
      expect(mockStockService.StockInStock).toHaveBeenCalledWith({
        stockId: 123,
        articleIds: [100, 101],
      });
    });

    it('should fetch assigned stock when stockId not provided', async () => {
      // Arrange
      const paramsWithoutStockId = {
        itemIds: [100, 101],
      };

      mockStockService.StockCurrentStock.mockReturnValue(
        of({ result: mockStock, error: false }),
      );
      mockStockService.StockInStock.mockReturnValue(
        of({ result: mockStockInfo, error: false }),
      );

      // Act
      const result = await service.fetchStockInfos(paramsWithoutStockId);

      // Assert
      expect(result).toEqual(mockStockInfo);
      expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
      expect(mockStockService.StockInStock).toHaveBeenCalledWith({
        stockId: 123, // Should use the fetched assigned stock ID
        articleIds: [100, 101],
      });
    });

    it('should throw ResponseArgsError when API returns error', async () => {
      // Arrange
      const errorResponse = { error: true, result: undefined };
      mockStockService.StockInStock.mockReturnValue(of(errorResponse));

      // Act & Assert
      await expect(service.fetchStockInfos(validParams)).rejects.toThrow(
        ResponseArgsError,
      );
      expect(mockStockService.StockInStock).toHaveBeenCalled();
    });

    it('should throw ResponseArgsError when API returns no result', async () => {
      // Arrange
      mockStockService.StockInStock.mockReturnValue(
        of({ error: false, result: undefined }),
      );

      // Act & Assert
      await expect(service.fetchStockInfos(validParams)).rejects.toThrow(
        ResponseArgsError,
      );
      expect(mockStockService.StockInStock).toHaveBeenCalled();
    });

    it('should handle abort signal by configuring the observable pipeline', async () => {
      // Arrange
      const abortController = new AbortController();
      const pipeSpy = jest
        .fn()
        .mockReturnValue(of({ result: mockStockInfo, error: false }));

      mockStockService.StockInStock.mockReturnValue({
        pipe: pipeSpy,
      } as any);

      // Act
      const result = await service.fetchStockInfos(
        validParams,
        abortController.signal,
      );

      // Assert
      expect(result).toEqual(mockStockInfo);
      expect(pipeSpy).toHaveBeenCalled();
      expect(mockStockService.StockInStock).toHaveBeenCalled();
    });

    it('should validate params with schema and reject invalid stockId type', async () => {
      // Arrange - This will be validated by the schema
      const invalidParams = {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        stockId: 'invalid' as any, // Should be number
        itemIds: [100, 101],
      };

      // Act & Assert - The schema parsing should throw an error for invalid params
      await expect(service.fetchStockInfos(invalidParams)).rejects.toThrow();
      // Should not reach the API call
      expect(mockStockService.StockInStock).not.toHaveBeenCalled();
    });

    it('should validate params with schema and reject invalid itemIds type', async () => {
      // Arrange
      const invalidParams = {
        stockId: 123,
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        itemIds: 'not-an-array' as any, // Should be array
      };

      // Act & Assert
      await expect(service.fetchStockInfos(invalidParams)).rejects.toThrow();
      expect(mockStockService.StockInStock).not.toHaveBeenCalled();
    });

    it('should handle empty itemIds array', async () => {
      // Arrange
      const paramsWithEmptyItems = {
        stockId: 123,
        itemIds: [],
      };

      mockStockService.StockInStock.mockReturnValue(
        of({ result: [], error: false }),
      );

      // Act
      const result = await service.fetchStockInfos(paramsWithEmptyItems);

      // Assert
      expect(result).toEqual([]);
      expect(mockStockService.StockInStock).toHaveBeenCalledWith({
        stockId: 123,
        articleIds: [],
      });
    });

    it('should handle API observable errors', async () => {
      // Arrange
      mockStockService.StockInStock.mockReturnValue(
        throwError(() => new Error('Network error')),
      );

      // Act & Assert
      await expect(service.fetchStockInfos(validParams)).rejects.toThrow(
        'Network error',
      );
      expect(mockStockService.StockInStock).toHaveBeenCalled();
    });

    it('should return empty array when API returns empty result', async () => {
      // Arrange
      mockStockService.StockInStock.mockReturnValue(
        of({ result: [], error: false }),
      );

      // Act
      const result = await service.fetchStockInfos(validParams);

      // Assert
      expect(result).toEqual([]);
      expect(mockStockService.StockInStock).toHaveBeenCalled();
    });
  });
});
