import { AggregationMappingResource } from '@admin-client/reporting-shared';
import { ROUTES } from '@admin-client/shared';
import { NavigationService, RouteData } from '@alfa-client/navigation-shared';
import {
  createEmptyStateResource,
  createStateResource,
  decodeUrlFromEmbedding,
  ResourceRepository,
  ResourceServiceConfig,
  StateResource,
} from '@alfa-client/tech-shared';
import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { UrlSegment } from '@angular/router';
import { faker } from '@faker-js/faker';
import { afterAll, expect } from '@jest/globals';
import { ResourceUri } from '@ngxp/rest';
import { Observable, of } from 'rxjs';
import { createRouteData, createUrlSegment } from '../../../../navigation-shared/test/navigation-test-factory';
import { singleColdCompleted } from '../../../../tech-shared/test/marbles';
import { createAggregationMappingResource } from '../../test/aggregation-mapping';
import * as self from './aggregation-mapping-resource.service';

jest.mock('@alfa-client/tech-shared', () => ({
  ...jest.requireActual('@alfa-client/tech-shared'),
  decodeUrlFromEmbedding: jest.fn(),
}));

const decodeUrlFromEmbeddingMock: jest.Mock = decodeUrlFromEmbedding as jest.Mock;

describe('AggregationMappingResourceService', () => {
  let repository: Mock<ResourceRepository>;
  let navigationService: Mock<NavigationService>;

  beforeEach(() => {
    repository = mock(ResourceRepository);
    navigationService = mock(NavigationService);
  });

  describe('build config', () => {
    const getResourceByNavigationRouteSpy: jest.SpyInstance = jest
      .spyOn(self, '_getResourceByNavigationRoute')
      .mockImplementation();

    afterAll(() => {
      getResourceByNavigationRouteSpy.mockRestore();
    });

    it('should get resource by navigation route', () => {
      self._buildResourceServiceConfig(useFromMock(repository), useFromMock(navigationService));

      expect(getResourceByNavigationRouteSpy).toHaveBeenCalled();
    });

    it('should have aggregation mapping static resource', () => {
      const staticResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
      getResourceByNavigationRouteSpy.mockReturnValue(of(staticResource));

      const config: ResourceServiceConfig<AggregationMappingResource> = self._buildResourceServiceConfig(
        useFromMock(repository),
        useFromMock(navigationService),
      );

      expect(config.resource).toBeObservable(singleColdCompleted(staticResource));
    });
  });

  describe('get resource by navigation route', () => {
    const routeData: RouteData = createRouteData();
    const _isAggregationMappingEditUrl: jest.SpyInstance = jest.spyOn(self, '_isAggregationMappingEditUrl').mockImplementation();
    const _getAggregationMappingResourceByRoute: jest.SpyInstance = jest
      .spyOn(self, '_getAggregationMappingResourceByRoute')
      .mockImplementation();

    beforeEach(() => {
      navigationService.getCurrentRouteData.mockReturnValue(of(routeData));
    });

    afterAll(() => {
      _isAggregationMappingEditUrl.mockRestore();
      _getAggregationMappingResourceByRoute.mockRestore();
    });

    it('should get current route data', () => {
      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();

      expect(navigationService.getCurrentRouteData).toHaveBeenCalled();
    });

    it('should check if url contains resource uri', () => {
      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();

      expect(_isAggregationMappingEditUrl).toHaveBeenCalled();
    });

    it('should get aggregation mapping resource by route', () => {
      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();

      expect(_getAggregationMappingResourceByRoute).toHaveBeenCalled();
    });

    it('should return aggregation mapping by route', () => {
      const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
      _getAggregationMappingResourceByRoute.mockReturnValue(of(stateResource));
      _isAggregationMappingEditUrl.mockReturnValue(true);

      const resourceByRoute$: Observable<StateResource<AggregationMappingResource>> = self._getResourceByNavigationRoute(
        useFromMock(repository),
        useFromMock(navigationService),
      );

      expect(resourceByRoute$).toBeObservable(singleColdCompleted(stateResource));
    });

    it('should return empty state resource', () => {
      const stateResource: StateResource<AggregationMappingResource> = createEmptyStateResource();
      _isAggregationMappingEditUrl.mockReturnValue(false);

      const resourceByRoute$: Observable<StateResource<AggregationMappingResource>> = self._getResourceByNavigationRoute(
        useFromMock(repository),
        useFromMock(navigationService),
      );

      expect(resourceByRoute$).toBeObservable(singleColdCompleted(stateResource));
    });
  });

  describe('is aggregation mapping edit url', () => {
    it.each([0, 1, 3])('should return false of wrong number of segments = %d', (numberOfSegments: number) => {
      const routeData: RouteData = {
        ...createRouteData(),
        urlSegments: Array(numberOfSegments).fill(createUrlSegment()),
      };

      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
    });

    it('should return false if first path is wrong', () => {
      const routeData: RouteData = {
        ...createRouteData(),
        urlSegments: Array(2).fill(createUrlSegment()),
      };

      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
    });

    it('should return false if second path is wrong', () => {
      const firstUrlSegment: UrlSegment = createUrlSegment();
      firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
      const secondUrlSegment: UrlSegment = createUrlSegment();
      secondUrlSegment.path = 'neu';
      const routeData: RouteData = {
        ...createRouteData(),
        urlSegments: [firstUrlSegment, secondUrlSegment],
      };

      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
    });

    it('should return true', () => {
      const firstUrlSegment: UrlSegment = createUrlSegment();
      firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
      const secondUrlSegment: UrlSegment = createUrlSegment();
      secondUrlSegment.path = faker.internet.url();
      const routeData: RouteData = {
        ...createRouteData(),
        urlSegments: [firstUrlSegment, secondUrlSegment],
      };

      expect(self._isAggregationMappingEditUrl(routeData)).toBe(true);
    });
  });

  describe('get aggregation mapping resource by route', () => {
    const firstUrlSegment: UrlSegment = createUrlSegment();
    firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
    const secondUrlSegment: UrlSegment = createUrlSegment();
    secondUrlSegment.path = faker.internet.url();
    const routeData: RouteData = {
      ...createRouteData(),
      urlSegments: [firstUrlSegment, secondUrlSegment],
    };
    const uri: ResourceUri = faker.internet.url();
    const resource: AggregationMappingResource = createAggregationMappingResource();

    beforeEach(() => {
      decodeUrlFromEmbeddingMock.mockReturnValue(uri);
      repository.getResource.mockReturnValue(of(resource));
    });

    it('should decode url', () => {
      self._getAggregationMappingResourceByRoute(useFromMock(repository), routeData);

      expect(decodeUrlFromEmbeddingMock).toHaveBeenCalledWith(secondUrlSegment.path);
    });

    it('should get resource', () => {
      self._getAggregationMappingResourceByRoute(useFromMock(repository), routeData);

      expect(repository.getResource).toHaveBeenCalledWith(uri);
    });

    it('should return state resource', () => {
      const stateResource$: Observable<StateResource<AggregationMappingResource>> = self._getAggregationMappingResourceByRoute(
        useFromMock(repository),
        routeData,
      );

      expect(stateResource$).toBeObservable(singleColdCompleted(createStateResource(resource)));
    });
  });
});