import { TestBed } from '@angular/core/testing';
import { SearchEffects } from './search.effects';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable } from 'rxjs';
import {
  StrictHttpResponse,
  ResponseArgsOfIEnumerableOfInputDTO,
  OrderService,
  InputDTO,
  ListResponseArgsOfOrderItemListItemDTO,
  AbholfachService,
} from '@swagger/oms';
import { hot, cold } from 'jasmine-marbles';
import * as actions from './search.actions';
import * as processActions from 'apps/sales/src/app/core/store/actions/process.actions';
import { Store as NgxsStore } from '@ngxs/store';
import { Store } from '@ngrx/store';
import { Actions, NgxsModule } from '@ngxs/store';
import { BranchService } from '@sales/core-services';
import { SearchStateFacade } from './search.facade';
import { inputDtoToSelectedFilters, inputDtoToPrimaryFilterOption } from './mappers';
import { Process } from 'apps/sales/src/app/core/models/process.model';
import { first } from 'rxjs/operators';
import { ProcessState } from 'apps/sales/src/app/core/store/state/process.state';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpHeaders } from '@angular/common/http';

fdescribe('#SearchEffects', () => {
  let abholfachService: jasmine.SpyObj<AbholfachService>;
  let searchEffects: SearchEffects;
  let actions$: Observable<any>;
  let store: MockStore<any>;
  let ngxsStore: NgxsStore;
  let ngxsActions$: Observable<any>;
  let searchStateFacade: SearchStateFacade;

  // Test Data
  const id = 123;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, NgxsModule.forRoot([]), NgxsModule.forFeature([ProcessState])],
      providers: [
        SearchEffects,
        provideMockStore({}),
        provideMockActions(() => actions$),
        {
          provide: AbholfachService,
          useValue: jasmine.createSpyObj('abholfachService', ['AbholfachGetWarenausgabeFilterResponse']),
        },
        {
          provide: BranchService,
          useValue: jasmine.createSpy('branchService'),
        },
        {
          provide: SearchStateFacade,
          useValue: jasmine.createSpy('searchStateFacade'),
        },
        Actions,
        SearchStateFacade,
      ],
    });

    searchEffects = TestBed.inject(SearchEffects);
    abholfachService = TestBed.inject(AbholfachService);
    store = TestBed.inject(Store);
    ngxsStore = TestBed.inject(NgxsStore);
    ngxsActions$ = TestBed.inject(Actions);
    searchStateFacade = TestBed.inject(SearchStateFacade);
  });

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

  describe('#FetchFilters', () => {
    it('should fetch the filters and dispatch fetch filters done with filters http response', () => {
      const mockHttpResponse = ({
        ok: true,
        body: [],
      } as unknown) as StrictHttpResponse<ResponseArgsOfIEnumerableOfInputDTO>;
      const fetchFiltersAction = actions.fetchFilters({ id });

      const fetchFiltersDoneAction = actions.fetchFiltersDone({
        id,
        response: mockHttpResponse,
      });

      const response = cold('-a', { a: mockHttpResponse });
      abholfachService.AbholfachGetWarenausgabeFilterResponse.and.returnValue(response);

      actions$ = hot('--a', { a: fetchFiltersAction });
      const expected = cold('---b', { b: fetchFiltersDoneAction });

      expect(searchEffects.fetchFilters$).toBeObservable(expected);
    });
  });

  describe('#FetchFiltersDone', () => {
    it('should dispatch setSelectedFiters and setPrimaryFilters if the filters http response is ok ', () => {
      const mockHttpResponse = ({
        ok: true,
        body: {
          result: [
            { key: 'Key Mock 1', label: 'Label Mock 1' },
            {
              key: 'Key Mock 2',
              label: 'Label Mock 2',
              options: {
                max: 3,
                values: [
                  { key: 'Sub 1 Key', label: 'Sub 1 Label', selected: false },
                  { key: 'Sub 2 Key', label: 'Sub 2 Label', selected: true },
                ],
              },
            },
          ],
        },
      } as unknown) as StrictHttpResponse<ResponseArgsOfIEnumerableOfInputDTO>;
      const fetchFiltersDoneAction = actions.fetchFiltersDone({
        id,
        response: mockHttpResponse,
      });

      const selectFilters = [
        {
          key: 'Key Mock 2',
          label: 'Label Mock 2',
          options: {
            max: 3,
            values: [
              { key: 'Sub 1 Key', label: 'Sub 1 Label', selected: false },
              { key: 'Sub 2 Key', label: 'Sub 2 Label', selected: true },
            ],
          },
        } as InputDTO,
      ].map((input) => inputDtoToSelectedFilters(input));

      const primaryFilters = [{ key: 'Key Mock 1', label: 'Label Mock 1' } as InputDTO].map((input) =>
        inputDtoToPrimaryFilterOption(input)
      );

      const setSelectedFiltersAction = actions.setSelectFilters({
        id,
        filters: selectFilters,
      });
      const setPrimaryFiltersAction = actions.setPrimaryFilters({
        id,
        filters: primaryFilters,
      });

      actions$ = hot('-a', { a: fetchFiltersDoneAction });
      const expected = cold('-(bc)', {
        b: setSelectedFiltersAction,
        c: setPrimaryFiltersAction,
      });

      expect(searchEffects.fetchFiltersDone$).toBeObservable(expected);
    });
  });

  describe('#InitAddProcess', () => {
    beforeEach(() => {
      searchStateFacade = TestBed.inject(SearchStateFacade);
    });
    it('should dispatch addSearchProcess and fetch filters', async () => {
      spyOn(ngxsStore, 'dispatch').and.callThrough();
      spyOn(store, 'dispatch').and.callThrough();
      spyOn(searchStateFacade, 'fetchFilters').and.callFake(() => null);

      const addProcessAction = new processActions.AddProcess({
        id,
      } as Process);

      const addSearchProcessAction = actions.addSearchProcess({ id });

      searchEffects.initAddProcess();

      await ngxsStore
        .dispatch(
          new processActions.AddProcess({
            id,
          } as Process)
        )
        .pipe(first())
        .toPromise();

      expect(ngxsStore.dispatch).toHaveBeenCalledWith(addProcessAction);
      expect(store.dispatch).toHaveBeenCalledWith(addSearchProcessAction);
      expect(searchStateFacade.fetchFilters).toHaveBeenCalledWith(id);
    });
  });

  describe('#FetchResultsDone', () => {
    it('should dispatch setHits and addResult with fallback values when no result is returned', () => {
      const httpResponse: {
        id: number;
        response: StrictHttpResponse<ListResponseArgsOfOrderItemListItemDTO>;
      } = {
        id,
        response: {
          ok: true,
          body: {
            error: false,
            hits: 0,
            invalidProperties: {},
            skip: 0,
            take: 20,
          },
          type: null,
          clone: null,
          headers: new HttpHeaders(),
          status: 200,
          statusText: 'Okay',
          url: 'someRandomUrl.de',
        },
      };

      const fetchResultsDone = actions.fetchResultDone(httpResponse);
      actions$ = hot('-a', { a: fetchResultsDone });

      const setHitsAction = actions.setHits({ id, hits: 0 });
      const addResultAction = actions.addResult({ id, result: [] });

      const expected = cold('-(bc)', { b: setHitsAction, c: addResultAction });

      expect(searchEffects.fetchResultDone$).toBeObservable(expected);
    });

    it('should dispatch setHits and add Result', () => {
      const httpResponse: {
        id: number;
        response: StrictHttpResponse<ListResponseArgsOfOrderItemListItemDTO>;
      } = {
        id,
        response: {
          ok: true,
          body: {
            error: false,
            result: [{ orderId: 345 }],
            hits: 1,
            invalidProperties: {},
            skip: 5,
            take: 20,
          },
          type: null,
          clone: null,
          headers: new HttpHeaders(),
          status: 200,
          statusText: 'Okay',
          url: 'someRandomUrl.de',
        },
      };

      const fetchResultsDone = actions.fetchResultDone(httpResponse);
      actions$ = hot('-a', { a: fetchResultsDone });

      const setTimestampActions = actions.setTimestamp({ id });
      const clearResultsActions = actions.clearResults({ id });
      const setHitsAction = actions.setHits({ id, hits: 1 });
      const addResultAction = actions.addResult({
        id,
        result: [{ orderId: 345 }],
      });

      const expected = cold('-(bcde)', {
        b: setTimestampActions,
        c: clearResultsActions,
        d: setHitsAction,
        e: addResultAction,
      });

      expect(searchEffects.fetchResultDone$).toBeObservable(expected);
    });

    it('should not return an action if the response is not ok', () => {
      const httpResponse: {
        id: number;
        response: StrictHttpResponse<ListResponseArgsOfOrderItemListItemDTO>;
      } = {
        id,
        response: {
          ok: false,
          body: {
            error: false,
            result: [{ orderId: 345 }],
            hits: 1,
            invalidProperties: {},
            skip: 5,
            take: 20,
          },
          type: null,
          clone: null,
          headers: new HttpHeaders(),
          status: 200,
          statusText: 'Okay',
          url: 'someRandomUrl.de',
        },
      };

      const fetchResultsDone = actions.fetchResultDone(httpResponse);
      actions$ = hot('-a', { a: fetchResultsDone });

      const expected = cold('-');
      expect(searchEffects.fetchResultDone$).toBeObservable(expected);
    });
  });
});
