import { SearchStateFacade } from './search.facade';
import { TestBed } from '@angular/core/testing';
import { Store, StoreModule } from '@ngrx/store';
import { Store as NgxsStore, NgxsModule } from '@ngxs/store';
import { primaryFiltersMock, mockFilters } from 'apps/sales/src/app/modules/shelf/shared/mockdata';
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
import { of, Observable } from 'rxjs';
import * as actions from './search.actions';
import * as selectors from './search.selectors';
import { SelectFilter } from 'apps/sales/src/app/modules/filter';
import { first } from 'rxjs/operators';
import { hot, cold } from 'jasmine-marbles';
import { TypedAction } from '@ngrx/store/src/models';

fdescribe('SearchFacade', () => {
  let facade: SearchStateFacade;
  let store: Store<any>;
  let ngxsStore: NgxsStore;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [StoreModule.forRoot({}), NgxsModule.forRoot()],
      providers: [Store, NgxsStore],
    });
  });

  beforeEach(() => {
    facade = TestBed.inject(SearchStateFacade);
    store = TestBed.inject(Store);
    ngxsStore = TestBed.inject(NgxsStore);
  });

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

  describe('setPrimaryFilters', () => {
    const primaryFilterOption: PrimaryFilterOption = {
      ...primaryFiltersMock[0],
      selected: true,
    };
    const id = 123;

    it('should dispatch setPrimary in Store', async () => {
      spyOn(store, 'dispatch').and.callThrough();
      spyOn(facade, 'setPrimaryFilters').and.callThrough();
      spyOn(facade, 'getPrimaryFilters$').and.returnValue(of(primaryFiltersMock));

      await facade.setPrimaryFilters(primaryFilterOption, id);

      expect(store.dispatch).toHaveBeenCalled();
    });

    it('should keep the original order of filter when dispatching setPrimary in Store', async () => {
      spyOn(store, 'dispatch').and.callThrough();
      spyOn(facade, 'setPrimaryFilters').and.callThrough();
      spyOn(facade, 'getPrimaryFilters$').and.returnValue(of(primaryFiltersMock));

      await facade.setPrimaryFilters(primaryFilterOption, id);

      const updatedFilters = [primaryFilterOption, primaryFiltersMock[1]];

      expect(store.dispatch).toHaveBeenCalledWith(actions.setPrimaryFilters({ filters: updatedFilters, id }));
    });
  });

  describe('selectedFilter$', () => {
    it('should return all selected filters from store', async () => {
      const currentFilters: Observable<SelectFilter[]> = of([
        {
          type: 'select',
          key: 'key1',
          name: 'Filter Name 1',
          options: [
            {
              key: 'Option 1',
              id: 'Option 1',
              name: 'Option 1',
              selected: true,
            },
            {
              key: 'Option 2',
              id: 'Option 2',
              name: 'Option 2',
              selected: false,
            },
          ],
        },
      ]);
      const primaryFilters: Observable<PrimaryFilterOption[]> = of([
        {
          id: 'primary1',
          key: 'primary1',
          name: 'Primary Filter 1',
          selected: true,
        },
        {
          id: 'primary2',
          key: 'primary2',
          name: 'Primary Filter 2',
          selected: false,
        },
      ]);
      spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(currentFilters);
      spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(primaryFilters);

      const result = await facade.selectedFilter$.pipe(first()).toPromise();

      expect(result).toEqual({
        key1: 'Option 1',
        primary1: 'true',
      });
    });
  });

  describe('#filtersChangedFromDefault$', () => {
    let selectFilters: SelectFilter[];
    let primaryFilters: PrimaryFilterOption[];

    beforeEach(() => {
      selectFilters = mockFilters;
      primaryFilters = primaryFiltersMock;
    });

    it('should return true if the select filters are changed', async () => {
      spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(of(primaryFilters));
      const updatedSelectFilters = [
        ...selectFilters.map((filter) =>
          filter.options
            ? {
                ...filter,
                options: filter.options.map((o, i) => (!i ? { ...o, selected: !o.initial_selected_state } : o)),
              }
            : filter
        ),
      ];
      spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(of(updatedSelectFilters));
      const result = await facade.filtersChangedFromDefault$.pipe(first()).toPromise();

      expect(result).toBe(true);
    });
    it('should return false if the select filters are unchanged', async () => {
      spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(of(primaryFilters));
      spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(of(selectFilters));
      const result = await facade.filtersChangedFromDefault$.pipe(first()).toPromise();

      expect(result).toBe(false);
    });

    it('should return false if the primary filters are unchanged', () => {
      const selectedFilterChangedFromDefault$ = hot('--a', { a: false });
      const primaryFilterChangedFromDefault$ = hot('--a', { a: false });
      const expected = cold('--b', { b: false });

      spyOnProperty(facade, 'selectedFilterChangedFromDefault$', 'get').and.returnValue(selectedFilterChangedFromDefault$);
      spyOnProperty(facade, 'primaryFilterChangedFromDefault$', 'get').and.returnValue(primaryFilterChangedFromDefault$);

      expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
    });

    it('should return true if the primary filters are changed', async () => {
      const selectFilters$ = hot('--a', { a: selectFilters });
      const primaryFilters$ = hot('--a', {
        a: primaryFilters.map((f, i) => (!i ? { ...f, selected: true } : f)),
      });
      const expected = cold('--b', { b: true });

      spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(selectFilters$);
      spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(primaryFilters$);

      expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
    });
  });

  describe('pendingInput$', () => {
    beforeEach(() => {
      spyOnProperty(facade, 'pendingInput$').and.callThrough();
    });

    it('should get the current pending input belonging to the provided id', async () => {
      const id = 123;
      spyOn(ngxsStore, 'select').and.returnValue(of({ id }));
      spyOn(store, 'select').and.returnValue('test');

      await facade.pendingInput$.pipe(first()).toPromise();
      expect(ngxsStore.select).toHaveBeenCalled();
      expect(store.select).toHaveBeenCalledWith(selectors.selectPendingInput, 123);
    });

    it('should return the pending input for the provided id', () => {
      const process$ = hot('a', { a: { id: 123 } });
      const input$ = hot('-b', { b: 'test' });

      spyOn(ngxsStore, 'select').and.returnValue(process$);
      spyOn(store, 'select').and.returnValue(input$);

      const expected = cold('-c', { c: 'test' });

      expect(facade.pendingInput$).toBeObservable(expected);
    });
  });

  describe('setPendingInput', () => {
    const id = 123;

    beforeEach(() => {
      spyOn(facade, 'getProcessId').and.returnValue(new Promise((resolve) => resolve(id)));
      spyOn(facade, 'setPendingInput').and.callThrough();
      spyOn(store, 'dispatch').and.callThrough();
    });
    it('should get the current process id when not provided', async () => {
      const pendingInput = 'Input Without Id';
      await facade.setPendingInput('Input Without Id');

      expect(facade.getProcessId).toHaveBeenCalled();
      expect(store.dispatch).toHaveBeenCalledWith(actions.setPendingInput({ id, input: pendingInput }));
    });
    it('should dispach set input to the store', async () => {
      const pendingInput = 'Input With Id';
      await facade.setPendingInput(pendingInput, id);

      expect(facade.getProcessId).not.toHaveBeenCalled();
      expect(store.dispatch).toHaveBeenCalledWith(actions.setPendingInput({ id, input: pendingInput }));
    });
  });
});
