/*
 * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
import { Environment, ENVIRONMENT_CONFIG } from '@alfa-client/environment-shared';
import { Inject, Injectable } from '@angular/core';
import { AuthConfig, OAuthEvent, OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { UserProfileResource } from 'libs/user-profile-shared/src/lib/user-profile.model';
import { getUserNameInitials } from 'libs/user-profile-shared/src/lib/user-profile.util';
import { filter, Subscription } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
  currentUserResource: UserProfileResource;

  private eventSubscription: Subscription;

  constructor(
    private oAuthService: OAuthService,
    @Inject(ENVIRONMENT_CONFIG) private envConfig: Environment,
  ) {}

  public async login(): Promise<void> {
    this.oAuthService.configure(this.buildConfiguration());
    this.oAuthService.setupAutomaticSilentRefresh();
    this.oAuthService.tokenValidationHandler = new JwksValidationHandler();

    const eventPromise: Promise<void> = this.buildAuthEventPromise();
    await this.oAuthService.loadDiscoveryDocumentAndLogin();
    return eventPromise;
  }

  buildAuthEventPromise(): Promise<void> {
    return new Promise<void>((resolve, reject) => this.handleAuthEventsForPromise(resolve, reject));
  }

  private handleAuthEventsForPromise(resolve: (value: void | PromiseLike<void>) => void, reject: (reason?: any) => void): void {
    this.eventSubscription = this.oAuthService.events
      .pipe(filter((event: OAuthEvent) => this.shouldProceedByAuthEvent(event)))
      .subscribe({
        next: () => {
          this.setCurrentUser();
          this.unsubscribeEvents();
          resolve();
        },
        error: (error: any) => {
          this.unsubscribeEvents();
          reject(error);
        },
      });
  }

  shouldProceedByAuthEvent(event: OAuthEvent): boolean {
    return this.consideredAsLoginEvent(event.type) || this.consideredAsPageReloadEvent(event.type);
  }

  consideredAsLoginEvent(eventType: string): boolean {
    return eventType === 'token_received';
  }

  consideredAsPageReloadEvent(eventType: string): boolean {
    return eventType === 'discovery_document_loaded' && this.hasValidToken();
  }

  hasValidToken(): boolean {
    return this.oAuthService.hasValidAccessToken() && this.oAuthService.hasValidIdToken();
  }

  buildConfiguration(): AuthConfig {
    return {
      issuer: this.envConfig.authServer + '/realms/' + this.envConfig.realm,
      tokenEndpoint: this.envConfig.authServer + '/realms/' + this.envConfig.realm + '/protocol/openid-connect/token',
      redirectUri: window.location.origin + '/',
      clientId: this.envConfig.clientId,
      scope: 'openid profile',
      requireHttps: false,
      responseType: 'code',
      showDebugInformation: false,
    };
  }

  setCurrentUser(): void {
    const claims: Record<string, any> = this.oAuthService.getIdentityClaims();
    const userResource: UserProfileResource = <any>{
      firstName: claims['given_name'],
      lastName: claims['family_name'],
    };
    this.currentUserResource = userResource;
  }

  unsubscribeEvents(): void {
    this.eventSubscription.unsubscribe();
  }

  public getCurrentUserInitials(): string {
    return getUserNameInitials(this.currentUserResource);
  }

  public logout(): void {
    this.oAuthService.revokeTokenAndLogout();
  }
}