import { AfterContentInit, Component, HostBinding, HostListener, Input, OnDestroy, OnInit } from '@angular/core';
import { AuthService } from '../../auth/auth.service';
import { animate, state, style, transition, trigger } from '@angular/animations';
import {
  ActivatedRoute,
  Event,
  NavigationCancel,
  NavigationEnd,
  NavigationError,
  NavigationStart,
  Router
} from '@angular/router';
import { RouteHelper } from '../../ui-api/route-helper';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { delay } from 'rxjs/operators';
import { VERSION } from '@environments/version';
import { ThemingService } from '../../ui/theming/theming.service';
import { NotificationService } from '@shared/ui-api/notification.service';
import { LanguageService } from '@shared/language/language.service';
import { UserNotificationsApiService } from '@fleio-api/notifications/user-notifications-api.service';
import { INotificationModel } from '@fleio-api/notifications/model/notification.model';
import { ConfigService } from '@shared/config/config.service';
import { IIcon } from '@shared/ui/common/interfaces/icon';
import { AppColor } from '@shared/ui/common/enums/app-color.enum';
import { MatDialog } from '@angular/material/dialog';
import { hideLoader } from '@shared/ui/common/utils';
import { AppLocalStorageService } from '@shared/ui-api/app-local-storage.service';
import { BlockScrollStrategy, Overlay } from '@angular/cdk/overlay';
import { MAT_SELECT_SCROLL_STRATEGY } from '@angular/material/select';

export function scrollFactory(overlay: Overlay): () => BlockScrollStrategy {
  return () => overlay.scrollStrategies.block();
}

@Component({
  selector: 'app-panel-layout',
  templateUrl: './panel-layout.component.html',
  styleUrls: ['./panel-layout.component.scss'],
  providers: [
    {
      provide: MAT_SELECT_SCROLL_STRATEGY,
      useFactory: scrollFactory,
      deps: [Overlay],
    }
  ],
  animations: [
    trigger('openCloseSidebar', [
      state('closed', style({
        left: '{{sidebarOffsetLeft}}px',
        right: '{{sidebarOffsetRight}}px'
      }), {
        params: {
          sidebarOffsetLeft: -237,
          sidebarOffsetRight: 0
        }
      }),
      transition('open => closed', [
        animate('430ms ease-in-out')
      ]),
      transition('closed => open', [
        animate('430ms ease-in-out')
      ]),
    ]),
    trigger('alignContent', [
      state('closed', style({
        paddingLeft: '0px',
      })),
      transition('open => closed', [
        animate('430ms ease-in-out')
      ]),
      transition('closed => open', [
        animate('430ms ease-in-out')
      ]),
    ]),
    trigger('openCloseTopBar', [
      state('closed', style({
        marginTop: '-67px',
      })),
      transition('open => closed', [
        animate('430ms ease-in-out')
      ]),
      transition('closed => open', [
        animate('430ms ease-in-out')
      ]),
    ]),
  ],
})
export class PanelLayoutComponent implements OnInit, OnDestroy, AfterContentInit {
  @Input() panel: string;

  private readonly routeHelper: RouteHelper;
  private routerEventsSubscription: Subscription;
  private isLoggedInSubscription: Subscription;
  private userDataSubscription: Subscription;
  private smallWindow: boolean;
  private isLoggedIn: boolean;

  public version = VERSION.version;
  public topBarVisible = true;
  public sideBarVisible: boolean;
  public showSidebar: boolean = null;
  public loading = false;
  panelNotifications: Array<INotificationModel>;
  shownPanelNotifications: Array<INotificationModel> = [];
  trigger: any;
  sidebarOffsetLeft: number;
  sidebarOffsetRight: number;
  appContainerTopOffset = '0';
  sidenavContainerHeight = 'calc(100% - 64px)';
  removeWidthLimit = false;
  newVersionReloadIcon = {
    name: 'cached',
    color: AppColor.White,
    class: 'fl-margin-top-small',
    marginLeft: '25px',
    verticalAlign: 'middle',
    fontSize: '35px',
  } as IIcon;
  currentBreakpoint: string;
  private appBreakpointBS: BehaviorSubject<string> = new BehaviorSubject(null);
  public readonly appBreakpointObservable: Observable<string> = this.appBreakpointBS.asObservable();

  constructor(
    public auth: AuthService, private router: Router, private activatedRoute: ActivatedRoute,
    private themingService: ThemingService, private notificationService: NotificationService,
    private languageService: LanguageService,
    private userNotificationsApiService: UserNotificationsApiService,
    public config: ConfigService,
    private matDialog: MatDialog,
    private appLocalStorageService: AppLocalStorageService,
  ) {
    this.routeHelper = new RouteHelper(activatedRoute);

    // set "appBreakpointObservable" in config service, so it will also be accessible from dialogs
    this.config.appBreakpointObservable = this.appBreakpointObservable;
  }

  ngAfterContentInit() {
    this.windowResized();
  }

  @HostListener('window:resize', ['$event'])
  onResize() {
    this.windowResized();
  }

  @HostBinding('@openCloseSidebar') get openCloseSidebar() {
    if (this.languageService.langDirection === 'rtl') {
      this.sidebarOffsetLeft = 0;
      this.sidebarOffsetRight = -237;
    } else {
      this.sidebarOffsetLeft = -237;
      this.sidebarOffsetRight = 0;
    }
    return {
      value: this.trigger,
      params: {
        sidebarOffsetLeft: this.sidebarOffsetLeft,
        sidebarOffsetRight: this.sidebarOffsetRight
      }
    };
  }

  private setAppBreakpoint() {
    const windowWidth = window.innerWidth;
    const breakPointBeforeChange = this.currentBreakpoint;
    if (windowWidth < 599) {
      this.currentBreakpoint = 'xs';
    } else if (windowWidth < 959) {
      this.currentBreakpoint = 'sm';
    } else if (windowWidth < 1279) {
      this.currentBreakpoint = 'md';
    } else if (windowWidth < 1919) {
      this.currentBreakpoint = 'lg';
    } else if (windowWidth < 5000) {
      this.currentBreakpoint = 'xl';
    } else {
      this.currentBreakpoint = 'xl';
    }
    const breakpointChanged = breakPointBeforeChange !== this.currentBreakpoint;
    if (breakpointChanged) {
      this.appBreakpointBS.next(this.currentBreakpoint);
    }
  }

  windowResized() {
    this.setAppBreakpoint();
    const smallWindow = window.innerWidth <= 1280;
    if (smallWindow !== this.smallWindow) {
      // window size changed, invalidate user choice
      this.showSidebar = null;
    }
    this.smallWindow = smallWindow;
    this.updateSideBarVisibility();
  }

  toggleSideBar() {
    if (this.showSidebar === null) {
      // if no user choice present yet just toggle visibility
      this.showSidebar = !this.sideBarVisible;
    } else {
      // toggle user choice if present
      this.showSidebar = !this.showSidebar;
    }
    this.updateSideBarVisibility();
  }

  updateSideBarVisibility() {
    // first take into account window size
    let sideBarVisible = !this.smallWindow;

    // then take into account user choice
    if (this.showSidebar !== null) {
      sideBarVisible = this.showSidebar;
    }

    // finally if user is not logged in hide sidebar
    sideBarVisible = sideBarVisible && this.isLoggedIn;

    this.sideBarVisible = sideBarVisible && !this.auth.suspended;
  }

  refreshConfig() {
    this.removeWidthLimit = !!this.routeHelper.getRouteConfig().removeWidthLimit;
  }

  ngOnInit() {
    this.refreshConfig();
    this.themingService.activateTheme();
    this.isLoggedInSubscription = this.auth.isLoggedIn.subscribe(
      isLoggedIn => {
        this.isLoggedIn = isLoggedIn;
        if (isLoggedIn) {
          if (this.auth.userData?.is_development) {
            // when dev. license we need to re-calculate offset because of sticky footer
            this.calculateAppOffset();
            // development license, periodically show (every 2 days) a toast warning
            const lastDevLicenseWarningDate = this.appLocalStorageService.getLastDevLicenseWarningDate();
            const lastShown = lastDevLicenseWarningDate ? new Date(lastDevLicenseWarningDate) : null;
            let shouldShowDevWarning: boolean;
            if (lastShown) {
              // check if enough time passed since we last showed the warning
              const hourDiff = (new Date().getTime() - lastShown.getTime()) / 3600000;
              shouldShowDevWarning = hourDiff > 48;
            } else {
              shouldShowDevWarning = true;
            }
            if (shouldShowDevWarning && Math.ceil(Math.random() * 30) === 1) {
              this.notificationService.showMessage(
                $localize`Fleio is running with a development license and is not authorized for production usage. Please report any cases of abuse to abuse@fleio.com.`,
                10000,
              );
              this.appLocalStorageService.setLastDevLicenseWarningDate(new Date());
            }
          }

          if (this.auth.userData?.has_panel_notifications) {
            this.userNotificationsApiService.getPanelNotifications().subscribe(response => {
              this.panelNotifications = response.panel_notifications;
              if (this.panelNotifications.length) {
                if (this.shownPanelNotifications.length <= 1) {
                  this.shownPanelNotifications = [this.panelNotifications[0]];
                } else {
                  this._showPanelNotifications();
                }
              } else {
                this.shownPanelNotifications = [];
              }
              this.calculateAppOffset();
            });
          } else {
            this.removePanelNotifications();
          }
        } else {
          // invalidate user choice when logged out
          this.showSidebar = null;
          this.removePanelNotifications();
        }
        this.topBarVisible = isLoggedIn && !this.auth.suspended;
        this.updateSideBarVisibility();
      }
    );

    this.routerEventsSubscription = this.router.events.pipe(delay(0)).subscribe((event: Event) => {
      switch (true) {
        case event instanceof NavigationStart: {
          const urlChanges = this.routeHelper.getUrlChanges((event as NavigationStart).url);
          this.loading = urlChanges.path || urlChanges.query;
          break;
        }

        case event instanceof NavigationEnd:
        case event instanceof NavigationCancel:
        case event instanceof NavigationError: {
          this.refreshConfig();
          if (this.loading && event instanceof NavigationEnd) {
            this.showSidebar = null;
            this.updateSideBarVisibility();
          }
          this.loading = false;
          break;
        }
        default: {
          break;
        }
      }
    });

    // check user data here in order to update view only after components load
    //  - hide application loader
    //  - show extend token validity dialog
    this.userDataSubscription = this.auth.observableUserData.subscribe(userData => {
      // hide application loader
      hideLoader();
      // show extend token validity dialog if necessary
      if (userData && userData.user && userData.user.fleio_token?.expiry_seconds) {
        if (!this.auth.isImpersonating() && userData.user.fleio_token?.expiry_seconds < this.auth.token_expiry_warn_seconds) {
          this.auth.showExtendTokenValidityDialog(userData.user.fleio_token);
        } else {
          if (this.auth.extendTokenDialogShown) {
            // hide if already shown and not needed anymore
            this.matDialog.closeAll();
            this.auth.extendTokenDialogShown = false;
          }
        }
      }
    });

  }

  removePanelNotifications() {
    this.panelNotifications = [];
    this.appContainerTopOffset = '0';
    this.themingService.setAppTopOffset(0);
  }

  calculateAppOffset() {
    // * 60 because of 30px top panel notification height and a total of 30px padding
    let offset = this.shownPanelNotifications.length * 60;
    if (this.panelNotifications && this.panelNotifications.length > 1) {
      // add more 15px to offset because of "see more" line
      offset += 15;
    }
    this.themingService.setAppTopOffset(offset);
    // dev. license sticky footer has 40px height, add offset
    const devLicenseOffset = this.auth?.userData?.is_development ? 40 : 0;
    const sidenavContainerHeight = 64 + offset + devLicenseOffset;
    // sidenav container is also fixed thus impacted by top panel notifications heights, adjust its height
    this.sidenavContainerHeight = `calc(100% - ${sidenavContainerHeight}px)`;
    this.appContainerTopOffset = `${offset}px`;
  }

  private _showPanelNotifications() {
    this.shownPanelNotifications = [];
    let shownCount = 0;
    for (const notification of this.panelNotifications) {
      if (shownCount === 5) {
        // show at most 5 top notifications at once
        break;
      }
      this.shownPanelNotifications.push(notification);
      shownCount++;
    }
  }

  expandTopPanelNotifications() {
    if (this.shownPanelNotifications.length > 1) {
      this.shownPanelNotifications = [this.panelNotifications[0]];
    } else {
      this._showPanelNotifications();
    }
    this.calculateAppOffset();
  }

  reloadPage() {
    window.location.reload();
  }

  ngOnDestroy(): void {
    this.isLoggedInSubscription.unsubscribe();
    this.routerEventsSubscription.unsubscribe();
    if (this.userDataSubscription) {
      this.userDataSubscription.unsubscribe();
      this.userDataSubscription = null;
    }
  }
}
