import { Router } from 'aurelia-router';
import { HttpClient } from 'aurelia-fetch-client';
import * as msal from "@azure/msal-browser";
import { autoinject } from 'aurelia-framework';
import { AuthService } from './auth-service';
import { Datacontext } from '../data/data-context';
import { Config } from '../config';
import { UIInternal } from '../utils/ui-internal';

@autoinject
export class B2CAuthService extends AuthService {
  msalConfig: msal.Configuration = {
    auth: {
      clientId: this.config.b2cConfig.clientId,
      redirectUri: this.config.b2cConfig.redirectUri,
      authority: this.config.b2cConfig.authorities.signin, // Choose sign-up/sign-in user-flow as your default.
      knownAuthorities: [this.config.b2cConfig.authorityDomain], // You must identify your tenant's domain as a known authority.      
      navigateToLoginRequestUrl: false
    },
    cache: {
      cacheLocation: "localStorage",
      storeAuthStateInCookie: false,
    },
    system: {
      loggerOptions: {
        loggerCallback: (level, message, containsPii) => {
          if (containsPii) {
            return;
          }
          switch (level) {
            case msal.LogLevel.Error:
              console.error(message);
              return;
            case msal.LogLevel.Info:
              console.info(message);
              return;
            case msal.LogLevel.Verbose:
              console.debug(message);
              return;
            case msal.LogLevel.Warning:
              console.warn(message);
              return;
          }
        }
      }
    }

  };

  tokenRequest = {
    scopes: [...this.config.b2cConfig.scopes],  // e.g. ["https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read"]
    forceRefresh: true // Set this to "true" to skip a cached token and go to the server to get a new token
  };

  loginRequest = {
    scopes: ["openid", ...this.config.b2cConfig.scopes],
    prompt: 'consent'
  };
  msalInstance: msal.PublicClientApplication;
  token: any;
  constructor(private dataContext: Datacontext, config: Config, private httpClient: HttpClient, private router: Router) {
    super(config);
    this.msalInstance = new msal.PublicClientApplication(this.msalConfig);
  }

  get usernameType(): "email" | "username" {
    return "email";
  }

  public addBearerTokenToHttpClient() {
    let requestFn = async (message: Request) => {
      try {
        if (this.requestRequiresAuth(message)) {
          if (this.isAuthenticated()) {
            let token = await this.accessToken();
            console.log('append token to header : ' + token);
            message.headers.append('Authorization', `Bearer ${token}`);
          }
          return message;
        }
        return message;
      }
      catch (error) {
        console.log(error);
      }
    }
    this.httpClient.configure(config => {
      config
        .withInterceptor({
          request: requestFn
        });
    });


  }

  async accessToken() {
    if (!this.token) {
      await this.getTokenRedirect(this.tokenRequest);
    }
    return this.token;
  }

  async getTokenRedirect(request): Promise<boolean> {

    /**
    * See here for more info on account retrieval: 
    * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
    */
    request.account = this.msalInstance.getActiveAccount();
    try {
      const response = await this.msalInstance.acquireTokenSilent(request);
      if (!response.accessToken || response.accessToken === "") {
        throw new msal.InteractionRequiredAuthError;
      } else {
        //console.log("access_token acquired at: " + new Date().toString());
        this.token = response.accessToken;
        return true;
      }
    }
    catch (error) {
      console.log("Silent token acquisition fails. Acquiring token using popup. \n", error);
      if (error instanceof msal.InteractionRequiredAuthError) {
        // fallback to interaction when silent call fails
        await this.msalInstance.acquireTokenRedirect(request).catch((redirectError) => {
          console.error(redirectError);
          this.msalInstance.logoutRedirect({
            postLogoutRedirectUri: 'https://localhost:5001/logout'
          })

          return false;
        });
        return false;

      } else {
        console.error(error);
        return false;
      }
    }
  }

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

    sessionStorage.removeItem('location');

    this.router.navigate(navigateTarget);

  }

  async login(navInstruction: string) {
    var response = await this.msalInstance.handleRedirectPromise();
    if (response == null) {
      console.log('login : no user authenticated, call loginRedirect')
      sessionStorage.setItem('location', JSON.stringify({ url: navInstruction }));
      this.msalInstance.loginRedirect(this.loginRequest);
    }
    else {
      console.log('login : a user is already authenticated');
      console.log(response);
      await this.handleResponse(response);
    }
  }

  async logout() {
    await this.msalInstance.logoutRedirect();
  }
  equalsIgnoreOrder(a, b) {
    if (a.length !== b.length) return false;

    const uniqueValues = new Array(...a, ...b);

    for (const v of uniqueValues) {
      const aCount = a.filter(e => e === v).length;
      const bCount = b.filter(e => e === v).length;
      if (aCount !== bCount) return false;
    }
    return true;
  }
  async handleResponse(response) {
    if (response !== null) {
      let policyIdClaimName;
      if (Object.keys(response.idTokenClaims).indexOf('tfp') > -1) {
        policyIdClaimName = 'tfp';
      }
      if (policyIdClaimName == null && Object.keys(response.idTokenClaims).indexOf('acr') > -1) {
        policyIdClaimName = 'acr';
      }
      if (policyIdClaimName == null) {
        throw Error('Cannot detect tfp or acr claim in authentication response');
      }
      if (response.idTokenClaims[policyIdClaimName].toUpperCase() === this.config.b2cConfig.names.signUpSignIn.toUpperCase()) {
        if (!this.equalsIgnoreOrder(response.scopes, this.tokenRequest.scopes)) {
          alert('Access refused!');
          this.msalInstance.logoutRedirect();
        }
        this.msalInstance.setActiveAccount(response.account);

        this.token = response.hasOwnProperty('accessToken') ? response.accessToken : await this.accessToken();
        if (this.token?.length > 0) {
          UIInternal.broadcast(UIInternal.EVT_AUTH_CHANGE, { authenticated: true });
        }
      }
    } else {
      //selectAccount();
    }
  }
  async handleAuthentication() {
    console.warn('handleAuthentication');
    try {

      var response = await this.msalInstance.handleRedirectPromise();
      await this.handleResponse(response);
    }
    catch (error) {
      console.error(error);
    }
  }

  async isAuthenticated() {
    if (this.msalInstance.getActiveAccount() == null) {
      console.log('isAuthenticated : return false');
      return false;
    }
    else {
      const ret = this.token?.length > 0
      console.log(`isAuthenticated : return ${ret}`);
      return ret;
    }
  }

  checkAccess(resource: string, action: string) {
    return true;
  }

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

  public async onUserPermissionsChanged(): Promise<void> {

  }
}
