import { inject, Inject, Injectable, Optional, SkipSelf } from '@angular/core';
import { Router } from '@angular/router';
import * as Sentry from '@sentry/angular';
import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSpinnerService } from 'ngx-spinner';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { LoginPopupComponent } from '../components/login/login-popup/login-popup.component';
import * as Cnst from '../consts';
import { LOGIN_PAGE } from './globals';
import { ENVIRONMENT_TOKEN } from '../injection-tokens';
import { CoreUser, EN_UserRoles } from '../models/coreuser';
import { ResultSubject } from '../models/resultsubject';
import { AppLogsService } from '../services/applogs.service';
import { RepositoryService } from '../services/http/repository.service';
import * as Utils from '../utils';
import { HenselTranslateService } from './localization/hensel-translate.service';
import { LocaleService } from './localization/locale.service';
import { MatDialogService } from './mat-dialog.service';
import { UINotifierService } from './notification/uinotifier.service';

// tslint:disable-next-line: class-name
export class HR_JWToken {
  idToken: string;
  expiresInSec?: number;
  expiresAt?: Date;

  constructor(idToken: string, expiresAt: Date, expiresInSec?: number) {
    this.idToken = idToken;
    this.expiresAt = expiresAt;
    this.expiresInSec = expiresInSec;
  }
}



@Injectable({ providedIn: 'root' })
export class AuthorizeService {
  private environment = inject(ENVIRONMENT_TOKEN);

  public EN_LoginStatus_Unknown = Cnst.EN_LoginStatus.unknown;
  public EN_LoginStatus_JWT = Cnst.EN_LoginStatus.jwt;
  public EN_LoginStatus_Password = Cnst.EN_LoginStatus.loginWithNameAndPassword;
  public EN_LoginStatus_Windows = Cnst.EN_LoginStatus.windowsAuthorizatoin;

  public versionObsolete$ = new Subject<void>();
  public loginAction$ = new Subject<ResultSubject>();
  public loginLogout$ = new BehaviorSubject<CoreUser>(null);
  public loginStatus: Cnst.EN_LoginStatus = Cnst.EN_LoginStatus.unknown;
  public useWindowsLogin: boolean = true;
  public isMobileDevice: boolean = false;
  public expiredJWTLogout: boolean = false;
  public useJWT: boolean = false;
  public HR_JWT: HR_JWToken;
  public processDisclaimer: (coreUser: CoreUser) => Promise<boolean>;
  private _user: CoreUser = new CoreUser();
  private lastLoggedUserId: number;
  private _versionObsolete: boolean = false;
  public get versionObsolete(): boolean {
    return this._versionObsolete;
  }
  public set versionObsolete(value: boolean) {
    if (this._versionObsolete === value) return;
    this._versionObsolete = value;
    if (value) this.versionObsolete$.next();
  }

  private isRunningRenewJWT: boolean = false;



  constructor(
    private dialog: MatDialogService,
    private router: Router,
    private repositoryService: RepositoryService,
    private appLogsService: AppLogsService,
    private uiNotifier: UINotifierService,
    private deviceService: DeviceDetectorService,
    private spinnerService: NgxSpinnerService,
    private translate: HenselTranslateService,
    public localeService: LocaleService,

    @Optional() @SkipSelf() parentService: AuthorizeService,
    // @Inject(LOCALE_ID) private localeId: string,
  ) {

    repositoryService.authService = this; //to give possibility to use authservice in base entity wrappers
    this.isMobileDevice = !this.deviceService.isDesktop();
    this.useWindowsLogin = !this.isMobileDevice;
    if ('useLoginWithJWT' in this.environment) this.useJWT = this.environment.useLoginWithJWT;
    if ('dontUseWindowsLogin' in this.environment) this.useWindowsLogin = !this.environment.dontUseWindowsLogin;

    // if (this.useJWT) {
    //   if (!this.checkJWT(this.environment.production ? 60 * 24 /* relogin one day before expiration*/ : 0)) this.loginStatus = this.EN_LoginStatus_Password;
    // } else if (!this.useWindowsLogin) this.loginStatus = this.EN_LoginStatus_Password;

    if (parentService) throw new Error('Authorization service is already loaded. Import it in the AppModule only');

    this.loginAction$.subscribe((resultObject) => {
      if (resultObject.result) {
        // ACCOUNT_PAGE.hidden = false;
        if (!(this.user.isAdmin) && this.environment.production && window) {
          window.console.log = function () { };
          this.uiNotifier.isActive = false;
        }
        this.appLogsService.log(resultObject.errMsg, this.user);
        this.uiNotifier.showMessage(this.translate.instant('core.msg.loggedon', { value: this.user.loginName }));
      } else {
        this.appLogsService.error(resultObject.errMsg);
        this.uiNotifier.showMessage(`${resultObject.errMsg}`);
      }
    });

    if (!!this.environment.sentry_dsn) {
      this.loginLogout$.subscribe((user: CoreUser) => {
        const scope = Sentry.getCurrentScope();
        scope.setUser({
          'id': user?.webAppUserId.toString(),
          'shortname': user?.shortName,
          'username': user?.name,
          'login': user?.loginName,
        });
      });
    }

    this.appLogsService.dlog('AuthorizeService is initialized');
  }


  get user(): CoreUser {
    return this._user;
  }


  set user(user: CoreUser) {
    this._user = user;
  }

  isLoggedIn(): boolean {
    return this.isValidUser();
  }

  private isValidUser(): boolean {
    return !this.isEmptyUser() && this.user.entityId !== 0;
  }

  private isEmptyUser(): boolean {
    return (!this._user) || this._user.isEmpty();
  }

  get loginname() {
    return this.user?.loginName;
  }

  get userName() {
    return this.user?.name;
  }

  logout(gotoLoginPage: boolean = true) {
    this.loginStatus = this.EN_LoginStatus_Password;
    this._user.clear();
    localStorage.removeItem('HR-JWToken');
    this.HR_JWT = undefined;
    if (gotoLoginPage) {
      if (this.isLoggedUserChanged()) this.loginLogout$.next(null);
      this.lastLoggedUserId = null;
      this.router.navigate(['/' + LOGIN_PAGE.path]);
    }
  }


  login(loginname?: string, passw?: string) {
    const loginWithPassword: boolean = !!loginname;
    if (!loginWithPassword && !this.useJWT && !this.useWindowsLogin) return;

    const logController = loginWithPassword ? Cnst.cnst_LoginPassw_URL : (this.useJWT ? Cnst.cnst_LoginJWT_URL : Cnst.cnst_LoginAuth_URL);
    const errorText = loginWithPassword ? this.translate.instant('core.error.logon') : this.translate.instant('core.error.logonnotpossible', { value: this.translate.instant(this.useJWT ? 'JWT-' : 'Windows-') });

    this.user.loginName = loginname;
    this.user.password = passw;
    this.user.clientVersion = this.environment.appVersion;

    try {
      this.user.timeZoneOffsetInMin = new Date().getTimezoneOffset();
      const loginObj$: Observable<CoreUser> = this.repositoryService.postData<CoreUser>(this.user.typeName + '/' + logController, this.user);

      if (loginWithPassword) {
        this.HR_JWT = undefined;
        this.user.culture = this.localeService.currentCulture;
        this.user.language = this.localeService.currentLanguage;
      }

      loginObj$.subscribe(
        async (coreUser) => {
          this.user.assign(coreUser);

          if (this.user.entityId) {
            if (this.user.roleList && (this.user.isMaster || this.user.isInRolle(EN_UserRoles.DepartmentUser) || this.user.isInRolle(EN_UserRoles.User))) {

              if (loginWithPassword) {
                this.jwtAuthentication();
                this.loginStatus = this.EN_LoginStatus_Password;
              } else {
                this.loginStatus = this.jwtAuthentication() ? this.EN_LoginStatus_JWT : this.EN_LoginStatus_Windows;
              }

              if (!!coreUser.language) this.localeService.currentLanguage = coreUser.language;
              if (!!coreUser.culture) this.localeService.currentCulture = coreUser.culture;

              if (coreUser.showDisclaimer && this.processDisclaimer) {
                LoginPopupComponent.LOGIN_IS_SHOWN = true; //do not show Login poup
                if (!await this.processDisclaimer(coreUser)) {
                  LoginPopupComponent.LOGIN_IS_SHOWN = false;
                  this.loginAction$.next({ result: false, errMsg: this.translate.instant('core.error.disclaimernotconfirmed') });
                  // this.loginStatus = this.EN_LoginStatus_Password; //the order is important
                  return;
                }
              }

              if (this.isLoggedUserChanged()) this.loginLogout$.next(coreUser);
              this.appLogsService.dlog('!! - eingelogged', new Date().toTimeString(), coreUser);
              this.loginAction$.next({ result: true, entity: coreUser });
              this.lastLoggedUserId = coreUser.webAppUserId;
            } else {
              this.loginAction$.next({ result: false, errMsg: this.translate.instant('core.error.nouserrole'), entity: coreUser }); //roleList must be checked in backend
              this.loginStatus = this.EN_LoginStatus_Password; //the order is important
            }
          } else {
            this.loginAction$.next({ result: false, errMsg: coreUser['Message'] });
            this.loginStatus = this.EN_LoginStatus_Password; //the order is important
          }
        },
        error => {
          this.loginAction$.next({
            result: false, errMsg: errorText
              + (typeof error.error === 'string' && (error.error as string).indexOf('Exception') === -1 ? ' ' + error.error : '')
          });
          this.loginStatus = this.EN_LoginStatus_Password; //the order is important
        }
      );
    } catch (error) {
      this.loginStatus = this.EN_LoginStatus_Password;
      this.appLogsService.dlog(logController + ' Unexpected Error', error);
    } finally {
    }
  }


  isLoggedUserChanged() {
    return (this.lastLoggedUserId ?? 0) !== (this.user?.webAppUserId ?? 0);
  }

  public checkJWT(correctionInMin?: number): boolean {
    if (!this.HR_JWT) {
      this.HR_JWT = JSON.parse(localStorage.getItem('HR-JWToken')) as HR_JWToken;
      if (!this.HR_JWT?.idToken) return false;
      if (this.HR_JWT) this.HR_JWT.expiresAt = new Date(this.HR_JWT?.expiresAt);
    }
    const expiresAt = this.HR_JWT.expiresAt;
    if (correctionInMin === null || correctionInMin === undefined) return !!this.HR_JWT?.idToken; //correctionInMin === null - just load and check JWT
    expiresAt.setMinutes(expiresAt.getMinutes() - correctionInMin);
    return (!!this.HR_JWT?.idToken && Utils.isBefore(this.HR_JWT?.expiresAt));
  }


  public jwtAuthentication(): boolean {
    if (this.useJWT && this.user.token) {
      this.user.jwtExpiredOn.setMinutes(this.user.jwtExpiredOn.getMinutes() + 1);
      this.HR_JWT = new HR_JWToken(this.user.token, this.user.jwtExpiredOn);
      localStorage.setItem('HR-JWToken', JSON.stringify(this.HR_JWT));
      return true;
    } else return false;
  }


  loginPopup(errorMsg?: string, gotoLoginPage: boolean = false) {
    if (LoginPopupComponent.LOGIN_IS_SHOWN) return;
    LoginPopupComponent.LOGIN_IS_SHOWN = true;
    this.logout(gotoLoginPage);
    if ((errorMsg === null || errorMsg === undefined) && this.expiredJWTLogout) errorMsg = Cnst.cnst_JWT_Expired;
    this.expiredJWTLogout = false;
    this.dialog.openDialog(LoginPopupComponent, { errorMsg: errorMsg, authorizationService: this }, true, () => { });
  }


  public async tryRenewJWTAsync(openLogin: boolean = true): Promise<boolean> {
    if (!this.useJWT) return false;

    if (LoginPopupComponent.LOGIN_IS_SHOWN || this.isRunningRenewJWT) return;
    this.isRunningRenewJWT = true;
    this.appLogsService.warn('Try to renew JWT');
    this.uiNotifier.showMessage(this.translate.instant('core.msg.renewJWT'));
    this.spinnerService.show();
    try {
      if (!this.HR_JWT || !await this.loginAsync()) {
        if (openLogin) this.loginPopup(Cnst.cnst_JWT_Expired);
        return false;
      }
    }
    finally {
      this.spinnerService.hide();
      this.isRunningRenewJWT = false;
    }
    return true;
  }


  public async loginAsync(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      const logisSubscription =
        this.loginAction$.subscribe((resultObject) => {
          logisSubscription.unsubscribe();
          resolve(resultObject.result);
        });
      this.login();
    });
  }

}
