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

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

import { SampEffects } from './samp.effects';
import { SampService } from '../services/samp.service';
import * as sampActions from '../actions/samp.actions';

describe('SampEffects', () => {
    let actions = new Observable();
    let effects: SampEffects;
    let metadata: EffectsMetadata<SampEffects>;
    let sampService: SampService;
    let toastr: ToastrService;

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                SampEffects,
                { provide: SampService, useValue: {
                    register: jest.fn(),
                    unregister: jest.fn(),
                    broadcast: jest.fn()
                }},
                { provide: ToastrService, useValue: {
                    success: jest.fn(),
                    error: jest.fn()
                }},
                provideMockActions(() => actions)
            ]
        }).compileComponents();
        effects = TestBed.inject(SampEffects);
        metadata = getEffectsMetadata(effects);
        sampService = TestBed.inject(SampService);
        toastr = TestBed.inject(ToastrService);
    });

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

    describe('register$ effect', () => {
        it('should dispatch the registerSuccess action on success', () => {
            const action = sampActions.register();
            const outcome = sampActions.registerSuccess();

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

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

        it('should dispatch the registerFail action on failure', () => {
            const action = sampActions.register();
            const error = new Error();
            const outcome = sampActions.registerFail();

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

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

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

        it('should display a success notification', () => {
            const spy = jest.spyOn(toastr, 'success');
            const action = sampActions.registerSuccess();

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

            expect(effects.registerSuccess$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
            expect(spy).toHaveBeenCalledWith('You are now connected to a SAMP-hub', 'SAMP-hub register success');
        });
    });

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

        it('should display a error notification', () => {
            const spy = jest.spyOn(toastr, 'error');
            const action = sampActions.registerFail();

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

            expect(effects.registerFail$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
            expect(spy).toHaveBeenCalledWith('Connection to a SAMP-hub has failed', 'SAMP-hub register fail');
        });
    });

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

        it('should call unregister from sampService', () => {
            const spy = jest.spyOn(sampService, 'unregister');
            const action = sampActions.unregister();

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

            expect(effects.unregister$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
        });
    });

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

        it('should call broadcast from sampService', () => {
            const spy = jest.spyOn(sampService, 'broadcast');
            const action = sampActions.broadcastVotable({ url: 'url' });

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

            expect(effects.broadcastVotable$).toBeObservable(expected);
            expect(spy).toHaveBeenCalledTimes(1);
        });
    });
});