import {Injectable, OnDestroy} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from "@angular/common/http";
import {Observable, Subject} from "rxjs";
import {environment} from "../../../../environments/environment";
import {AppState} from "../app-state";
import {Router} from "@angular/router";
import {share, takeUntil} from "rxjs/operators";

@Injectable({
  providedIn: 'root'
})
export class ApiService implements OnDestroy {
  private _destroy$ = new Subject<boolean>()

  constructor(
    private http: HttpClient,
    private appState: AppState,
    private router: Router
  ) {
  }

  ngOnDestroy(): void {
    this._destroy$.next(true)
    this._destroy$.complete()
  }

  get<T>(url: string, needAuthentication = true, options: any = {}): Observable<T> {
    return this.executeHttpRequest(
      httpOptions => this.http.get<T>(environment.apiUrl + url, httpOptions),
      needAuthentication,
      options
    )
  }

  post<T>(url: string, body: any = {}, needAuthentication = true, options: any = {}): Observable<T> {
    return this.executeHttpRequest(
      httpOptions => this.http.post<T>(environment.apiUrl + url, body, httpOptions),
      needAuthentication,
      options
    )
  }

  put<T>(url: string, body: any = {}, needAuthentication = true, options: any = {}): Observable<T> {
    return this.executeHttpRequest(
      httpOptions => this.http.put<T>(environment.apiUrl + url, body, httpOptions),
      needAuthentication,
      options
    )
  }

  delete<T>(url: string, needAuthentication = true, options: any = {}): Observable<T> {
    return this.executeHttpRequest(
      httpOptions => this.http.delete<T>(environment.apiUrl + url, httpOptions),
      needAuthentication,
      options
    )
  }

  private executeHttpRequest<T>(requestFn: (httpOptions?: {
    headers?: HttpHeaders | {
      [header: string]: string | string[];
    };
    observe?: 'body';
    params?: HttpParams | {
      [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType?: 'json';
    withCredentials?: boolean;
  }) => Observable<T>, needAuthentication = true, httpOptions: any = {}): Observable<T> {
    if (needAuthentication) {
      if (this.checkAuthenticated()) {
        if (httpOptions) {
          httpOptions.withCredentials = true
        } else {
          console.error("httpOptions need to be specified to use credentials in HTTP methods.")
        }
      } else {
        this.navigateToLogin()
      }
    }

    const result$ = requestFn(httpOptions)
      .pipe(takeUntil(this._destroy$), share())

    const errorCheckSubscription = result$.subscribe({
      next: () => this.appState.isServerOnline = true,
      error: err => {
        errorCheckSubscription.unsubscribe()
        this.appState.isServerOnline = !(err.status === 0 && err.statusText === "Unknown Error");
      }
    })

    return result$
  }

  private checkAuthenticated(): boolean {
    return this.appState.isAuthenticated && Date.now() <= this.appState.authenticationExpiration
  }

  private navigateToLogin() {
    this.router.navigate(['/account/login'])
  }
}
