import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { fakeAsync, tick } from '@angular/core/testing';
import faker from '@faker-js/faker';
import { Resource, getUrl } from '@ngxp/rest';
import { BehaviorSubject, of } from 'rxjs';
import { DummyResourceService, createDummyResource } from '../../../test/resource';
import { LinkRelationName, ResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import { StateResource, createEmptyStateResource, createStateResource } from './resource.util';

describe.skip('FIXME: mocking.ts issue due to module test | ResourceService ITCase', () => {
  let service: DummyResourceService<Resource, Resource>;
  let config: ResourceServiceConfig<Resource>;
  let repository: Mock<ResourceRepository>;

  const getLinkRel: LinkRelationName = faker.random.word();
  const editLinkRel: LinkRelationName = faker.random.word();
  const deleteLinkRel: LinkRelationName = faker.random.word();

  const configResource: Resource = createDummyResource([getLinkRel, editLinkRel]);
  const configStateResource: StateResource<Resource> = createStateResource(configResource);
  const configResourceSubj: BehaviorSubject<StateResource<Resource>> = new BehaviorSubject<
    StateResource<Resource>
  >(configStateResource);

  const loadedResource: Resource = createDummyResource([getLinkRel, editLinkRel]);

  const EXPECTED_EMITTED_TIMES_FOR_GET: number = 3;

  beforeEach(() => {
    config = {
      resource: configResourceSubj,
      getLinkRel,
      edit: { linkRel: editLinkRel },
      delete: { linkRel: deleteLinkRel },
    };
    repository = mock(ResourceRepository);

    service = new DummyResourceService<Resource, Resource>(config, useFromMock(repository));

    repository.getResource.mockReturnValueOnce(of(loadedResource));
    service.stateResource.next(createEmptyStateResource());
  });

  describe('get', () => {
    it('should emit initially loading stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        if (emittedTimes === 1) {
          console.info('RESPONSE 1: ', response);
          expect(response.loading).toBeTruthy();
          expect(response.resource).toBeNull();
          done();
        }
      });
    });

    it('should emit loading stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        if (emittedTimes === 2) {
          expect(response.loading).toBeTruthy();
          expect(response.resource).toBeNull();
          done();
        }
      });
    });

    it('should emit loaded stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        if (emittedTimes === EXPECTED_EMITTED_TIMES_FOR_GET) {
          expect(repository.getResource).toHaveBeenCalledWith(getUrl(configResource, getLinkRel));
          expect(response.resource).toBe(loadedResource);
          expect(response.loading).toBeFalsy();
          done();
        }
      });
    });

    it('should emit 3 times', async () => {
      let emittedTimes: number = 0;

      service.get().subscribe((response) => {
        emittedTimes++;
        console.info('RESPONSE ON GET: ', response);
      });
      console.info('EMITTED TIMES: ', emittedTimes);
      expect(emittedTimes).toBe(EXPECTED_EMITTED_TIMES_FOR_GET);
    });
  });

  describe('get - change configResource', () => {
    const reloadedResource: Resource = createDummyResource();

    const EXPECTED_EMITTED_TIMES: number = EXPECTED_EMITTED_TIMES_FOR_GET + 2;

    const newConfigResource: Resource = createDummyResource([deleteLinkRel, getLinkRel]);
    const newConfigStateResource: StateResource<Resource> = createStateResource(newConfigResource);

    beforeEach(() => {
      repository.getResource.mockReturnValueOnce(of(reloadedResource));
    });

    it('should emit loading stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => configResourceSubj.next(newConfigStateResource));
        if (emittedTimes === 4) {
          expect(response.loading).toBeTruthy();
          expect(response.resource).toBeNull();
          done();
        }
      });
    });

    it.skip('should emit reloaded stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => configResourceSubj.next(newConfigStateResource));
        if (emittedTimes === EXPECTED_EMITTED_TIMES) {
          expect(repository.getResource).toHaveBeenCalledTimes(2);
          expect(repository.getResource).toHaveBeenCalledWith(getUrl(configResource, getLinkRel));
          expect(repository.getResource).toHaveBeenCalledWith(
            getUrl(newConfigResource, getLinkRel),
          );
          expect(response.resource).toBe(reloadedResource);
          expect(response.loading).toBeFalsy();
          done();
        }
      });
    });

    it.skip('should emit 5 times', fakeAsync(async () => {
      let emittedTimes: number = 0;
      service.get().subscribe((response) => {
        emittedTimes++;
        console.info('RESPONSE ON GET: ', response);
        doAfterGetIsDone(emittedTimes, () => configResourceSubj.next(newConfigStateResource));
      });
      tick();

      console.info('EMITTED TIMES: ', emittedTimes);
      expect(emittedTimes).toBe(EXPECTED_EMITTED_TIMES);
    }));
  });

  describe('get - change configResource to null', () => {
    const EXPECTED_EMITTED_TIMES_FOR_GET: number = 3;
    const EXPECTED_EMITTED_TIMES: number = EXPECTED_EMITTED_TIMES_FOR_GET + 1;

    const emptyConfigStateResource: StateResource<Resource> = createEmptyStateResource();

    it('should emit empty stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => configResourceSubj.next(emptyConfigStateResource));
        if (emittedTimes === EXPECTED_EMITTED_TIMES) {
          expect(response.loading).toBeFalsy();
          expect(response.resource).toBeNull();
          done();
        }
      });
    });

    it('should emit 4 times', fakeAsync(async () => {
      let emittedTimes: number = 0;
      service.get().subscribe(() => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => configResourceSubj.next(emptyConfigStateResource));
      });
      tick();

      expect(emittedTimes).toBe(EXPECTED_EMITTED_TIMES);
    }));
  });

  describe.skip('FIXME (Funktioniert nicht mit den anderen Tests zusammen) refresh', () => {
    const reloadedResource: Resource = createDummyResource();

    const EXPECTED_EMITTED_TIMES_FOR_GET: number = 3;
    const EXPECTED_EMITTED_TIMES: number = EXPECTED_EMITTED_TIMES_FOR_GET + 2;

    beforeEach(() => {
      repository.getResource.mockReturnValueOnce(of(reloadedResource));
    });

    it('should return loading stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => service.refresh());
        if (emittedTimes === 4) {
          expect(repository.getResource).toHaveBeenCalledWith(getUrl(configResource, getLinkRel));
          expect(response.loading).toBeTruthy();
          done();
        }
      });
    });

    it('should return reloaded stateResource', (done) => {
      let emittedTimes: number = 0;
      service.get().subscribe((response: StateResource<Resource>) => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => service.refresh());
        if (emittedTimes === EXPECTED_EMITTED_TIMES) {
          expect(response.resource).toBe(reloadedResource);
          expect(response.loading).toBeFalsy();
          done();
        }
      });
    });

    it('should emit 5 times', fakeAsync(async () => {
      let emittedTimes: number = 0;
      service.get().subscribe(() => {
        emittedTimes++;
        doAfterGetIsDone(emittedTimes, () => service.refresh());
      });
      tick();

      expect(emittedTimes).toBe(EXPECTED_EMITTED_TIMES);
    }));
  });

  function doAfterGetIsDone(emittedTimes: number, runnable: () => void): void {
    if (emittedTimes === EXPECTED_EMITTED_TIMES_FOR_GET) {
      runnable();
    }
  }
});