import { AuthConfig } from './../config';
import { HttpClient } from 'aurelia-fetch-client';
import { Aurelia, inject } from "aurelia-framework";
import { Router } from "aurelia-router";
import { Datacontext } from '../data/data-context';
import { Config } from '../config';
import { UIInternal } from '../utils/ui-internal';
import { AuthService } from './auth-service';

export abstract class OAuthClientWrapper {
  abstract accessToken(): Promise<string>
  abstract handleSignInRedirect(): Promise<void>;
  abstract isAuthenticated(): Promise<boolean>;
  abstract logout(): Promise<void>;
  abstract getUserProfile(): Promise<any>;

  constructor(protected config: AuthConfig) { }

  public login() {
    if (sessionStorage.getItem('location') == null) {
      let url = location.pathname;
      if (false == url.startsWith('/login/')) {
        sessionStorage.setItem('location', JSON.stringify({ url }));
      }
    }
    this.loginWithRedirect();
  }

  protected abstract loginWithRedirect(): Promise<void>;
}

const sleep = async (waitTime: number): Promise<void> => new Promise(resolve => setTimeout(resolve, waitTime));

@inject(Router, Aurelia, Datacontext, Config, HttpClient)
export abstract class BaseOAuthService<T extends OAuthClientWrapper> extends AuthService {

  protected client: T;

  constructor(protected router: Router, protected app: Aurelia, protected dataContext: Datacontext,
    config: Config, protected httpClient: HttpClient) {
    super(config);
  }

  public addBearerTokenToHttpClient() {
    let requestFn = async (message: Request) => {
      try {
        if (this.requestRequiresAuth(message)) {
          const maxSleepTime = 1000;
          const sleepInterval = 100;
          let totalSleepTime = 0;
          while (false == await this.isAuthenticated()) {
            if (totalSleepTime >= maxSleepTime) {
              console.error(`After waiting ${maxSleepTime}ms user is still not authenticated, sending request without bearer token.`);
              return message;
            }
            await sleep(sleepInterval);
            totalSleepTime += sleepInterval;
          }
          if (totalSleepTime > 0) {
            console.warn(`HTTP request slept for ${totalSleepTime}ms before isAuthenticated flag was true.`)
          }
          message.headers.append('Authorization', `Bearer ${await this.accessToken()}`);
          return message;
        }
        return message;
      }
      catch (error) {
        console.log(error);
      }
    }
    this.httpClient.configure(config => {
      config
        .withInterceptor({
          request: requestFn
        });
    });
  }

  public async accessToken() {
    return await (await this.getClient()).accessToken();
  }

  protected async getClient(): Promise<T> {
    if (!this.client) {
      this.client = await this.makeClient();
    }
    return this.client;
  }

  protected abstract makeClient(): Promise<T>;

  public async login(navInstruction: string) {
    sessionStorage.setItem('location', JSON.stringify({ url: navInstruction }));
    (await this.getClient()).login();
  }

  public navigateToSavedInstruction() {
    const savedLocation = JSON.parse(sessionStorage.getItem('location'));
    const navigateTarget = savedLocation?.url || 'watchlist';

    sessionStorage.removeItem('location');

    this.router.navigate(navigateTarget);
  }

  public async handleAuthentication() {
    try {
      await (await this.getClient()).handleSignInRedirect();
      UIInternal.broadcast(UIInternal.EVT_AUTH_CHANGE, { authenticated: true });
    } catch (err) {
      if (err.error_description == 'user is blocked') {
        alert("Ce compte utilisateur est désactivé");
      } else {
        alert("Ce compte utilisateur n'a pas été validé. Veuillez cliquez sur le lien que vous avec reçu dans l'email de bienvenue que vous avez reçu");
      }
      throw err;
    }
  }

  public async isAuthenticated() {
    return await (await this.getClient()).isAuthenticated();
  }

  public async logout() {
    await (await this.getClient()).logout();
  }

  public async getProfile() {
    this.currentUser = await this.dataContext.getCurrentUserProfile();

    if (this.currentUser == null) {
      await this.logout();
    } else {
      this.authProfile = await (await this.getClient()).getUserProfile();
      this.currentUser.avatarUri = this.authProfile?.picture;
    }
  }

  public checkAccess(resource: string, action: string) {
    if (resource == null || action == null) return true;
    if (this.currentUser == null) return false;
    let scope = `${action}:${resource.toLocaleLowerCase()}`;
    return this.currentUser.permissions?.indexOf(scope) >= 0;
  }
}