import {
  HttpClient,
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from '@angular/common/http';
import { Injectable, NgZone } from '@angular/core';
import { EMPTY, Observable } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { NotificationService } from '../ui-api/notification.service';
import { ConfigService } from '../config/config.service';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { InvalidLicenseComponent } from '../common-dialogs/license/invalid-license/invalid-license.component';
import { LanguageService } from '../language/language.service';
import { UiFeaturesService } from '../ui-api/ui-features.service';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  private disconnected = false;
  private invalidLicenseDialogShown = false;
  private excludedPaths = ['/invitations/'];
  private snackBarShown = false;

  constructor(
    private notificationService: NotificationService, private configService: ConfigService,
    private router: Router, private httpClient: HttpClient, private route: ActivatedRoute,
    private readonly matDialog: MatDialog, private lang: LanguageService, private uiFeatures: UiFeaturesService,
    private ngZone: NgZone
  ) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req?.headers?.has('skipErrorInterceptor')) {
      req = req.clone({
        headers: req.headers.delete('skipErrorInterceptor')
      });
      return next.handle(req);
    }
    return next.handle(req).pipe(
      tap(response => {
        if (this.disconnected && response.type !== 0) {
          this.disconnected = false;
        }
      }),
      catchError(error => {
        let nextUrl;
        if (error instanceof HttpErrorResponse) {
          const httpError = error as HttpErrorResponse;

          // do not show errors for excluded paths
          if (this.excludedPaths.find(p => httpError.url.includes(p))) {
            throw httpError;
          }

          switch (httpError.status) {
            case 402:
              this.configService.authService.suspended = true;
              const coreLimitInfo = httpError.error.core_limit_info || null;
              const ramLimitInfo = httpError.error.ram_limit_info || null;
              const staff = this.configService.currentConfigurationName === 'staff';
              if (this.invalidLicenseDialogShown) {
                return EMPTY;
              }
              this.invalidLicenseDialogShown = true;
              if (!staff) {
                if (this.configService.authService.userData?.impersonated) {
                  this.configService.authService.stopImpersonating();
                } else {
                  this.matDialog.open(
                    InvalidLicenseComponent, {
                      data: {staff, coreLimitInfo, ramLimitInfo},
                      autoFocus: false,
                    });
                }
              } else {
                this.configService.authService.logout().subscribe(() => {
                  this.matDialog.open(
                    InvalidLicenseComponent, {
                      data: {staff, coreLimitInfo, ramLimitInfo},
                      autoFocus: false,
                    });
                });
              }

              return EMPTY;
            case 401:
              const currentUrl = this.configService.getCurrentUrl();
              if (currentUrl) {
                if (
                  currentUrl.startsWith(this.configService.getPanelUrl('login')) ||
                  currentUrl.startsWith(this.configService.getPanelUrl('saml2sso'))
                ) {
                  // we are at login page, pass error
                  throw error;
                }
              }

              console.warn('Unauthorized, redirecting to login');
              nextUrl = this.configService.checkNextUrl(
                currentUrl && !currentUrl.includes('nextUrl') ? this.configService.getCurrentUrl() : null
              );
              this.ngZone.run(() => {
                this.configService.authService.clearUser();
                this.router.navigate([this.configService.getPanelUrl('login')], {
                  queryParams: nextUrl ? {
                    nextUrl
                  } : {}
                }).then(() => {
                });
              });
              return EMPTY;
            case 400:
              // ignored errors, will be treated by caller
              break;
            case 409:
              this.ngZone.run(() => {
                if (!this.snackBarShown) {
                  if (httpError.error?.detail) {
                     this.notificationService.showMessage(httpError.error.detail);
                  } else {
                    const errorDetail = `${httpError.status}: ${httpError.statusText}`;
                    this.notificationService.showMessage($localize`HTTP Error ${errorDetail}`);
                  }
                  this.snackBarShown = true;
                }
                const queryParams = {};
                Object.assign(queryParams, this.route.snapshot.queryParams);
                this.router.navigate([], {
                  relativeTo: this.route,
                  fragment: this.route.snapshot.fragment,
                  queryParams
                }).then((couldRedirect) => {
                  if (!couldRedirect) {
                    // error appeared on current path, we need to redirect user on home
                    this.router.navigateByUrl(this.configService.getPanelHomeUrl()).finally(
                      () => {
                        this.snackBarShown = false;
                      }
                    );
                  } else {
                    this.snackBarShown = false;
                  }
                });
              });
              throw error;
            case 428:
              let fragment;
              if (httpError.error) {
                if (httpError.error.hasOwnProperty('clientRequired') && httpError.error.clientRequired) {
                  fragment = 'clients/create';
                } else if (httpError.error.hasOwnProperty('emailNotVerified') && httpError.error.emailNotVerified) {
                  fragment = 'confirm-email';
                } else if (httpError.error.hasOwnProperty('checkTOS') && httpError.error.checkTOS) {
                  fragment = 'terms-of-service';
                } else if (httpError.error.hasOwnProperty('sfaNotSet') && httpError.error.sfaNotSet) {
                  fragment = 'sfa/options';
                } else if (httpError.error.hasOwnProperty('creditCardRequired') && httpError.error.creditCardRequired) {
                  fragment = '';
                } else if (httpError.error.hasOwnProperty('accountUnderReview') && httpError.error.accountUnderReview) {
                  fragment = '';
                }
                this.ngZone.run(() => {
                  if (httpError.error.detail) {
                    this.notificationService.showMessage(httpError.error.detail);
                  }
                  let url;
                  if (fragment) {
                    url = this.configService.getPanelUrl(fragment);
                  } else {
                    url = this.configService.getPanelHomeUrl();
                  }
                  this.router.navigateByUrl(url).then(() => {
                  });
                });
              }
              return EMPTY;
            case 502:
            case 0:
              if (!this.configService.getCurrentUrl()) {
                nextUrl = window.location.href;
              } else {
                nextUrl = this.configService.getCurrentUrl();
              }
              if (!this.disconnected) {
                this.disconnected = true;
                if (!this.configService.getCurrentUrl() ||
                  !this.configService.getCurrentUrl().startsWith(
                    this.configService.getPanelUrl('disconnected')
                  )
                ) {
                  this.ngZone.run(() => {
                    this.router.navigate(
                      [this.configService.getPanelUrl('disconnected')],
                      {
                        queryParams: {
                          nextUrl
                        }
                      }
                    ).then(() => {
                      // this ensures sidebar and top bar are hidden
                      this.configService.authService.clearUser();
                    });
                  });
                }
              }
              return EMPTY;
            default:
              if (httpError && httpError.status === 404 && httpError.url &&
                httpError.url.includes('assets/locale/messages')) {
                // do not show 404 error toast when trying to load missing translations file, console error is enough
                return EMPTY;
              }
              let message;
              if (httpError.error && httpError.error.detail && typeof httpError.error.detail === 'string') {
                message = httpError.error.detail;
              } else {
                const errorDetail = `${httpError.status}: ${httpError.statusText}`;
                message = $localize`HTTP Error ${errorDetail}`;
              }
              this.ngZone.run(() => {
                this.notificationService.showMessage(message);
              });
          }
        }
        throw error;
      }));
  }
}
