import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { fakeAsync, tick } from '@angular/core/testing';
import faker from '@faker-js/faker';
import { Resource } from '@ngxp/rest';
import { DummyLinkRel } from 'libs/tech-shared/test/dummy';
import { createDummyListResource, createDummyResource } from 'libs/tech-shared/test/resource';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { singleColdCompleted } from '../../../test/marbles';
import { EMPTY_STRING } from '../tech.util';
import { ResourceSearchService } from './resource-search.service';
import { LinkRelationName, ListItemResource, SearchResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import {
  ListResource,
  StateResource,
  createEmptyStateResource,
  createStateResource,
} from './resource.util';

describe('ResourceSearchService', () => {
  let service: ResourceSearchService<Resource, ListResource, ListItemResource>;
  let config: SearchResourceServiceConfig<Resource>;
  let repository: Mock<ResourceRepository>;

  const baseResource: Resource = createDummyResource();
  const baseResourceSubj: BehaviorSubject<Resource> = new BehaviorSubject<Resource>(baseResource);
  const searchLinkRel: LinkRelationName = DummyLinkRel.DUMMY;

  const listResource: ListResource = createDummyListResource();
  const stateListResource: StateResource<ListResource> = createStateResource(listResource);

  const searchBy: string = faker.random.words(2);

  beforeEach(() => {
    config = {
      baseResource: baseResourceSubj,
      searchLinkRel,
    };
    repository = mock(ResourceRepository);

    service = new ResourceSearchService(config, useFromMock(repository));
  });

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

  describe('get result list', () => {
    beforeEach(() => {
      service.handleChanges = jest.fn();

      service.listResource.next(stateListResource);
      service.searchBy.next(searchBy);
    });

    it('should call handleChanges', fakeAsync(() => {
      service.getResultList().subscribe();
      tick();

      expect(service.handleChanges).toHaveBeenCalledWith(stateListResource, searchBy, baseResource);
    }));

    it('should return value', (done) => {
      service.getResultList().subscribe((resultList) => {
        expect(resultList).toBe(stateListResource);
        done();
      });
    });
  });

  describe('handle changes', () => {
    it('should call doSearch on loading flag', () => {
      service.doSearch = jest.fn();

      service.handleChanges({ ...stateListResource, loading: true }, searchBy, baseResource);

      expect(service.doSearch).toHaveBeenCalledWith(searchBy, baseResource);
    });

    it('should NOT call doSearch on loading flag false', () => {
      service.doSearch = jest.fn();

      service.handleChanges({ ...stateListResource, loading: false }, searchBy, baseResource);

      expect(service.doSearch).not.toHaveBeenCalled();
    });
  });

  describe('do search', () => {
    beforeEach(() => {
      service.dispatchSearch = jest.fn();
    });
    describe('on existing searchBy', () => {
      it('should call dispatchSearch', () => {
        service.doSearch(searchBy, baseResource);

        expect(service.dispatchSearch).toHaveBeenCalledWith(baseResource, searchLinkRel, searchBy);
      });

      it('should NOT clear list resource', () => {
        service.listResource.next(stateListResource);

        service.doSearch(searchBy, baseResource);

        expect(service.listResource.value).toBe(stateListResource);
      });
    });

    describe('on empty searchBy', () => {
      it('should clear list resource', () => {
        service.listResource.next(stateListResource);

        service.doSearch(EMPTY_STRING, baseResource);

        expect(service.listResource.value).toEqual(createEmptyStateResource());
      });

      it('should NOT call dispatchSearch', () => {
        service.doSearch(EMPTY_STRING, baseResource);

        expect(service.dispatchSearch).not.toHaveBeenCalled();
      });
    });
  });

  describe('dispatch search', () => {
    beforeEach(() => {
      repository.search.mockReturnValue(of(listResource));
    });

    it('should call respository', () => {
      service.dispatchSearch(baseResource, searchLinkRel, searchBy);

      expect(repository.search).toHaveBeenCalledWith(baseResource, searchLinkRel, searchBy);
    });

    it('should update list resource', () => {
      service.listResource.next(createEmptyStateResource());

      service.dispatchSearch(baseResource, searchLinkRel, searchBy);

      expect(service.listResource.value).toEqual(createStateResource(listResource));
    });
  });

  describe('clear search list', () => {
    it('should call dispatchClearSearch listResource by given result', () => {
      service.listResource.next(stateListResource);

      service.clearResultList();

      expect(service.listResource.value).toEqual(createEmptyStateResource());
    });
  });

  describe('saerch', () => {
    it('should set searchBy', () => {
      service.search(searchBy);

      expect(service.searchBy.value).toEqual(searchBy);
    });

    it('should set list resource loading', () => {
      service.listResource.next(stateListResource);

      service.search(searchBy);

      expect(service.listResource.value).toEqual({ ...stateListResource, loading: true });
    });
  });

  describe('get selected', () => {
    const dummyResource: Resource = createDummyResource();

    it('should return selected resource', () => {
      service.getSelectedResult = jest.fn().mockReturnValue(of(dummyResource));
      const selectedResult$: Observable<Resource> = service.getSelectedResult();

      expect(selectedResult$).toBeObservable(singleColdCompleted(dummyResource));
    });
  });

  describe('select result', () => {
    const dummyResource: Resource = createDummyResource();

    it('should update selected resource', () => {
      service.selectedResource.next(null);

      service.selectResult(dummyResource);

      expect(service.selectedResource.value).toEqual(dummyResource);
    });
  });

  describe('clear select result', () => {
    const dummyResource: Resource = createDummyResource();

    it('should update selected resource to null', () => {
      service.selectedResource.next(dummyResource);

      service.clearSelectedResult();

      expect(service.selectedResource.value).toBeNull();
    });
  });
});