
import { cnst_ErrorCode4Unauthorized, EN_Error } from '../../consts';
import { ResultSubject } from '../../models/resultsubject';
import { RepositoryService } from '../../services/http/repository.service';
import { Observable } from 'rxjs';
import { now } from '../../utils';

export type BaseEntityCallBack = (entity?: BaseEntity) => void;
export type ErrorCallBack = (error: any) => void;

export class BaseEntity {
  public typeName: string = ''; //is used as Controller for URL queries
  public get entitytitle(): string { return ''; } //is used for messages
  public get useNull4Clear(): boolean { return false; } //is used for clear to set null instead of 0 or ''
  public entityChanged: boolean = false;
  get dateFields(): string {
    return null;
  }
  protected technicalFields: string[] = ['dateFields', 'dontInsertFields', 'dontUpdateFields', 'typeName', 'entitytitle', 'technicalFields', 'lastLoaded', 'useNull4Clear'];
  public dontUpdateFields: string[] = [...this.technicalFields, 'entityChanged'];
  public dontInsertFields: string[] = [...this.technicalFields, 'entityChanged'];
  public lastLoaded: number;

  resetCallBack: (obj: BaseEntity) => void;

  constructor(source?: Partial<BaseEntity>) {
    //    this.typeName = this.constructor.name;  //is not working in productive environment !!!
    if (source) this.assign(source);
    else this.clear();
  }

  public deleteFields(): BaseEntity {
    const retEntity: BaseEntity = Object.assign({}, this);
    const whatfields: string[] = this.isNew() ? this.dontInsertFields : this.dontUpdateFields;
    whatfields.forEach((field: string) => delete retEntity[field]);
    return retEntity;
  }

  get entityId(): number { return -1; }
  set entityId(id: number) { }


  public assign(source: Partial<BaseEntity>): BaseEntity {
    this.clear();
    const res = Object.assign(this, source);
    this.approveDateFields(this.dateFields);
    return res;
  }


  public approveDateField(fieldname: string) {
    if (this[fieldname] && !(this[fieldname] instanceof Date)) this[fieldname] = new Date(this[fieldname]);
  }


  public approveDateFields(fieldnames: string) {
    if (!fieldnames) return;
    const list = fieldnames.split(',');
    list.forEach(fld => this.approveDateField(fld.trim()));
  }


  protected clearField(fldName: string, useNull: boolean = false) {
    if (this[fldName] instanceof Array) this[fldName] = [];
    else {
      if (fldName.slice(-2) === 'Id') this[fldName] = null;
      else {
        switch (useNull || this.useNull4Clear ? null : typeof this[fldName]) {
          case 'number':
            this[fldName] = 0;
            break;
          case 'boolean':
            this[fldName] = false;
            break;
          case 'string':
            this[fldName] = '';
            break;
          default:
            this[fldName] = null;
            break;
        }
      }
    }
  }


  public clear(useNull: boolean = false) {
    try {
      for (const prop in this) {
        if ((this.technicalFields.indexOf(prop) === -1) && this.propertyIsEnumerable(prop)) this.clearField(prop, useNull);
      }
    } catch (ex) { }

    if (this.isRealObject()) this.entityId = 0;

    this.entityChanged = false;
    if (this.resetCallBack) { this.resetCallBack(this); }
  }


  public isEmpty() {
    return Object.keys(this).length === 0 && (typeof this) === 'object';
  }

  //checks if this a realy Object with all methods but not only record with data fields after http call
  public isRealObject() {
    return 'entityId' in this;
  }


  public isValidInput() { return true; }

  public isNew() { return !this.entityId || this.entityId <= 0; }

  public insertWithIds() { return false; }

  load(repositoryService: RepositoryService, loadedcallback: BaseEntityCallBack, errorcallback?: ErrorCallBack, completecallback?: () => void): boolean {
    const currentId = this.entityId;
    if (!this.isNew() || currentId === -1) //-1 means load entity w/o id, f.e. Info
    {
      repositoryService.getDataById<BaseEntity>(this.typeName, currentId)
        .subscribe((entity: BaseEntity) => {
          if (this.entityId !== currentId) return false; //means, that meanwhile a load command for another Id was executed. it must win
          console.log('loaded', entity);
          this.lastLoaded = now();
          this.assign(entity);
          if (loadedcallback) loadedcallback(this);
          return true;
        }, (error: any) => {
          if (error.status !== cnst_ErrorCode4Unauthorized) this.clear();
          if (errorcallback) errorcallback(new ResultSubject(error, this, this.typeName + ': '));
        }, completecallback);
    } else {
      this.clear();
      // if (loadedcallback) loadedcallback(this); //for new only complete is triggered - otherwise an empty line is added to the grid, because of sync
      if (completecallback) completecallback();
    }
    return false;
  }


  public includeFieldForUpdate(fieldNames: string) {
    const fieldList = fieldNames.split(',');
    fieldList.forEach(fieldName => {
      const index = this.dontUpdateFields.indexOf(fieldName);
      if (index > -1) this.dontUpdateFields.splice(index, 1);
    });
  }


  public excludeFieldFromUpdate(fieldNames: string) {
    const fieldList = fieldNames.split(',');
    fieldList.forEach(fieldName => {
      const index = this.dontUpdateFields.indexOf(fieldName);
      if (index === -1) this.dontUpdateFields.push(fieldName);
    });
  }


  public async saveAsync(repositoryService: RepositoryService): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => this.save(repositoryService, () => resolve(true), () => resolve(false)));
  }


  public save(repositoryService: RepositoryService, loadedcallback: BaseEntityCallBack, errorcallback?: ErrorCallBack, completecallback?: () => void, useSuffixInsteadEntityId?: string): void {
    const lastId = this.entityId;
    if (this.isNew()) this.entityId = this.insertWithIds() ? -this.entityId : 0; //the case when entityId<0
    const cloneEntity = this.deleteFields();
    this.entityId = lastId; //the case when entityId<0
    // if (this.isNew()) cloneEntity.entityId = 0;  //the case when entityId<0

    const loadedObj$: Observable<BaseEntity> = !this.isNew() && (useSuffixInsteadEntityId === null || useSuffixInsteadEntityId === undefined) ?
      repositoryService.putDataById<BaseEntity>(this.typeName, this.entityId, cloneEntity) :
      repositoryService.postDataById<BaseEntity>(this.typeName, useSuffixInsteadEntityId ?? this.entityId, cloneEntity);

    loadedObj$.subscribe(
      (entity: BaseEntity) => {
        console.log('loaded', entity);
        this.assign(entity);
        if (!this.isNew() /* means save successed*/) {
          this.lastLoaded = now();
          if (loadedcallback) loadedcallback(this);
        } else {
          this.entityChanged = true;
          if (errorcallback) errorcallback(<ResultSubject>({ errMsg: this.typeName + ': ' + EN_Error.Save, entity: this }));
        }
      }
      , (error: any) => { if (errorcallback) errorcallback(new ResultSubject(error, this, this.typeName + ': ')); }
      , completecallback);
  }


  //delete by Id
  delete(repositoryService: RepositoryService, loadedcallback: BaseEntityCallBack, errorcallback?: ErrorCallBack, completecallback?: () => void): boolean {
    //return this.load_update(EN_HTPPMethod.Delete, dataservice, loadedcallback, errorcallback, extrafilter);
    if (!this.isNew()) {
      repositoryService.deleteDataById<BaseEntity>(this.typeName, this.entityId)
        .subscribe(() => {
          console.log('deleted', this);
          if (loadedcallback) loadedcallback(this);
          return true;
        }
          , (error: any) => { if (errorcallback) errorcallback(new ResultSubject(error, this, this.typeName + ': ')); }
          , completecallback);
    } else this.clear();
    return true;
  }

}
