/*
 * Copyright (C) 2019 Robert Bosch GmbH Copyright (C) 2019 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { Injectable } from '@angular/core';
import { EventTypes, OidcSecurityService, PublicEventsService, ValidationResult } from 'angular-auth-oidc-client';
import { fromEvent, merge, Observable, of, switchMap } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
import { AuthenticationService } from '../../services/authentication.service';
import { OauthService } from './oauth.service';

@Injectable()
export class MacmaAuthenticationService extends AuthenticationService {
  public redirectUrl: string;
  private idTokenPayload: any;
  private idToken: any;

  private _loginRequired$ = merge(
    fromEvent(window, 'oidc-code-message').pipe(
      filter((event: CustomEvent) => event.detail.includes('error=login_required'))
    ),
    this.eventsService
      .registerForEvents()
      .pipe(
        filter(
          (notification) =>
            notification.type === EventTypes.NewAuthenticationResult &&
            notification.value?.validationResult === ValidationResult.LoginRequired
        )
      )
  );

  constructor(
    private oidcSecurityService: OidcSecurityService,
    private oauthService: OauthService,
    private eventsService: PublicEventsService
  ) {
    super();
    this.isAuthenticated().pipe(switchMap(() =>
      this.oidcSecurityService.getIdToken()
    )).subscribe(idToken => this.idToken = idToken);

    this.isAuthenticated().pipe(switchMap((isAuthenticated) =>
      isAuthenticated ? this.oidcSecurityService.getPayloadFromIdToken() : of({})
    )).subscribe(payload => this.idTokenPayload = payload);
  }

  /**
   * Return an observable that emits whenever silent token renewal (or inital login) failed and manual login is required.
   * This may either be the case when the single-sign-on session ended or if third party cookies are blocked.
   */
  get loginRequired$(): Observable<any> {
    return this._loginRequired$;
  }

  /**
   * Returns accessToken or undefined observable.
   */
  getAccessToken() {
    return this.oidcSecurityService.getConfigurations().length > 0 ? this.oidcSecurityService.getAccessToken().pipe(map((token) => (token === '' ? null : token))) : of(undefined);
  }

  /**
   * Return object representing payload of id token or null.
   */
  getIdTokenPayload() {
    if (!!this.idToken) {
      return this.idTokenPayload;
    } else {
      return null;
    }
  }

  /**
   * Returns expiration time of accessToken in milliseconds unix or 0.
   */
  getExpiration(): Observable<number> {
    return !!this.getIdTokenPayload() ? of(this.getIdTokenPayload().exp * 1000) : of(0);
  }

  /**
   * Returns name of logged in user or null.
   */
  getUsername() {
    const payloadFromIdToken = this.getIdTokenPayload();
    if (!!payloadFromIdToken) {
      return payloadFromIdToken.name || payloadFromIdToken.preferred_username || payloadFromIdToken.unique_name;
    }
    return null;
  }

  /**
   * Returns realm of logged in user or null.
   */
  getTenantId() {
    const payloadFromIdToken = this.getIdTokenPayload();
    if (!!payloadFromIdToken) {
      return payloadFromIdToken.tid;
    }
    return null;
  }

  getRoles(): Observable<string[]> {
    return this.oidcSecurityService.userData$.pipe(map((data) => ((!!data && !!data.userData) ? data.userData.roles : [])));
  }

  isAuthenticated(): Observable<boolean> {
    return this.oidcSecurityService.isAuthenticated$.pipe(
      map((result) => result.isAuthenticated),
      distinctUntilChanged()
    );
  }

  login() {
    this.oauthService.login(this.oidcSecurityService);
  }

  logout(): Observable<boolean> {
    const logoutObservable = this.oauthService.logoff(this.oidcSecurityService).pipe(
      map(() => true), shareReplay(1));
    logoutObservable.subscribe();
    return logoutObservable;
  }
}
