import { TestBed } from '@angular/core/testing';

import { provideMockActions } from '@ngrx/effects/testing';
import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { Observable } from 'rxjs';
import { cold, hot } from 'jasmine-marbles';
import { ToastrService } from 'ngx-toastr';

import { DetailEffects } from './detail.effects';
import { DetailService } from '../services/detail.service';
import * as detailActions from '../actions/detail.actions';
import * as detailSelector from '../selectors/detail.selector';
import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector';
import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
import * as fromDetail from '../reducers/detail.reducer';
import * as fromMetamodel from '../../../metamodel/metamodel.reducer';

describe('DetailEffects', () => {
    let actions = new Observable();
    let effects: DetailEffects;
    let metadata: EffectsMetadata<DetailEffects>;
    let detailService: DetailService;
    let store: MockStore;
    let toastr: ToastrService;
    let mockDatasetSelectorSelectDatasetNameByRoute;
    let mockAttributeSelectorSelectAllAttributes;
    let mockDetailSelectorSelectIdByRoute;
    const initialState = {
        metamodel: { ...fromMetamodel.getMetamodelState },
        instance: {
            detail: { ...fromDetail.initialState }
        }
    };

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                DetailEffects,
                { provide: DetailService, useValue: {
                    retrieveObject: jest.fn(),
                    retrieveSpectra: jest.fn()
                }},
                { provide: ToastrService, useValue: { error: jest.fn() }},
                provideMockActions(() => actions),
                provideMockStore({ initialState }),
            ]
        }).compileComponents();
        effects = TestBed.inject(DetailEffects);
        metadata = getEffectsMetadata(effects);
        detailService = TestBed.inject(DetailService);
        store = TestBed.inject(MockStore);
        toastr = TestBed.inject(ToastrService);
        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
            datasetSelector.selectDatasetNameByRoute,''
        );
        mockAttributeSelectorSelectAllAttributes = store.overrideSelector(
            attributeSelector.selectAllAttributes,[]
        );
        mockDetailSelectorSelectIdByRoute = store.overrideSelector(
            detailSelector.selectIdByRoute,1
        );
    });

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

    describe('retrieveObject$ effect', () => {
        it('should dispatch the retrieveObjectSuccess action on success', () => {
            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
                datasetSelector.selectDatasetNameByRoute, 'myDatasetName'
            );
            mockAttributeSelectorSelectAllAttributes = store.overrideSelector(
                attributeSelector.selectAllAttributes, [{
                    id: 1,
                    name: 'myFirstAttribute',
                    label: 'My First Attribute',
                    form_label: 'My First Attribute',
                    output_display: 1,
                    criteria_display: 1,
                    type: 'type',
                    display_detail: 1,
                    order_by: true,
                    detail: true
                }]
            );
            mockDetailSelectorSelectIdByRoute = store.overrideSelector(
                detailSelector.selectIdByRoute, 1
            );

            const action = detailActions.retrieveObject();
            const outcome = detailActions.retrieveObjectSuccess({ object: 'myObject' });

            actions = hot('-a', { a: action });
            const response = cold('-b|', { b: ['myObject'] });
            const expected = cold('--c', { c: outcome });
            detailService.retrieveObject = jest.fn(() => response);

            expect(effects.retrieveObject$).toBeObservable(expected);
        });

        it('should dispatch the retrieveObjectFail action on failure', () => {
            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
                datasetSelector.selectDatasetNameByRoute, 'myDatasetName'
            );
            mockAttributeSelectorSelectAllAttributes = store.overrideSelector(
                attributeSelector.selectAllAttributes, [{
                    id: 1,
                    name: 'myFirstAttribute',
                    label: 'My First Attribute',
                    form_label: 'My First Attribute',
                    output_display: 1,
                    criteria_display: 1,
                    type: 'type',
                    display_detail: 1,
                    order_by: true,
                    detail: true
                }]
            );
            mockDetailSelectorSelectIdByRoute = store.overrideSelector(
                detailSelector.selectIdByRoute, 1
            );

            const action = detailActions.retrieveObject();
            const error = new Error();
            const outcome = detailActions.retrieveObjectFail();

            actions = hot('-a', { a: action });
            const response = cold('-#|', {}, error);
            const expected = cold('--b', { b: outcome });
            detailService.retrieveObject = jest.fn(() => response);

            expect(effects.retrieveObject$).toBeObservable(expected);
        });
    });

    describe('retrieveObjectFail$ effect', () => {
        it('should not dispatch', () => {
            expect(metadata.retrieveObjectFail$).toEqual(
                expect.objectContaining({ dispatch: false })
            );
        });

        it('should display an error notification', () => {
            const spy = jest.spyOn(toastr, 'error');
            const action = detailActions.retrieveObjectFail();

            actions = hot('a', { a: action });
            const expected = cold('a', { a: action });

            expect(effects.retrieveObjectFail$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
            expect(spy).toHaveBeenCalledWith('Loading Failed!', 'Unable to load the object');
        });
    });

    describe('retrieveSpectra$ effect', () => {
        it('should dispatch the retrieveSpectraSuccess action on success', () => {
            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
                datasetSelector.selectDatasetNameByRoute, 'myDatasetName'
            );

            const action = detailActions.retrieveSpectra({ filename: 'mySpectraFilename' });
            const outcome = detailActions.retrieveSpectraSuccess({ spectraCSV: 'mySpectraFile' });

            actions = hot('-a', { a: action });
            const response = cold('-b|', { b: 'mySpectraFile' });
            const expected = cold('--c', { c: outcome });
            detailService.retrieveSpectra = jest.fn(() => response);

            expect(effects.retrieveSpectra$).toBeObservable(expected);
        });

        it('should dispatch the retrieveSpectraFail action on failure', () => {
            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
                datasetSelector.selectDatasetNameByRoute, 'myDatasetName'
            );

            const action = detailActions.retrieveSpectra({ filename: 'mySpectraFilename' });
            const error = new Error();
            const outcome = detailActions.retrieveSpectraFail();

            actions = hot('-a', { a: action });
            const response = cold('-#|', {}, error);
            const expected = cold('--b', { b: outcome });
            detailService.retrieveSpectra = jest.fn(() => response);

            expect(effects.retrieveSpectra$).toBeObservable(expected);
        });
    });

    describe('retrieveSpectraFail$ effect', () => {
        it('should not dispatch', () => {
            expect(metadata.retrieveSpectraFail$).toEqual(
                expect.objectContaining({ dispatch: false })
            );
        });

        it('should display an error notification', () => {
            const spy = jest.spyOn(toastr, 'error');
            const action = detailActions.retrieveSpectraFail();

            actions = hot('a', { a: action });
            const expected = cold('a', { a: action });

            expect(effects.retrieveSpectraFail$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
            expect(spy).toHaveBeenCalledWith('Loading Failed!', 'Unable to load spectra');
        });
    });
});