import { Injectable, isDevMode } from '@angular/core';
import { HttpClient, HttpEvent, HttpHeaders, HttpParams } from '@angular/common/http';
import { FleioId, IBaseFleioObjectModel } from './base-model/base-fleio-object.model';
import { Observable } from 'rxjs';
import { FleioObjectsList } from './fleio-objects-list';
import { map } from 'rxjs/operators';
import { IPermissionsModel } from './base-model/IPermissionsModel';
import { IListQueryParams } from './base-model/list-query-params';
import { ConfigService } from '../config/config.service';
import { environment } from '@environments/environment';
import { Params } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export abstract class FleioApiService<ObjectModelType extends IBaseFleioObjectModel> {
  protected endpoint: string = null;
  protected httpClient: HttpClient = null;
  protected config: ConfigService = null;
  protected additionalQueryParams: Params = {};

  protected setEndpoint(endpoint: string) {
    this.endpoint = endpoint;
  }

  public setAdditionalQueryParams(additionalQueryParams: Params) {
    this.additionalQueryParams = additionalQueryParams;
  }

  public checkIfInitialized() {
    if (isDevMode()) {
      if (!this.httpClient) {
        console.error('httpClient property is null, it must be injected as protected property in derived classes.');
      }

      if (!this.config) {
        console.error('config property is null, it must be injected as protected property in derived classes.');
      }

      if (!this.endpoint) {
        if (environment.enableErrorLogging) {
          console.error('endpoint property is null, it must be passed as a parameter to constructor in derived classes.');
        }
        this.endpoint = 'dummy'; // dummy string for unit tests purposes
      }
    }
  }

  public list(queryParams: IListQueryParams = {}, action: string = null):
    Observable<FleioObjectsList<ObjectModelType>> {
    this.checkIfInitialized();
    let effectiveQueryParams;
    if (!queryParams.hasOwnProperty('page_size') && (this.config && this.config.current &&
      this.config.current.settings)) {
      // make sure page size from configs is used
      effectiveQueryParams = Object.assign(
        {page_size: this.config.current.settings.paginateBy}, queryParams, this.additionalQueryParams,
      );
    } else {
      effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    }
    const listParams = new HttpParams({fromObject: {...effectiveQueryParams}});
    const queryEndpoint = action ? `${this.endpoint}/${action}` : this.endpoint;
    return this.httpClient.get(
      queryEndpoint,
      {params: listParams},
    ).pipe(map(objectList => objectList as FleioObjectsList<ObjectModelType>));
  }

  public get(objectId: FleioId, queryParams: IListQueryParams = {}, skipErrorInterceptor=false): Observable<ObjectModelType> {
    this.checkIfInitialized();
    const effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    let options = {
      params: effectiveQueryParams
    } as Record<string, any>;
    if (skipErrorInterceptor) {
      options.headers = {skipErrorInterceptor: 'true'};
    }
    return this.httpClient.get(
      `${this.endpoint}/${encodeURIComponent(objectId)}`,
      options,
    ) as Observable<ObjectModelType>;
  }

  public create(objectData: ObjectModelType): Observable<ObjectModelType> {
    this.checkIfInitialized();

    return this.httpClient.post(
      this.endpoint,
      objectData,
      {params: this.additionalQueryParams},
    ).pipe(map(result => result as ObjectModelType));
  }

  public createWithUpload(
    objectData: FormData, action: string = null, queryParams: Params = null,
  ): Observable<HttpEvent<ObjectModelType>> {
    this.checkIfInitialized();

    let headers: HttpHeaders;
    if (objectData instanceof FormData) {
      headers = new HttpHeaders();
      headers.append('Content-Type', 'multipart/form-data');
      headers.append('Accept', 'application/json');
    }
    let endpoint = this.endpoint;
    if (action) {
      endpoint += `/${action}`;
    }
    let effectiveQueryParams = this.additionalQueryParams;
    if (queryParams) {
      effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    }
    return this.httpClient.post(
      endpoint,
      objectData,
      {
        headers,
        observe: 'events',
        reportProgress: true,
        params: effectiveQueryParams,
      },
    ).pipe(map(result => result as HttpEvent<ObjectModelType>));
  }

  public postActionWithUpload(
    objectData: FormData, action: string = null, queryParams: Params = null,
  ): Observable<any> {
    this.checkIfInitialized();

    let headers: HttpHeaders;
    if (objectData instanceof FormData) {
      headers = new HttpHeaders();
      headers.append('Content-Type', 'multipart/form-data');
      headers.append('Accept', 'application/json');
    }
    let endpoint = this.endpoint;
    if (action) {
      endpoint += `/${action}`;
    }
    let effectiveQueryParams = this.additionalQueryParams;
    if (queryParams) {
      effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    }
    return this.httpClient.post(
      endpoint,
      objectData,
      {
        headers,
        params: effectiveQueryParams,
      },
    );
  }

  public objectPostActionWithUpload(
    objectId: FleioId, objectData: FormData, action: string, queryParams: Params = null,
  ): Observable<any> {
    this.checkIfInitialized();

    let headers: HttpHeaders;
    if (objectData instanceof FormData) {
      headers = new HttpHeaders();
      headers.append('Content-Type', 'multipart/form-data');
      headers.append('Accept', 'application/json');
    }
    const endpoint = `${this.endpoint}/${encodeURIComponent(objectId)}/${action}`;
    let effectiveQueryParams = this.additionalQueryParams;
    if (queryParams) {
      effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    }
    return this.httpClient.post(
      endpoint,
      objectData,
      {
        headers,
        params: effectiveQueryParams,
      },
    );
  }

  public update(objectId: FleioId, objectData: ObjectModelType): Observable<ObjectModelType> {
    this.checkIfInitialized();
    return this.httpClient.put(
      `${this.endpoint}/${encodeURIComponent(objectId)}`,
      objectData,
      {params: this.additionalQueryParams},
    ).pipe(map(result => result as ObjectModelType));
  }

  public partialUpdate(objectId: FleioId, objectData: Partial<ObjectModelType>): Observable<ObjectModelType> {
    this.checkIfInitialized();
    return this.httpClient.patch(
      `${this.endpoint}/${encodeURIComponent(objectId)}`,
      objectData,
      {params: this.additionalQueryParams},
    ).pipe(map(result => result as ObjectModelType));
  }

  public delete(objectId: FleioId, queryParams: IListQueryParams = {}): Observable<any> {
    this.checkIfInitialized();
    const effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    const listParams = new HttpParams({fromObject: {...effectiveQueryParams}});
    return this.httpClient.delete(
      `${this.endpoint}/${encodeURIComponent(objectId)}`,
      {params: listParams}
    );
  }

  public permissions(): Observable<IPermissionsModel> {
    this.checkIfInitialized();
    return this.httpClient.get(
      `${this.endpoint}/permissions`,
      {params: this.additionalQueryParams},
    ) as Observable<IPermissionsModel>;
  }

  public createOptions(parameters?: Record<string, any>): Observable<Record<string, any>> {
    this.checkIfInitialized();
    if (typeof parameters === 'object' && Object.keys(parameters).length) {
      return this.httpClient.get(
        `${this.endpoint}/create_options`,
        {params: Object.assign({}, parameters, this.additionalQueryParams)},
      ) as Observable<Record<string, any>>;
    }
    return this.httpClient.get(
      `${this.endpoint}/create_options`,
      {params: this.additionalQueryParams},
    ) as Observable<Record<string, any>>;
  }

  public filterOptions(): Observable<Record<string, any>> {
    this.checkIfInitialized();
    return this.httpClient.get(
      `${this.endpoint}/filter_options`,
      {params: this.additionalQueryParams},
    ) as Observable<Record<string, any>>;
  }

  public objectPostAction(objectId: FleioId, action: string, body: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    return this.httpClient.post(
      `${this.endpoint}/${encodeURIComponent(objectId)}/${action}`,
      body,
      {params: this.additionalQueryParams},
    );
  }

  public postAction(action: string, body?: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    let url = this.endpoint;
    if (action) {
      url += `/${action}`;
    }
    return this.httpClient.post(
      url,
      body,
      {params: this.additionalQueryParams},
    );
  }

  public objectPutAction(objectId: FleioId, action: string, body: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    return this.httpClient.put(
      `${this.endpoint}/${encodeURIComponent(objectId)}/${action}`,
      body,
      {params: this.additionalQueryParams},
    );
  }

  public putAction(action: string, body: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    return this.httpClient.put(
      `${this.endpoint}/${action}`,
      body,
      {params: this.additionalQueryParams},
    );
  }

  public objectGetAction(objectId: FleioId, action: string, queryParams?: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    const effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    return this.httpClient.get(
      `${this.endpoint}/${encodeURIComponent(objectId)}/${action}`, {
        params: effectiveQueryParams
      }) as Observable<ObjectModelType>;
  }

  public getAction(action?: string, queryParams?: Record<string, any>): Observable<any> {
    this.checkIfInitialized();
    let url = this.endpoint;
    if (action) {
      url += `/${action}`;
    }
    const effectiveQueryParams = Object.assign({}, queryParams, this.additionalQueryParams);
    return this.httpClient.get(
      url, {
        params: effectiveQueryParams,
      }) as Observable<ObjectModelType>;
  }
}
