import {ApiUsersService} from './business-logic/api-services/users.service';
import {selectCurrentUser, selectActiveWorkspace} from './webapp-common/core/reducers/users-reducer';
import {Component, OnDestroy, OnInit, ViewEncapsulation, HostListener, Renderer2, Injector} from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router, Params, RouterEvent} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {selectLoggedOut} from './webapp-common/core/reducers/view-reducer';
import {Store} from '@ngrx/store';
import {get} from 'lodash/fp';
import {selectRouterParams, selectRouterUrl} from './webapp-common/core/reducers/router-reducer';
import {ApiProjectsService} from './business-logic/api-services/projects.service';
import {Project} from './business-logic/model/projects/project';
import {GetAllProjects, SetSelectedProjectId, UpdateProject} from './webapp-common/core/actions/projects.actions';
import {selectSelectedProject} from './webapp-common/core/reducers/projects.reducer';
import {
  selectS3BucketCredentialsBucketCredentials,
  selectS3PopUpDetails,
  selectShowLocalFilesPopUp,
  selectShowS3PopUp
} from './webapp-common/core/reducers/common-auth-reducer';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {S3AccessResolverComponent} from './webapp-common/layout/s3-access-resolver/s3-access-resolver.component';
import {cancelS3Credentials, getTutorialBucketCredentials} from './webapp-common/core/actions/common-auth.actions';
import {termsOfUseAccepted} from './webapp-common/core/actions/users.actions';
import {debounceTime, distinctUntilChanged, filter, map, tap, withLatestFrom} from 'rxjs/operators';
import * as routerActions from './webapp-common/core/actions/router.actions';
import {combineLatest, Observable, Subscription} from 'rxjs';
import {selectBreadcrumbsStrings} from './webapp-common/layout/layout.reducer';
import {prepareNames} from './layout/breadcrumbs/breadcrumbs.utils';
import {formatStaticCrumb} from './webapp-common/layout/breadcrumbs/breadcrumbs-common.utils';
import {ServerUpdatesService} from './webapp-common/shared/services/server-updates.service';
import {selectAvailableUpdates, selectShowSurvey} from './core/reducers/view-reducer';
import {UPDATE_SERVER_PATH} from './app.constants';
import {firstLogin, plotlyReady, setScaleFactor, VisibilityChanged} from './webapp-common/core/actions/layout.actions';
import {UiUpdatesService} from './webapp-common/shared/services/ui-updates.service';
import {UsageStatsService} from './core/Services/usage-stats.service';
import {dismissSurvey} from './core/Actions/layout.actions';
import {getScaleFactor} from './webapp-common/shared/utils/shared-utils';
import {User} from './business-logic/model/users/user';
import {ConfigurationService} from './webapp-common/shared/services/configuration.service';
import {GoogleTagManagerService} from 'angular-google-tag-manager';
import {selectIsSharedAndNotOwner} from './features/experiments/reducers';
import {TipsService} from './webapp-common/shared/services/tips.service';
import {USER_PREFERENCES_KEY} from '@common/user-preferences';

@Component({
  selector: 'sm-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class AppComponent implements OnInit, OnDestroy {
  public loggedOut$: Observable<any>;
  public isDashboardContext: boolean;
  public activeFeature: string;
  private urlSubscription: Subscription;
  public selectedProject$: Observable<Project>;
  public projectId: string;
  public isWorkersContext: boolean;
  public updatesAvailable$: Observable<any>;
  private selectedProjectFromUrl$: Observable<string>;
  private showS3Popup$: Observable<any>;
  private s3Dialog: MatDialogRef<S3AccessResolverComponent, any>;
  private showLocalFilePopup$: Observable<any>;
  private breadcrumbsSubscription: Subscription;
  private selectedCurrentUserSubscription: Subscription;
  private breadcrumbsStrings;
  private selectedCurrentUser$: Observable<any>;
  public showNotification: boolean = true;
  public showSurvey$: Observable<boolean>;
  public demo = ConfigurationService.globalEnvironment.demo;
  public isLoginContext: boolean;
  public currentUser: User;
  private gtmService;
  public isSharedAndNotOwner$: Observable<boolean>;
  private activeWorkspace: string;
  public hideUpdate: boolean;
  public showSurvey: boolean;
  private plotlyURL: string;

  @HostListener('document:visibilitychange') onVisibilityChange() {
    this.store.dispatch(new VisibilityChanged(!document.hidden));
  }

  @HostListener('window:beforeunload', ['$event'])
  beforeunloadHandler() {
    window.localStorage.setItem('lastWorkspace', this.activeWorkspace);
  }

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private titleService: Title,
    private store: Store<any>,
    private projectsApi: ApiProjectsService,
    private userService: ApiUsersService,
    public serverUpdatesService: ServerUpdatesService,
    private uiUpdatesService: UiUpdatesService,
    private tipsService: TipsService,
    private matDialog: MatDialog,
    private userStats: UsageStatsService,
    private renderer: Renderer2,
    private injector: Injector,
    private configService: ConfigurationService
  ) {
    this.showS3Popup$ = this.store.select(selectShowS3PopUp);
    this.showLocalFilePopup$ = this.store.select(selectShowLocalFilesPopUp);
    this.loggedOut$ = store.select(selectLoggedOut);
    this.isSharedAndNotOwner$ = this.store.select(selectIsSharedAndNotOwner);
    this.selectedProject$ = this.store.select(selectSelectedProject);
    this.updatesAvailable$ = this.store.select(selectAvailableUpdates);
    this.showSurvey$ = combineLatest([this.store.select(selectShowSurvey), this.store.select(selectCurrentUser)])
      .pipe(
        debounceTime(0),
        filter(([, user]) => !!user),
        map(([show]) => {
          if (show) {
            let loginTime = parseInt(localStorage.getItem(USER_PREFERENCES_KEY.firstLogin), 10);
            if(!loginTime) {
              this.store.dispatch(firstLogin({first: true}));
              loginTime = Date.now();
              localStorage.setItem(USER_PREFERENCES_KEY.firstLogin, `${loginTime}`);
              return false;
            }
            return Date.now() - loginTime > (14 * 24 * 60 * 60 * 1000); // 2 weeks in milliseconds
          }
          return false;
        })
      );
    this.selectedCurrentUser$ = this.store.select(selectCurrentUser);
    this.selectedProjectFromUrl$ = this.store.select(selectRouterParams)
      .pipe(
        filter((params: Params) => !!params),
        map(params => get('projectId', params) || null)
      );

    if (ConfigurationService.globalEnvironment.GTM_ID) {
      this.gtmService = injector.get(GoogleTagManagerService);
    }
  }

  ngOnInit(): void {
    this.configService.globalEnvironmentObservable.subscribe(env => {
      this.hideUpdate = env.hideUpdateNotice;
      this.showSurvey = env.showSurvey;
      this.plotlyURL = env.plotlyURL;
    });
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(
        (item: RouterEvent) => {
          const gtmTag = {
            event: 'page',
            pageName: item.url
          };
          this.gtmService?.pushTag(gtmTag);
          this.store.dispatch(new routerActions.NavigationEnd());
          this.updateTitle();
        });

    this.selectedCurrentUserSubscription = this.selectedCurrentUser$.pipe(
      tap(user => this.currentUser = user), // should not be filtered
      filter(user => !!user?.id),
      distinctUntilChanged((prev, next) => prev?.id === next?.id)
    )
    .subscribe(() => {
        this.store.dispatch(new GetAllProjects());
        this.store.dispatch(getTutorialBucketCredentials());
        this.store.dispatch(termsOfUseAccepted());
        this.uiUpdatesService.checkForUiUpdate();
        this.tipsService.initTipsService();
        this.serverUpdatesService.checkForUpdates(UPDATE_SERVER_PATH);
      });

    this.selectedProjectFromUrl$.subscribe((projectId: string) => {
      this.store.dispatch(new SetSelectedProjectId(projectId));
    });

    this.showS3Popup$.subscribe(showS3 => {
      if (showS3) {
        this.s3Dialog = this.matDialog.open(S3AccessResolverComponent);
        this.s3Dialog.afterClosed().pipe(
          withLatestFrom(
            this.store.select(selectS3BucketCredentialsBucketCredentials), this.store.select(selectS3PopUpDetails))
        )
          .subscribe(([data, bucketCredentials, popupDetails]) => {
            if (!(data && data.success)) {
              const emptyCredentials = bucketCredentials.find((cred => cred.Bucket === popupDetails.credentials.Bucket)) === undefined;
              const dontAskAgainForBucketName = emptyCredentials ? '' : popupDetails.credentials.Bucket + popupDetails.credentials.Endpoint;
              this.store.dispatch(cancelS3Credentials({dontAskAgainForBucketName}));
            }
          });
      }
    });

    this.store.select(selectActiveWorkspace).pipe(filter(ws => !!ws))
      .subscribe(workspace => this.activeWorkspace = workspace?.id);

    this.urlSubscription = combineLatest([this.store.select(selectRouterUrl), this.store.select(selectRouterParams)])
      .subscribe(([url, params]) => {
        this.projectId = get('projectId', params);
        this.isDashboardContext = url && url.includes('dashboard');
        this.isLoginContext = url && url.includes('login');
        this.isWorkersContext = url && url.includes('workers-and-queues');
        if (this.projectId) {
          try { // TODO: refactor to a better solution after all navbar are declared...
            this.activeFeature = url.split(this.projectId)[1].split('/')[1];
          } catch (e) {

          }
        }
      });

    this.breadcrumbsSubscription = this.store.select(selectBreadcrumbsStrings)
      .pipe(filter(names => !!names))
      .subscribe(
        (names) => {
          this.breadcrumbsStrings = prepareNames(names);
          this.updateTitle();
        }
      );
    if (window.localStorage.getItem('disableHidpi') !== 'true') {
      this.setScale();
    }

    this.loadPlotly();
  }

  private setScale() {
    const dimensionRatio = getScaleFactor();
    this.store.dispatch(setScaleFactor({scale: dimensionRatio}));
    const scale = 100 / dimensionRatio;
    this.renderer.setStyle(document.body, 'transform', `scale(${scale})`);
    this.renderer.setStyle(document.body, 'transform-origin', '0 0');
    this.renderer.setStyle(document.body, 'height', `${dimensionRatio}vh`);
    this.renderer.setStyle(document.body, 'width', `${dimensionRatio}vw`);
  }

  nameChanged(name) {
    this.store.dispatch(new UpdateProject({id: this.projectId, changes: {name}}));
  }

  ngOnDestroy(): void {
    this.urlSubscription.unsubscribe();
    this.breadcrumbsSubscription.unsubscribe();
    this.selectedCurrentUserSubscription.unsubscribe();
  }

  changeRoute(feature) {
    return this.router.navigateByUrl('projects/' + this.projectId + '/' + feature);
  }

  backToProjects() {
    return this.router.navigateByUrl('projects');
  }

  updateTitle() {
    let route = this.route.snapshot.firstChild;
    let routeConfig = [];

    while (route) {
      const path = route.routeConfig.path.split('/').filter((item) => !!item);
      routeConfig = routeConfig.concat(path);
      route = route.firstChild;
    }
    const crumbs = routeConfig
      .reduce((acc, config) => {
        const dynamicCrumb = this.breadcrumbsStrings[config];
        return acc.concat(dynamicCrumb ? dynamicCrumb.name : formatStaticCrumb(config).name);
      }, [''])
      .filter(name => !!name);
    this.titleService.setTitle(`ClearML - ${crumbs.join(' / ')}`);
  }

  versionDismissed(version: string) {
    this.serverUpdatesService.setDismissedVersion(version);
  }

  notifierActive(show: boolean) {
    this.showNotification = show;
  }

  dismissSurvey() {
    this.store.dispatch(dismissSurvey());

  }

  get guestUser(): boolean {
    return !this.currentUser || this.currentUser?.role === 'guest';
  }

  public loadPlotly(): void {
    const init = () => {
      const script: HTMLScriptElement = document.createElement('script');
      script.type = 'text/javascript';
      script.src = this.plotlyURL;
      script.onerror = () => console.error(`Error loading plotly.js library from ${this.plotlyURL}`);
      script.crossOrigin = 'use-credentials';

      const head: HTMLHeadElement = document.getElementsByTagName('head')[0];
      head.appendChild(script);

      let counter = 600;

      const fn = () => {
        const plotly = (window as any).Plotly;
        if (plotly) {
          this.store.dispatch(plotlyReady());
        } else if (counter > 0) {
          counter --;
          setTimeout(fn, 100);
        } else {
          throw new Error(`Error loading plotly.js library from ${this.plotlyURL}. Timeout.`);
        }
      };

      fn();
    };

    setTimeout(init);
  }
}

