import { AppComponent } from './app.component';
import { CONFIGURATION_CONTEXT, LocalStorageService, MediaQueryService, WindowService } from '@armor/platform-browser';
import { LocaleService, NavStateService } from '@armor/brandkit';
import { LoginService, IdentityService, ApiService, LoginRedirectService } from '@armor/api';
import { TEST_UTILITIES } from '@armor/testing';
import { NavigationEnd } from '@angular/router';
import { HttpClient, HttpClientModule } from '@angular/common/http';

import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('AppComponent', () => {

  let component: AppComponent;
  let loginService: LoginService;
  let localeService: LocaleService;
  let navStateService: NavStateService;
  let httpClient: HttpClient;
  let identityService: IdentityService;
  let windowService: WindowService;
  let router: any;
  let localStorageService: LocalStorageService;

  let mockWindow: any;

  const initComponent = () => {
    component = new AppComponent(
      httpClient,
      identityService,
      localeService,
      loginService,
      localStorageService,
      navStateService,
      router,
      windowService
    );
  };

  beforeEach(() => {
    TEST_UTILITIES.initTestBed([
      HttpClientModule,
      HttpClientTestingModule
    ], [
      ApiService,
      IdentityService,
      LocalStorageService,
      LocaleService,
      LoginService,
      LoginRedirectService,
      NavStateService,
      MediaQueryService,
      WindowService
    ]);

    mockWindow = {
      ga: jasmine.createSpy(),
      scrollTo: jasmine.createSpy()
    };

    windowService = new WindowService();
    spyOn(windowService, 'getWindowReference').and.returnValue(mockWindow);

    httpClient = TEST_UTILITIES.getInjectable(HttpClient);

    navStateService = TEST_UTILITIES.getInjectable(NavStateService);
    spyOn(navStateService.navStateChanged$, 'subscribe').and.callFake((fn) => {
      fn('test_state');
    });

    localeService = TEST_UTILITIES.getInjectable(LocaleService);
    spyOn(localeService, 'init');

    loginService = TEST_UTILITIES.getInjectable(LoginService);
    spyOn(loginService, 'handleAuthorizationCode').and.callFake((fn) => {
      fn();
    });
    spyOn(loginService, 'logout');

    identityService = TEST_UTILITIES.getInjectable(IdentityService);

    localStorageService = new LocalStorageService();

    router = {
      navigateByUrl: jasmine.createSpy(),
      events: {},
      url: {
        replace: jasmine.createSpy('replace')
      }
    };

    router.events.distinctUntilChanged = jasmine.createSpy().and.returnValue(router.events);
    router.events.subscribe = jasmine.createSpy();
  });

  describe('constructor()', () => {
    const setupRouter = (previousUrl: string, currentUrl: string, expected: boolean, typed: boolean = true) => {

      const currentNavigaitonEnd = new NavigationEnd(0, currentUrl, currentUrl);
      const currentOtherType: object = {url: currentUrl};
      const previous: any = {url: previousUrl};

      router.events.distinctUntilChanged = jasmine.createSpy().and.callFake((fn) => {
        const result = fn(previous, (typed)
          ? currentNavigaitonEnd
          : currentOtherType);

        expect(result).toEqual(expected);
        return router.events;
      });

      router.events.subscribe = jasmine.createSpy().and.callFake((fn) => {
        fn({url: currentUrl});
      });
    };

    it('should handle authorization_code parameters in the query string', () => {
      initComponent();
      expect(loginService.handleAuthorizationCode).toHaveBeenCalledTimes(1);

      // TODO: when full story user registration is added, validate that it was called.
      expect(loginService.handleAuthorizationCode).toHaveBeenCalledWith(jasmine.any(Function));
    });

    it('should call RequestReissue at an interval of 100000ms', () => {
      jasmine.clock().install();
      initComponent();
      spyOn(component, 'requestReissue');
      expect(component.requestReissue).not.toHaveBeenCalled();
      jasmine.clock().tick(100001);
      expect(component.requestReissue).toHaveBeenCalledTimes(1);
    });

    it('should initialize the user locale preference settings', () => {
      initComponent();
      expect(localeService.init).toHaveBeenCalledTimes(1);
    });

    it('should set the nav state property from the NavStateService', () => {
      navStateService.state = 'test_state';
      initComponent();
      expect(component.navState).toEqual('test_state');
    });

    it('should subscribe to navStateChanged$ events', () => {
      spyOn(AppComponent.prototype, 'onNavStateChanged');
      initComponent();
      expect(component.onNavStateChanged).toHaveBeenCalledTimes(1);
      expect(component.onNavStateChanged).toHaveBeenCalledWith('test_state');
    });

    it('should filter distinct navigation events, returning true when current and previous URLs match', () => {
      setupRouter('http://test/testing', 'http://test/testing', true);
      initComponent();
    });

    it('should filter distinct navigation events, returning false when current and previous URLs do not match', () => {
      setupRouter('http://test/testing', 'http://test/distinct', false);
      initComponent();
    });

    it('should filter distinct navigation events, returning true when a non-NavigationEnd event is received', () => {
      setupRouter('http://test/testing', 'http://test/distinct', true, false);
      initComponent();
    });
  });

  describe('onNavStateChanged()', () => {
    it('should set the nav state to the parameter value', () => {
      initComponent();
      component.onNavStateChanged('test_state');
      expect(component.navState).toEqual('test_state');
    });
  });

  describe('settings()', () => {
    it('should navigate to the user settings page', () => {
      spyOn(identityService, 'onIdentityReady').and.callFake((fn) => {
        fn({
          user: {
            id: 1
          }
        });
      });

      initComponent();
      component.settings();
      expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
      expect(router.navigateByUrl).toHaveBeenCalledWith('/account/users/1');
    });
  });

  describe('logout()', () => {
    it('should call logout() on the LoginService', () => {
      initComponent();
      component.logout();
      expect(loginService.logout).toHaveBeenCalledTimes(1);
    });
  });

  describe('requestReissue()', () => {
    it('should POST to auth/token/reissue and subscribe', () => {

      spyOn(localStorageService, 'get').and.returnValue('test');
      spyOn(localStorageService, 'set');

      const mockObservable = {
        subscribe: jasmine.createSpy().and.callFake((fn) => {
          fn({access_token: 'testToken'});
          expect(localStorageService.set).toHaveBeenCalledTimes(1);
          expect(localStorageService.set).toHaveBeenCalledWith('auth-token', 'testToken');
        })
      };

      spyOn(httpClient, 'post').and.callFake((url, body, headers) => {
        expect(url).toEqual(`${CONFIGURATION_CONTEXT.instance.endpoints.api}auth/token/reissue`);
        expect(JSON.stringify(body)).toEqual('{"token":"test"}');
        expect(JSON.stringify(headers)).toEqual('{"headers":{"Authorization":"FH-AUTH test","Content-Type":"application/json"}}');

        return mockObservable;
      });
      initComponent();
      component.requestReissue();
    });
  });
});
