import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ALL_LOCAL_STORAGE_KEYS, AUTH_TOKEN_EXPIRE_KEY, AUTH_TOKEN_KEY } from '@msfa-constants/local-storage-keys';
import { environment } from '@msfa-environment';
import { AuthenticationResponse } from '@msfa-models/api/authentication.response.model';
import { Authentication, mapAuthApiResponseToAuthenticationResult } from '@msfa-models/authentication.model';
import { add, isBefore } from 'date-fns';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class AuthenticationService {
  private _token$ = new BehaviorSubject<string>(null);
  private _expiration$ = new BehaviorSubject<number>(null);

  isLoggedIn$: Observable<boolean> = combineLatest([this._token$, this._expiration$]).pipe(
    map(([token, expiration]) => {
      if (token && expiration) {
        return isBefore(new Date(), expiration);
      }

      return false;
    })
  );

  private static _authTokenApiUrl(code: string): string {
    return `${environment.api.url}/auth/token?accessCode=${code}`;
  }

  private _setSession(authenticationResult: Authentication): void {
    const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn });

    this._token$.next(authenticationResult.idToken);
    localStorage.setItem(AUTH_TOKEN_KEY, authenticationResult.idToken);
    this._expiration$.next(expiresAt.valueOf());
    localStorage.setItem(AUTH_TOKEN_EXPIRE_KEY, JSON.stringify(expiresAt.valueOf()));
  }

  login$(authorizationCodeFromCiam: string): Observable<Authentication> {
    this.removeLocalStorageData();

    return this.httpClient
      .get<{ data: AuthenticationResponse }>(AuthenticationService._authTokenApiUrl(authorizationCodeFromCiam))
      .pipe(
        map(({ data }) => mapAuthApiResponseToAuthenticationResult(data)),
        tap(authenticationResult => {
          this._setSession(authenticationResult);
        })
      );
  }

  private _getLocalStorageData(): { id_token: string; expires_at: number } {
    const id_token = localStorage.getItem(AUTH_TOKEN_KEY);
    const expiration = localStorage.getItem(AUTH_TOKEN_EXPIRE_KEY);

    return id_token && expiration
      ? {
          id_token,
          expires_at: +JSON.parse(expiration),
        }
      : null;
  }

  public removeLocalStorageData(): void {
    Object.values(ALL_LOCAL_STORAGE_KEYS).forEach(value => {
      localStorage.removeItem(value);
    });
  }

  get currentAuthorizationToken(): string {
    return this._token$.getValue();
  }

  constructor(private httpClient: HttpClient, private router: Router) {
    const localStorageData = this._getLocalStorageData();

    if (localStorageData) {
      this._token$.next(localStorageData.id_token);
      this._expiration$.next(localStorageData.expires_at);
    }
  }

  logout(): void {
    this.removeLocalStorageData();

    if (environment.environment === 'local') {
      void this.router.navigateByUrl(environment.logoutUrl);
    } else {
      document.location.href = environment.logoutUrl;
    }
  }
}
