import { Box } from './modules/box';
import { SecurityHubClient } from './services/signalr/security-hub-client';
import { BreezeSyncHubClient } from './services/signalr/breeze-sync-hub-client';
import { BindingSignaler } from 'aurelia-templating-resources';
import { UIValidationRenderer } from './ui/forms/ui-validation';
import { UIInternal } from './utils/ui-internal';
import { HttpClient } from 'aurelia-fetch-client';
import { Datacontext } from './data/data-context';
import 'jquery';
import { RouterConfiguration, Router, Redirect, NavigationInstruction, Next } from 'aurelia-router';
import { I18N } from 'aurelia-i18n';
import { inject, autoinject } from 'aurelia-framework';
import { ValidationControllerFactory, ValidationController } from 'aurelia-validation';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
import { Container } from 'aurelia-framework';
import { Config, RouteSettings } from './config';
import { AuthService } from './security/auth-service';
import { EntityManager } from './data/entity-manager';
import { CustomLogger } from './modules/custom-logger';
import { BreezeModel } from './data/breeze-model';
import moment from 'moment';
import { DialogService } from 'aurelia-dialog';

@autoinject
export abstract class BaseApp {
  public router: Router;
  public constants: any;
  public i18n: I18N;
  public isAuthenticated: boolean = false;
  public subscription: Subscription;

  constructor(public context: Datacontext, public config: Config, public eventAggregator: EventAggregator, public breezeModel: BreezeModel,
    public authService: AuthService, public entityManager: EntityManager, public httpClient: HttpClient, public dialogService: DialogService, private signaler: BindingSignaler) {
    this.i18n = Container.instance.get(I18N);
    let controllerFactory = <ValidationControllerFactory>Container.instance.get(ValidationControllerFactory);
    Container.instance.unregister(ValidationController);
    let controller = controllerFactory.createForCurrentScope();
    controller.addRenderer(new UIValidationRenderer());
    authService.addBearerTokenToHttpClient();

    this.subscription = UIInternal.subscribe(UIInternal.EVT_AUTH_CHANGE, async (authState: { authenticated: Boolean }) => {
      if (authState.authenticated) {
        await this.initialize();
        this.authService.navigateToSavedInstruction();
      }
      else {
        this.isAuthenticated = false;
      }
    });

    this.subscribeToSecurityHub();
  }

  protected abstract isValidLanguage(language: string): boolean;

  protected async switchLanguage(language: string) {
    if (this.isValidLanguage(language)) {
      await this.i18n.setLocale(language);
      moment.locale(language);
      this.eventAggregator.publish(UIInternal.EVT_CHANGE_LANGUAGE);
    }
  }

  async loadProfile() {
    await this.authService.getProfile();
    if (this.authService != null && this.authService.currentUser != undefined) {
      const language = this.authService.currentUser.language;
      await this.switchLanguage(language);
    }
  }

  protected disconnect() {
    this.authService.logout();
  }

  public async configureRouter(routerConfig: RouterConfiguration, router: Router) {
    this.router = router;
    this.router.configure(config => config.fallbackRoute('/'));
    routerConfig.title = this.config.globalConfig.applicationTitle;
    routerConfig.options.pushState = true;
    routerConfig.options.root = this.config.routesPrefix || '/';
    this.entityManager.initialize(this.config.globalConfig.breezeControllerUri);

    routerConfig.addAuthorizeStep(AuthorizeStep);
    routerConfig.map(this.config.routes);
    if (await this.authService.isAuthenticated()) {
      await this.initialize();
    }
  }

  protected async initialize() {
    await this.entityManager.initializeMetadataStore((store, manager) => this.breezeModel.configureMetadataStore(store, manager));
    await this.loadProfile();
    await this.context.primeData();
    // Because of cyclic dependencies, the hub cannot be instatiated on the entity-manager class
    this.entityManager.subscribeToBreezeSyncHub(Container.instance.get(BreezeSyncHubClient));

    this.isAuthenticated = true;

    UIInternal.broadcast(UIInternal.EVT_APP_READY);
  }

  private async subscribeToSecurityHub() {
    let hub = Container.instance.get(SecurityHubClient);
    await hub.whenReady();
    let subscription = hub.onUserPermissionsChanged(() => {
      // Avoid multiple warnings at the same time
      subscription.dispose();
      let trPrefix = 'common:role.onUserPermissionsChanged.';
      Container.instance.get(Box).showWarning(this.i18n.tr(`${trPrefix}dialogText`), this.i18n.tr(`${trPrefix}dialogTitle`), [
        {
          label: this.i18n.tr(`${trPrefix}dialogButton`), title: this.i18n.tr(`${trPrefix}dialogButton`),
          theme: 'warning', type: 'solid',
          fn: async () => {
            await this.authService.onUserPermissionsChanged();
            location.reload();
          }
        }
      ])
    });
  }
}

@autoinject
class AuthorizeStep {
  constructor(private authService: AuthService, private logger: CustomLogger, private i18n: I18N, private router: Router, private config: Config) { }
  async run(navigationInstruction: NavigationInstruction, next: Next) {
    if (navigationInstruction.getAllInstructions().some(i => i.config.name == 'ponto-callback')) {
      return next();
    }
    if (navigationInstruction.getAllInstructions().some(i => i.config.name != 'callback' && i.config.name != 'login') && !await this.authService.isAuthenticated()) {
      let url = navigationInstruction.fragment;
      if (navigationInstruction.queryString?.length > 0) {
        url += '?' + navigationInstruction.queryString;
      }
      return next.cancel(new Redirect('login/' + encodeURIComponent(url)));
    }
    else {
      let hasAccess: boolean = this.authService.checkRouteAccess(navigationInstruction.config.settings);
      if (!hasAccess) {
        let defaultAuthorizedNav = this.getDefaultAuthorizedNav();
        if (defaultAuthorizedNav != null && this.config.globalConfig.redirectionIfNotAuthorized) {
          let defaultRoute = typeof defaultAuthorizedNav.config.route == "string" ? defaultAuthorizedNav.config.route : defaultAuthorizedNav.config.route[0];
          return next.cancel(new Redirect(defaultRoute));
        }
        this.logger.LogError(this.i18n.tr("clientExceptions.unauthorized", { ns: "common" }), navigationInstruction.config, 'Router', true);
        return next.cancel();
      }
      return next();
    }
  }

  private getDefaultAuthorizedNav() {
    return this.router.navigation.find(nav => this.authService.checkRouteAccess(nav.config.settings));
  }

}
