import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { OverlayContainer } from '@angular/cdk/overlay';
import { MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ReplaySubject, throwError } from 'rxjs';
import { Store } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import { provideMockActions } from '@ngrx/effects/testing';

import { SharedModule } from '../shared/shared.module';
import { mockActionsData, mockResponseData, mockRTLStoreState } from '../shared/test-helpers/test-data';
import { mockDataService, mockLoggerService, mockSessionService, mockMatDialogRef } from '../shared/test-helpers/mock-services';
import { ThemeOverlay } from '../shared/theme/overlay-container/theme-overlay';
import { CommonService } from '../shared/services/common.service';
import { SessionService } from '../shared/services/session.service';
import { LoggerService } from '../shared/services/logger.service';
import { DataService } from '../shared/services/data.service';
import { WebSocketClientService } from '../shared/services/web-socket.service';
import { ErrorMessageComponent } from '../shared/components/data-modal/error-message/error-message.component';
import { API_END_POINTS, APICallStatusEnum, RTLActions, UI_MESSAGES } from '../shared/services/consts-enums-functions';

import { RTLEffects } from './rtl.effects';
import { RTLState } from './rtl.state';
import { updateRootAPICallStatus, openSpinner, closeSpinner, openAlert, resetRootStore } from './rtl.actions';
import { resetLNDStore, fetchInfoLND, fetchPageSettings as fetchPageSettingsLND } from '../lnd/store/lnd.actions';
import { resetCLNStore } from '../cln/store/cln.actions';
import { resetECLStore } from '../eclair/store/ecl.actions';

describe('RTL Root Effects', () => {
  let actions: ReplaySubject<any>;
  let effects: RTLEffects;
  let mockStore: Store<RTLState>;
  let snackBar: MatSnackBar;
  let container: any;
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;

  beforeEach(() => {
    window.jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
    TestBed.configureTestingModule({
      imports: [
        BrowserAnimationsModule,
        SharedModule,
        RouterTestingModule,
        HttpClientTestingModule
      ],
      providers: [
        RTLEffects, CommonService, HttpClient, WebSocketClientService,
        { provide: SessionService, useClass: mockSessionService },
        { provide: LoggerService, useClass: mockLoggerService },
        { provide: MatDialogRef, useClass: mockMatDialogRef },
        { provide: DataService, useClass: mockDataService },
        { provide: OverlayContainer, useClass: ThemeOverlay },
        provideMockStore({ initialState: mockRTLStoreState, selectors: [] }),
        provideMockActions(() => actions)
      ]
    });
    effects = TestBed.inject(RTLEffects);
    mockStore = TestBed.inject(Store);
    snackBar = TestBed.inject(MatSnackBar);
    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
    container = document.createElement('div');
    container.id = 'rtl-container';
    document.body.appendChild(container);
  });

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

  it('should dispatch set selected node', (done) => {
    const storeDispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough();
    actions = new ReplaySubject(1);
    const setSelectedNodeAction = {
      type: RTLActions.SET_SELECTED_NODE,
      payload: { uiMessage: UI_MESSAGES.UPDATE_SELECTED_NODE, prevLnNodeIndex: -1, currentLnNode: mockActionsData.setSelectedNode, isInitialSetup: false }
    };
    actions.next(setSelectedNodeAction);
    const sub = effects.setSelectedNode.subscribe((setSelectedNodeResponse) => {
      expect(setSelectedNodeResponse).toEqual({ type: RTLActions.VOID });
      expect(storeDispatchSpy.calls.all()[0].args[0]).toEqual(openSpinner({ payload: UI_MESSAGES.UPDATE_SELECTED_NODE }));
      expect(storeDispatchSpy.calls.all()[1].args[0]).toEqual(updateRootAPICallStatus({ payload: { action: 'UpdateSelNode', status: APICallStatusEnum.INITIATED } }));
      expect(storeDispatchSpy.calls.all()[2].args[0]).toEqual(updateRootAPICallStatus({ payload: { action: 'UpdateSelNode', status: APICallStatusEnum.COMPLETED } }));
      expect(storeDispatchSpy.calls.all()[3].args[0]).toEqual(closeSpinner({ payload: UI_MESSAGES.UPDATE_SELECTED_NODE }));
      expect(storeDispatchSpy.calls.all()[4].args[0]).toEqual(resetRootStore({ payload: mockActionsData.resetRootStore }));
      expect(storeDispatchSpy.calls.all()[5].args[0]).toEqual(resetLNDStore({ payload: mockActionsData.resetChildrenStores }));
      expect(storeDispatchSpy.calls.all()[6].args[0]).toEqual(resetCLNStore({ payload: mockActionsData.resetChildrenStores }));
      expect(storeDispatchSpy.calls.all()[7].args[0]).toEqual(resetECLStore({ payload: mockActionsData.resetChildrenStores }));
      expect(storeDispatchSpy.calls.all()[8].args[0]).toEqual(fetchPageSettingsLND());
      expect(storeDispatchSpy.calls.all()[9].args[0]).toEqual(fetchInfoLND({ payload: { loadPage: 'HOME' } }));
      expect(storeDispatchSpy).toHaveBeenCalledTimes(10);
      done();
      setTimeout(() => sub.unsubscribe());
    });
    const req = httpTestingController.expectOne(API_END_POINTS.CONF_API + '/updateSelNode/1/-1');
    const expectedResponse = mockResponseData.setSelectedNodeSuccess;
    req.flush(expectedResponse);
    expect(req.request.method).toEqual('GET');
  });

  it('should throw error on dispatch set selected node', (done) => {
    const storeDispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough();
    const httpClientSpy = spyOn(httpClient, 'get').and.returnValue(throwError(() => mockResponseData.error));
    actions = new ReplaySubject(1);
    const setSelectedNodeAction = {
      type: RTLActions.SET_SELECTED_NODE,
      payload: { uiMessage: UI_MESSAGES.UPDATE_SELECTED_NODE, prevLnNodeIndex: -1, currentLnNode: mockActionsData.setSelectedNode, isInitialSetup: false }
    };
    actions.next(setSelectedNodeAction);
    const sub = effects.setSelectedNode.subscribe((setSelectedNodeResponse: any) => {
      expect(setSelectedNodeResponse).toEqual({ type: RTLActions.VOID });
      expect(storeDispatchSpy.calls.all()[0].args[0]).toEqual(openSpinner({ payload: UI_MESSAGES.UPDATE_SELECTED_NODE }));
      expect(storeDispatchSpy.calls.all()[1].args[0]).toEqual(updateRootAPICallStatus({ payload: { action: 'UpdateSelNode', status: APICallStatusEnum.INITIATED } }));
      expect(storeDispatchSpy.calls.all()[2].args[0]).toEqual(closeSpinner({ payload: UI_MESSAGES.UPDATE_SELECTED_NODE }));
      expect(storeDispatchSpy.calls.all()[3].args[0]).toEqual(openAlert({ payload: { data: { type: 'ERROR', alertTitle: 'Update Selected Node Failed!', message: { code: '500', message: 'Request Failed. ', URL: API_END_POINTS.CONF_API + '/updateSelNode' }, component: ErrorMessageComponent } } }));
      expect(storeDispatchSpy.calls.all()[4].args[0]).toEqual(updateRootAPICallStatus({ payload: { action: 'UpdateSelNode', status: APICallStatusEnum.ERROR, statusCode: '500', message: 'Request Failed. ', URL: API_END_POINTS.CONF_API + '/updateSelNode' } }));
      expect(storeDispatchSpy).toHaveBeenCalledTimes(5);
      done();
      setTimeout(() => sub.unsubscribe());
    });
  });

  it('should open snack bar', (done) => {
    const snackBarOpenSpy = spyOn(snackBar, 'open').and.callThrough();
    actions = new ReplaySubject(1);
    const openSnackBarAction = {
      type: RTLActions.OPEN_SNACK_BAR,
      payload: 'Testing the snackbar open effect...'
    };
    actions.next(openSnackBarAction);
    const sub = effects.openSnackBar.subscribe((openSnackBarResponse) => {
      expect(openSnackBarResponse).toBeUndefined();
      expect(snackBarOpenSpy).toHaveBeenCalledWith(openSnackBarAction.payload);
      expect(snackBarOpenSpy).toHaveBeenCalledTimes(1);
      done();
      setTimeout(() => sub.unsubscribe());
    });
  });

  afterEach(() => {
    httpTestingController.verify();
  });
});
