import {Injectable, OnDestroy} from '@angular/core';
import {HttpClient} 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 {map, 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, requestOptions: ApiRequestOptions = new ApiRequestOptions()): Observable<T> {
    // @ts-ignore
    return this.executeHttpRequest(
      httpOptions => this.http.get<any>(environment.apiUrl + url, httpOptions),
      needAuthentication,
      requestOptions
    )
  }

  post<T>(url: string, body: any = {}, needAuthentication = true, requestOptions: ApiRequestOptions = new ApiRequestOptions()): Observable<T> {
    // @ts-ignore
    return this.executeHttpRequest(
      httpOptions => this.http.post<any>(environment.apiUrl + url, body, httpOptions),
      needAuthentication,
      requestOptions
    )
  }

  put<T>(url: string, body: any = {}, needAuthentication = true, requestOptions: ApiRequestOptions = new ApiRequestOptions()): Observable<T> {
    // @ts-ignore
    return this.executeHttpRequest(
      httpOptions => this.http.put<any>(environment.apiUrl + url, body, httpOptions),
      needAuthentication,
      requestOptions
    )
  }

  delete<T>(url: string, needAuthentication = true, requestOptions: ApiRequestOptions = new ApiRequestOptions()): Observable<T> {
    // @ts-ignore
    return this.executeHttpRequest(
      httpOptions => this.http.delete<any>(environment.apiUrl + url, httpOptions),
      needAuthentication,
      requestOptions
    )
  }

  private executeHttpRequest<T>(requestFn: (httpOptions) => Observable<any>, needAuthentication = true, requestOptions: ApiRequestOptions = new ApiRequestOptions()): Observable<T> {
    const httpOptions = {withCredentials: false, observe: 'response'}
    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$ = requestOptions.takeFullResponse
      ? requestFn(httpOptions)
        .pipe(takeUntil(this._destroy$), share())
      : requestFn(httpOptions)
        .pipe(takeUntil(this._destroy$), map(r => r.body), 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'])
  }
}

export class ApiRequestOptions {
  constructor(
    public takeFullResponse = false
  ) {
  }
}
