import {HttpClient, HttpErrorResponse, HttpEventType, HttpParameterCodec, HttpParams} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import { Serializable } from "ng-stalk/core/model";
import { MagDialogService } from "ng-stalk/core/service";
import * as _ from "lodash";
import {ApiResult} from "../../../model/api-result";
import {Observable} from "rxjs";
import {UploadingEvent} from "../../../service/api-service/uploading-event";
import * as moment from "moment";
import {catchError, map} from "rxjs/operators";
import {DefaultApiErrorHandler} from "../../../service/default-api-error-handler";
import {FileResponseTs} from "./file-response";

export class CustomURLEncoder implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }
  encodeValue(key: string): string {
    return encodeURIComponent(key);
  }
  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }
  decodeValue(key: string) {
    return decodeURIComponent(key);
  }
}

@Injectable({
  providedIn: 'root'
})
export class SampleApiService {

  constructor(private http: HttpClient, private router: Router, public dialog: MagDialogService) {

  }

  get(url, params = {}, responseClass?: any) {
    let httpParams: HttpParams = new HttpParams({
      encoder: new CustomURLEncoder(),
    });
    if (params != null) {
      _.forEach(params, (value, key) => {
        if (value != null) {
          if (Array.isArray(value)) {
            // @ts-ignore
            for (let arrayValue of value) {
              httpParams = httpParams.append(key, arrayValue);
            }
          } else if (typeof value !== 'function') {
            httpParams = httpParams.set(key, value);
          }

        }
      })
    }

    return new Promise((resolve, reject) => {
      this.http.get(url, {observe: 'response', params: httpParams}).subscribe(response => {
        const apiResult: ApiResult<any> = <ApiResult<any>>response.body;
        if (responseClass && (new responseClass()).fillFromJSONObject) {
          if (Array.isArray(apiResult.data)) {
            const result = [];
            for (let item of apiResult.data) {
              result.push(new responseClass().fillFromJSONObject(item));
            }
            resolve(result);
          } else {
            resolve(new responseClass().fillFromJSONObject(apiResult.data));
          }

        } else {
          resolve(apiResult.data);
        }
      }, (err: HttpErrorResponse) => {
        reject(this.makeDefaultErrorHandler(err));
      });
    });
  }

  post(url, data, responseClass?: any) {
    let clearedBody = new Serializable().fillFromJSONObject(data, {
      mapToObject: true,
    });

    return new Promise((resolve, reject) => {
      return this.http.post(url, clearedBody, {
        observe: 'response',
      }).subscribe(response => {
        const apiResult: ApiResult<any> = <ApiResult<any>>response.body;
        if (responseClass && (new responseClass()).fillFromJSONObject) {
          if (Array.isArray(apiResult.data)) {
            const result = [];
            for (let item of apiResult.data) {
              result.push(new responseClass().fillFromJSONObject(item));
            }
            resolve(result);
          } else {
            resolve(new responseClass().fillFromJSONObject(apiResult.data));
          }
        } else {
          resolve(apiResult.data);
        }
      }, (err: HttpErrorResponse) => {
        console.error('err:', err);
        reject(this.makeDefaultErrorHandler(err));
      })
    });
  }

  upload(url, data): Observable<UploadingEvent<FileResponseTs>> {
    const start = moment();
    return this.http.post(url, data, {
      reportProgress: true,
      observe: 'events',
    }).pipe(map((event) => {
        const costMilliSeconds = moment().diff(start, 'milliseconds');
        let uploadingEvent;
        switch (event.type) {
          case HttpEventType.Sent:
            uploadingEvent = new UploadingEvent({
              finished: false,
              success: false,
              progress: 0,
              leftSeconds: -1,
            });
            return uploadingEvent;
          case HttpEventType.UploadProgress:
            const progress = Math.round(100 * event.loaded / event.total);
            const leftSeconds = parseInt((parseInt(((100 * costMilliSeconds / progress) - costMilliSeconds) + '') / 1000) + '');

            uploadingEvent = new UploadingEvent({
              finished: false,
              success: false,
              progress,
              leftSeconds,
            });
            return uploadingEvent;
          case HttpEventType.ResponseHeader:
          case HttpEventType.DownloadProgress:
            uploadingEvent = new UploadingEvent({
              finished: false,
              success: false,
              progress: 100,
              leftSeconds: 0
            });
            return uploadingEvent;
          case HttpEventType.Response:
            const apiResult: ApiResult<any> = event.body as ApiResult<any>;
            uploadingEvent = new UploadingEvent({
              finished: true,
              success: true,
              progress: 100.0,
              leftSeconds: 0,
              data: apiResult.data,
            });
            return uploadingEvent;
          case HttpEventType.User:
            uploadingEvent = new UploadingEvent({
              finished: true,
              success: false,
              progress: 0,
              leftSeconds: 0,
              error: event.type
            });
            return uploadingEvent;
        }
      }),
      catchError((err) => {
        throw this.makeDefaultErrorHandler(err);
      })
    );
  }

  put(url, params, data: any, responseClass?) {
    let httpParams: HttpParams = new HttpParams({
      encoder: new CustomURLEncoder(),
    });
    if (params != null) {
      _.forEach(params, (value, key) => {
        if (value != null) {
          if (Array.isArray(value)) {
            // @ts-ignore
            for (let arrayValue of value) {
              httpParams = httpParams.append(key, arrayValue);
            }
          } else if (typeof value !== 'function') {
            httpParams = httpParams.set(key, value);
          }

        }
      })
    }

    let clearedBody = new Serializable().fillFromJSONObject(data, {
      mapToObject: true,
    });

    return new Promise((resolve, reject) => {
      return this.http.put(url, clearedBody, {observe: 'response', params: httpParams}).subscribe(response => {
        const apiResult: ApiResult<any> = <ApiResult<any>>response.body;
        if (responseClass && (new responseClass()).fillFromJSONObject) {
          if (Array.isArray(apiResult.data)) {
            const result = [];
            for (let item of apiResult.data) {
              result.push(new responseClass().fillFromJSONObject(item));
            }
            resolve(result);
          } else {
            resolve(new responseClass().fillFromJSONObject(apiResult.data));
          }

        } else {
          resolve(apiResult.data);
        }
      }, (err: HttpErrorResponse) => {
        reject(this.makeDefaultErrorHandler(err));
      })
    });
  }

  deleteOne(url, params = {}, responseClass?: any) {
    let httpParams: HttpParams = new HttpParams({
      encoder: new CustomURLEncoder(),
    });
    if (params != null) {
      _.forEach(params, (value, key) => {
        if (value != null) {
          httpParams = httpParams.set(key, value);
        }
      })
    }
    return new Promise((resolve, reject) => {
      this.http.delete(url, {observe: 'response', params: httpParams}).subscribe(response => {
        const apiResult: ApiResult<any> = <ApiResult<any>>response.body;
        if (responseClass && (new responseClass()).fillFromJSONObject) {
          if (Array.isArray(apiResult.data)) {
            const result = [];
            for (let item of apiResult.data) {
              result.push(new responseClass().fillFromJSONObject(item));
            }
            resolve(result);
          } else {
            resolve(new responseClass().fillFromJSONObject(apiResult.data));
          }

        } else {
          resolve(apiResult.data);
        }
      }, (err: HttpErrorResponse) => {
        reject(this.makeDefaultErrorHandler(err));
      });
    });
  }

  getAsDownload(url, params = {}, responseClass?: any) {
    let httpParams: HttpParams = new HttpParams({
      encoder: new CustomURLEncoder(),
    });
    if (params != null) {
      _.forEach(params, (value, key) => {
        if (value != null) {
          if (Array.isArray(value)) {
            // @ts-ignore
            for (let arrayValue of value) {
              httpParams = httpParams.append(key, arrayValue);
            }
          } else if (typeof value !== 'function') {
            httpParams = httpParams.set(key, value);
          }

        }
      })
    }
    document.location.href = url + '?'+httpParams.toString();
    return new Promise((resolve, reject) => {
      resolve();
    });
  }

  makeDefaultErrorHandler(errorResponse: HttpErrorResponse) {
    return new DefaultApiErrorHandler(this.router, this.dialog, errorResponse);
  }

}
