import { TestBed } from '@angular/core/testing';
import { RemissionSupplierService } from './remission-supplier.service';
import { SupplierService } from '@generated/swagger/inventory-api';
import { RemissionStockService } from './remission-stock.service';
import { DataAccessError } from '@isa/common/data-access';
import { Supplier, Stock } from '../models';
import { SUPPLIER_STORAGE_KEY } from '../constants';
import { of, throwError } from 'rxjs';

// Mock injectStorage at the module level
const mockMemoryStorage = {
  get: jest.fn(),
  set: jest.fn(),
};

jest.mock('@isa/core/storage', () => ({
  injectStorage: jest.fn(() => mockMemoryStorage),
  MemoryStorageProvider: jest.fn(),
}));

describe('RemissionSupplierService', () => {
  let service: RemissionSupplierService;
  let mockSupplierService: jest.Mocked<SupplierService>;
  let mockStockService: jest.Mocked<RemissionStockService>;

  const mockSuppliers: Supplier[] = [
    {
      id: 123,
      name: 'Test Supplier GmbH',
      active: true,
    } as Supplier,
    {
      id: 456,
      name: 'Another Supplier Ltd',
      active: true,
    } as Supplier,
  ];

  const mockStock: Stock = {
    id: 789,
    name: 'Test Stock',
    active: true,
  } as Stock;

  beforeEach(() => {
    // Clear all mocks before each test
    jest.clearAllMocks();
    
    const supplierServiceSpy = {
      SupplierGetSuppliers: jest.fn(),
    };

    const stockServiceSpy = {
      fetchAssignedStock: jest.fn(),
    };

    TestBed.configureTestingModule({
      providers: [
        RemissionSupplierService,
        { provide: SupplierService, useValue: supplierServiceSpy },
        { provide: RemissionStockService, useValue: stockServiceSpy },
      ],
    });

    service = TestBed.inject(RemissionSupplierService);
    mockSupplierService = TestBed.inject(
      SupplierService,
    ) as jest.Mocked<SupplierService>;
    mockStockService = TestBed.inject(
      RemissionStockService,
    ) as jest.Mocked<RemissionStockService>;
  });

  describe('Service Creation', () => {
    it('should be created', () => {
      expect(service).toBeTruthy();
    });
  });

  describe('fetchSuppliers - Cache Hit', () => {
    it('should return cached suppliers when available', async () => {
      mockMemoryStorage.get.mockResolvedValue(mockSuppliers);

      const result = await service.fetchSuppliers();

      expect(result).toEqual(mockSuppliers);
      expect(mockMemoryStorage.get).toHaveBeenCalledWith(SUPPLIER_STORAGE_KEY);
      expect(mockStockService.fetchAssignedStock).not.toHaveBeenCalled();
      expect(mockSupplierService.SupplierGetSuppliers).not.toHaveBeenCalled();
    });

    it('should use cached data with abortSignal', async () => {
      const abortController = new AbortController();
      mockMemoryStorage.get.mockResolvedValue(mockSuppliers);

      const result = await service.fetchSuppliers(abortController.signal);

      expect(result).toEqual(mockSuppliers);
      expect(mockMemoryStorage.get).toHaveBeenCalledWith(SUPPLIER_STORAGE_KEY);
      expect(mockStockService.fetchAssignedStock).not.toHaveBeenCalled();
    });
  });

  describe('fetchSuppliers - API Success', () => {
    beforeEach(() => {
      mockMemoryStorage.get.mockResolvedValue(null);
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);
    });

    it('should fetch suppliers from API when cache is empty', async () => {
      const apiResponse = {
        result: mockSuppliers,
        error: false,
        message: undefined,
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      const result = await service.fetchSuppliers();

      expect(result).toEqual(mockSuppliers);
      expect(mockSupplierService.SupplierGetSuppliers).toHaveBeenCalledWith({
        stockId: mockStock.id,
      });
      expect(mockMemoryStorage.set).toHaveBeenCalledWith(
        SUPPLIER_STORAGE_KEY,
        mockSuppliers,
      );
    });

    it('should handle empty suppliers from API', async () => {
      const emptySuppliers: Supplier[] = [];
      const apiResponse = {
        result: emptySuppliers,
        error: false,
        message: undefined,
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      const result = await service.fetchSuppliers();

      expect(result).toEqual([]);
      expect(mockMemoryStorage.set).toHaveBeenCalledWith(
        SUPPLIER_STORAGE_KEY,
        emptySuppliers,
      );
    });

    it('should pass abortSignal to stock service', async () => {
      const abortController = new AbortController();
      const apiResponse = {
        result: mockSuppliers,
        error: false,
        message: undefined,
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      await service.fetchSuppliers(abortController.signal);

      expect(mockStockService.fetchAssignedStock).toHaveBeenCalledWith(
        abortController.signal,
      );
    });
  });

  describe('fetchSuppliers - Error Cases', () => {
    beforeEach(() => {
      mockMemoryStorage.get.mockResolvedValue(null);
    });

    it('should throw DataAccessError when no assigned stock', async () => {
      mockStockService.fetchAssignedStock.mockResolvedValue(null as unknown as Stock);

      await expect(service.fetchSuppliers()).rejects.toThrow(DataAccessError);
      await expect(service.fetchSuppliers()).rejects.toThrow(
        'No assigned stock found',
      );
    });

    it('should throw error when API returns error', async () => {
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);
      const apiResponse = {
        result: undefined,
        error: true,
        message: 'API Error',
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      await expect(service.fetchSuppliers()).rejects.toThrow('API Error');
    });

    it('should throw default error when no message', async () => {
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);
      const apiResponse = {
        result: undefined,
        error: true,
        message: undefined,
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      await expect(service.fetchSuppliers()).rejects.toThrow(
        'Failed to fetch Suppliers',
      );
    });

    it('should throw error when result is undefined', async () => {
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);
      const apiResponse = {
        result: undefined,
        error: false,
        message: 'No data',
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      await expect(service.fetchSuppliers()).rejects.toThrow('No data');
    });

    it('should handle service errors', async () => {
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);
      const error = new Error('Service error');
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(
        throwError(() => error),
      );

      await expect(service.fetchSuppliers()).rejects.toThrow('Service error');
    });
  });

  describe('Integration', () => {
    it('should properly cache API responses', async () => {
      mockMemoryStorage.get.mockResolvedValue(null);
      mockStockService.fetchAssignedStock.mockResolvedValue(mockStock);

      const apiResponse = {
        result: mockSuppliers,
        error: false,
        message: undefined,
      };
      mockSupplierService.SupplierGetSuppliers.mockReturnValue(of(apiResponse));

      const result = await service.fetchSuppliers();

      expect(result).toEqual(mockSuppliers);
      expect(mockMemoryStorage.set).toHaveBeenCalledWith(
        SUPPLIER_STORAGE_KEY,
        mockSuppliers,
      );
    });

    it('should handle storage errors', async () => {
      const storageError = new Error('Storage error');
      mockMemoryStorage.get.mockRejectedValue(storageError);

      await expect(service.fetchSuppliers()).rejects.toThrow('Storage error');
    });
  });
});
