import { TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { CatalougeSearchService } from './catalouge-search.service';
import { SearchService } from '@generated/swagger/cat-search-api';
import { Item } from '../models';
import { SearchByTermInput } from '../schemas/catalouge-search.schemas';
import { QueryTokenInput } from '../schemas';

describe('CatalougeSearchService', () => {
  let service: CatalougeSearchService;
  let searchServiceSpy: jest.Mocked<SearchService>;

  beforeEach(() => {
    const searchServiceMock = {
      SearchByEAN: jest.fn(),
      SearchSearch: jest.fn(),
      SearchLoyaltySettings: jest.fn(),
      SearchLoyaltyItems: jest.fn(),
    };

    TestBed.configureTestingModule({
      providers: [
        CatalougeSearchService,
        { provide: SearchService, useValue: searchServiceMock },
      ],
    });

    service = TestBed.inject(CatalougeSearchService);
    searchServiceSpy = TestBed.inject(
      SearchService,
    ) as jest.Mocked<SearchService>;
  });

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

  describe('searchByEans', () => {
    it('should return items when search is successful', async () => {
      // Arrange
      const mockItems: Item[] = [
        {
          id: 1,
          product: { name: 'Item 1' },
          catalogAvailability: { available: true },
        } as unknown as Item,
        {
          id: 2,
          product: { name: 'Item 2' },
          catalogAvailability: { available: true },
        } as unknown as Item,
      ];
      const mockResponse = {
        error: false,
        result: mockItems,
      };
      searchServiceSpy.SearchByEAN.mockReturnValue(of(mockResponse));

      // Act
      const result = await service.searchByEans(['123456789', '987654321']);

      // Assert
      expect(result).toEqual(mockItems);
      expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith([
        '123456789',
        '987654321',
      ]);
    });

    it('should return empty array when response has error', async () => {
      // Arrange
      const mockResponse = {
        error: true,
        message: 'Search failed',
      };
      searchServiceSpy.SearchByEAN.mockReturnValue(of(mockResponse));

      // Act
      const result = await service.searchByEans(['123456789']);

      // Assert
      expect(result).toEqual([]);
      expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith(['123456789']);
    });

    it('should handle single EAN', async () => {
      // Arrange
      const mockItems: Item[] = [
        {
          id: 1,
          product: { name: 'Item 1' },
          catalogAvailability: { available: true },
        } as unknown as Item,
      ];
      const mockResponse = {
        error: false,
        result: mockItems,
      };
      searchServiceSpy.SearchByEAN.mockReturnValue(of(mockResponse));

      // Act
      const result = await service.searchByEans(['123456789']);

      // Assert
      expect(result).toEqual(mockItems);
      expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith(['123456789']);
    });

    it('should handle empty EAN array', async () => {
      // Arrange
      const mockResponse = {
        error: false,
        result: [],
      };
      searchServiceSpy.SearchByEAN.mockReturnValue(of(mockResponse));

      // Act
      const result = await service.searchByEans([]);

      // Assert
      expect(result).toEqual([]);
      expect(searchServiceSpy.SearchByEAN).toHaveBeenCalledWith([]);
    });
  });

  describe('searchByTerm', () => {
    it('should return search results when successful', async () => {
      // Arrange
      const mockItems: Item[] = [
        {
          id: 1,
          product: { name: 'Test Item' },
          catalogAvailability: { available: true },
        } as unknown as Item,
      ];
      const mockResponse = {
        error: false,
        result: mockItems,
        total: 1,
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: SearchByTermInput = {
        searchTerm: 'test',
        skip: 0,
        take: 10,
      };
      const abortController = new AbortController();

      // Act
      const result = await service.searchByTerm(params, abortController.signal);

      // Assert
      expect(result).toEqual(mockResponse);
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        input: { qs: 'test' },
        skip: 0,
        take: 10,
        doNotTrack: true,
      });
    });

    it('should throw error when response has error', async () => {
      // Arrange
      const mockResponse = {
        error: true,
        message: 'Search failed',
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: SearchByTermInput = {
        searchTerm: 'test',
        skip: 0,
        take: 10,
      };
      const abortController = new AbortController();

      // Act & Assert
      await expect(
        service.searchByTerm(params, abortController.signal),
      ).rejects.toThrow('Search failed');
    });

    it('should handle abort signal', async () => {
      // Arrange
      const abortController = new AbortController();
      const mockResponse = {
        error: false,
        result: [],
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: SearchByTermInput = {
        searchTerm: 'test',
        skip: 0,
        take: 10,
      };

      // Act
      const result = await service.searchByTerm(params, abortController.signal);

      // Assert
      expect(result).toEqual(mockResponse);
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalled();
    });

    it('should use default values when not provided', async () => {
      // Arrange
      const mockResponse = {
        error: false,
        result: [],
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: SearchByTermInput = {
        searchTerm: 'test',
      };
      const abortController = new AbortController();

      // Act
      await service.searchByTerm(params, abortController.signal);

      // Assert
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        input: { qs: 'test' },
        skip: 0,
        take: 20,
        doNotTrack: true,
      });
    });
  });

  describe('fetchLoyaltyQuerySettings', () => {
    it('should return query settings when successful', async () => {
      // Arrange
      const mockQuerySettings = {
        filters: ['category', 'brand'],
        orderByOptions: [
          { by: 'name', label: 'Name', desc: false, selected: true },
          { by: 'price', label: 'Price', desc: true, selected: false },
        ],
      };
      const mockResponse = {
        error: false,
        result: mockQuerySettings,
      } as any; // Using any to match the API response structure
      searchServiceSpy.SearchLoyaltySettings.mockReturnValue(of(mockResponse));

      // Act
      const result = await service.fetchLoyaltyQuerySettings();

      // Assert
      expect(result).toEqual(mockQuerySettings);
      expect(searchServiceSpy.SearchLoyaltySettings).toHaveBeenCalled();
    });

    it('should handle error and return undefined when response has error', async () => {
      // Arrange
      // Mock the service to throw an error when catchResponseArgsErrorPipe processes the error response
      searchServiceSpy.SearchLoyaltySettings.mockReturnValue(
        throwError(() => new Error('Failed to fetch settings')),
      );

      // Act
      const result = await service.fetchLoyaltyQuerySettings();

      // Assert
      expect(result).toBeUndefined();
      expect(searchServiceSpy.SearchLoyaltySettings).toHaveBeenCalled();
    });

    it('should handle HTTP error and return undefined', async () => {
      // Arrange
      const errorResponse = new Error('HTTP Error');
      searchServiceSpy.SearchLoyaltySettings.mockReturnValue(
        throwError(() => errorResponse),
      );

      // Act
      const result = await service.fetchLoyaltyQuerySettings();

      // Assert
      expect(result).toBeUndefined();
      expect(searchServiceSpy.SearchLoyaltySettings).toHaveBeenCalled();
    });
  });

  describe('searchLoyaltyItems', () => {
    it('should return loyalty items when search is successful', async () => {
      // Arrange
      const mockItems: Item[] = [
        {
          id: 1,
          product: { name: 'Loyalty Item 1' },
          catalogAvailability: { available: true },
        } as unknown as Item,
        {
          id: 2,
          product: { name: 'Loyalty Item 2' },
          catalogAvailability: { available: true },
        } as unknown as Item,
      ];
      const mockResponse = {
        error: false,
        result: mockItems,
        total: 2,
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: QueryTokenInput = {
        filter: { category: 'electronics' },
        input: { brand: 'test-brand' },
        orderBy: [{ by: 'name', label: 'Name', desc: false, selected: true }],
        skip: 0,
        take: 10,
      };
      const abortController = new AbortController();

      // Act
      const result = await service.searchLoyaltyItems(
        params,
        abortController.signal,
      );

      // Assert
      expect(result).toEqual(mockResponse);
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        filter: {
          format: '!eb;!dl',
          praemie: '1-',
          category: 'electronics',
        },
        input: { brand: 'test-brand' },
        orderBy: [{ by: 'name', label: 'Name', desc: false, selected: true }],
        skip: 0,
        take: 10,
      });
    });

    it('should apply default loyalty filter when no custom filter provided', async () => {
      // Arrange
      const mockItems: Item[] = [
        {
          id: 1,
          product: { name: 'Loyalty Item' },
          catalogAvailability: { available: true },
        } as unknown as Item,
      ];
      const mockResponse = {
        error: false,
        result: mockItems,
        total: 1,
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: QueryTokenInput = {
        input: { search: 'test' },
      };
      const abortController = new AbortController();

      // Act
      const result = await service.searchLoyaltyItems(
        params,
        abortController.signal,
      );

      // Assert
      expect(result).toEqual(mockResponse);
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        filter: {
          format: '!eb;!dl',
          praemie: '1-',
        },
        input: { search: 'test' },
        orderBy: undefined,
        skip: 0,
        take: 25,
      });
    });

    it('should merge custom filter with loyalty filter', async () => {
      // Arrange
      const mockResponse = {
        error: false,
        result: [],
        total: 0,
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: QueryTokenInput = {
        filter: {
          category: 'books',
          format: 'custom-format', // This should override the default format
        },
        input: { title: 'test-book' },
        skip: 5,
        take: 15,
      };
      const abortController = new AbortController();

      // Act
      await service.searchLoyaltyItems(params, abortController.signal);

      // Assert
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        filter: {
          format: 'custom-format', // Custom filter should override default
          praemie: '1-',
          category: 'books',
        },
        input: { title: 'test-book' },
        orderBy: undefined,
        skip: 5,
        take: 15,
      });
    });

    it('should handle error and return undefined when response has error', async () => {
      // Arrange
      const mockResponse = {
        error: true,
        message: 'Search failed',
      };
      // Mock the service to throw an error when catchResponseArgsErrorPipe processes the error response
      searchServiceSpy.SearchSearch.mockReturnValue(
        throwError(() => new Error('Search failed')),
      );

      const params: QueryTokenInput = {
        input: { search: 'test' },
      };
      const abortController = new AbortController();

      // Act
      const result = await service.searchLoyaltyItems(
        params,
        abortController.signal,
      );

      // Assert
      expect(result).toBeUndefined();
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalled();
    });

    it('should handle HTTP error and return undefined', async () => {
      // Arrange
      const errorResponse = new Error('HTTP Error');
      searchServiceSpy.SearchSearch.mockReturnValue(
        throwError(() => errorResponse),
      );

      const params: QueryTokenInput = {
        input: { search: 'test' },
      };
      const abortController = new AbortController();

      // Act
      const result = await service.searchLoyaltyItems(
        params,
        abortController.signal,
      );

      // Assert
      expect(result).toBeUndefined();
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalled();
    });

    it('should handle minimal parameters with defaults', async () => {
      // Arrange
      const mockResponse = {
        error: false,
        result: [],
        total: 0,
      };
      searchServiceSpy.SearchSearch.mockReturnValue(of(mockResponse));

      const params: QueryTokenInput = {};
      const abortController = new AbortController();

      // Act
      const result = await service.searchLoyaltyItems(
        params,
        abortController.signal,
      );

      // Assert
      expect(result).toEqual(mockResponse);
      expect(searchServiceSpy.SearchSearch).toHaveBeenCalledWith({
        filter: {
          format: '!eb;!dl',
          praemie: '1-',
        },
        input: undefined,
        orderBy: undefined,
        skip: 0,
        take: 25,
      });
    });
  });
});
