import { DialogBoxViewModel } from './../modules/dialogs/dialog-box';
import { EVT_REFRESH_HISTORY } from './../ui/history/ui-entity-history';
import { ActionDialogBoxInputParameters } from './../model/common-model';
import { Container } from 'aurelia-framework';
import { Box } from '../modules/box';
import { Config } from '../config';
import { DialogOpenResult, DialogCancellableOpenResult } from 'aurelia-dialog';
import { GridDataSource } from './data-source';
import { IGetRowsParams } from "ag-grid-community";
import { I18N } from 'aurelia-i18n';
import { Repository, IRequestObject } from './repository';
import { EntityManager } from './entity-manager';
import { CustomLogger } from '../modules/custom-logger';
import * as breeze from 'breeze-client';
import moment from 'moment';
import { IAgGridDataSource } from './ag-grid-data-source';
import { UIInternal } from '../utils/ui-internal';
import { KeyboardShortcut } from './../constants/constants';
export interface CustomContextOptions {
  uri: string;
  metadata?: string;
}
export interface OrderByOption {
  propertyPaths: string,
  isDescending?: boolean
}
export interface PagingOption {
  skip?: number,
  take?: number,
  pageNumber?: number
}
export class ServiceBase<T extends breeze.Entity> {
  public manager: breeze.EntityManager;
  public logger: CustomLogger;
  public box: Box;
  public config: Config;
  public i18n: I18N;
  public displayNames: Array<string>;

  public gridDataSource: GridDataSource<T>;
  public repository: Repository<T>;

  constructor(public entityTypeName: string, customContextOptions: CustomContextOptions = null, private resourceName?: string) {
    this.i18n = <I18N>Container.instance.get(I18N);
    let entityManager: EntityManager;
    if (customContextOptions == null || customContextOptions.uri == null || customContextOptions.uri.length == 0) {
      entityManager = <EntityManager>Container.instance.get(EntityManager);
    } else {
      entityManager = new EntityManager(this.i18n, null/* , null */);
      entityManager.initialize(customContextOptions.uri, customContextOptions.metadata);
    }
    this.box = Container.instance.get(Box);
    this.manager = entityManager.manager;
    this.logger = <CustomLogger>Container.instance.get(CustomLogger);
    this.config = <Config>Container.instance.get(Config);

    if (this.resourceName == null || this.resourceName.length == 0) {
      this.resourceName = this.config.globalConfig.resourceNames[this.entityTypeName];
    }
    this.repository = new Repository<T>(this.manager, this.logger, this.entityTypeName, this.resourceName, this.i18n);
    this.getDisplayNames(this.entityTypeName);

    this.gridDataSource = new GridDataSource<T>(this.entityTypeName, entityManager.manager.metadataStore);
    this.gridDataSource.getDataFunction = (params: IGetRowsParams, expands: any = null, quickFilter: string = null, queryCriteria: GridDataSource<T> = null, queryParameters: any = null) => this.fetchForDataGrid(this.resourceName, params, expands, quickFilter, this.gridDataSource, queryParameters);
    this.gridDataSource.getPredicatesFunction = (filterText) => this.getDefaultPredicates(filterText);
  }

  public createEntity(initialValues: Partial<T> = null, sync: boolean = false) {
    let fn = 'createEntity' + (sync ? 'Sync' : '');
    return this.repository[fn](initialValues);
  }

  public createLinkedEntity<E extends breeze.Entity>(linkedEntityName: string, initialValues = null, avoidMultipleAdd = false) {
    var linkedRepository = new Repository<E>(this.manager, this.logger, linkedEntityName, this.config.globalConfig.resourceNames[linkedEntityName], this.i18n);
    var existingEntity = linkedRepository.restoreDeletedEntity(initialValues);
    if (avoidMultipleAdd) {
      existingEntity = linkedRepository.findAddedEntity(initialValues);
    }
    if (existingEntity != null) {
      return Promise.resolve<E>(existingEntity);
    } else {
      return linkedRepository.createEntity(initialValues);
    }
  }

  public deleteEntities(entitiesToDelete: T[], detachInverseFKEntities: boolean, beforeSaveAction: ((entity: T) => void) = null, force: boolean = false): Promise<boolean> {
    let promise = new Promise<boolean>((resolve, reject) => {
      let msg = this.i18n.tr('menu.delete_confirmation', { count: entitiesToDelete.length });
      if (entitiesToDelete.length === 0) {
        this.box.showWarning(this.i18n.tr('general.noRecordsSelected', { ns: "common" }), this.i18n.tr("general.warning", { ns: "common" }));
        resolve(false);
      }
      else {
        let internalDeleteEntities = () => {
          return this.repository.deleteEntities(entitiesToDelete, detachInverseFKEntities, beforeSaveAction)
            .then(() => {
              this.logger.Log(msg, null, null, null);
              this.unselectAll(entitiesToDelete);
              resolve(true);
              UIInternal.broadcast(EVT_REFRESH_HISTORY);
            })
            .catch((errors) => {
              if (errors.entityErrors) {
                this.unselectAll(entitiesToDelete);
                this.repository.cancelChanges(true);
              }
              reject();
            });
        }
        if (force) {
          return internalDeleteEntities();
        }
        else {
          let buttonOk: ActionDialogBoxInputParameters = {
            label: this.i18n.tr("general.remove", { ns: "common" }),
            title: this.i18n.tr("general.remove", { ns: "common" }),
            theme: 'primary',
            type: 'solid',
            disabled: false,
            fn: (thisBox: DialogBoxViewModel) => { thisBox.controller.ok() },
            keyboardShortcut: this.config.globalConfig.keyboardShortcutFwBox ? KeyboardShortcut.Enter : null
          }
          let buttonCancel: ActionDialogBoxInputParameters = {
            label: this.i18n.tr("general.cancel", { ns: "common" }),
            title: this.i18n.tr("general.cancel", { ns: "common" }),
            theme: 'dark',
            type: 'ghost',
            disabled: false,
            fn: (thisBox) => { thisBox.controller.cancel() },
            keyboardShortcut: this.config.globalConfig.keyboardShortcutFwBox ? [KeyboardShortcut.Escape, KeyboardShortcut.Backspace] : null
          };
          this.box.showQuestion(msg, this.i18n.tr("menu.question", { ns: "common" }), [buttonCancel, buttonOk]).then((selectedOption: DialogCancellableOpenResult) => {
            (<DialogOpenResult>selectedOption).closeResult.then((result) => {
              if (result.wasCancelled) {
                resolve(false);
              } else {
                internalDeleteEntities()
              }
            });
          });
        }
      }
    });
    return promise;
  }

  public fetchForDataGrid(resourceNames: string, params: IGetRowsParams, expands: any = null, quickFilter: string = null, queryCriteria: GridDataSource<T>, queryParameters: any = null) {
    return this.repository.fetchForDataGrid(resourceNames, params, expands, quickFilter, queryCriteria, queryParameters);
  }

  public getEntityById(id: number, ...expands: string[]) {
    return this.repository.getEntityById(id, expands);
  }

  public getEntityByIdFromLocalCache(id: number, ...expands: string[]) {
    return this.repository.getEntityById(id, expands, true);
  }

  public getAllEntities(...expands: string[]) {
    return this.repository.getAllEntities(expands);
  }

  public getEntities(predicates: breeze.Predicate = null, expands: string[] = null, parameters: any = null, orderBy?: OrderByOption, paging?: PagingOption) {
    return this.repository.getEntities(predicates, expands, parameters, orderBy, paging);
  }

  public getEntitiesFromDataSource(predicates: breeze.Predicate = null, expands: string[] = null, parameters: any = null, orderBy?: OrderByOption, paging?: PagingOption) {
    return this.repository.getEntities(predicates ?? this.gridDataSource.predicates, expands ?? this.gridDataSource.expands, parameters ?? this.gridDataSource.queryParameters, orderBy ?? this.gridDataSource.queryOrderBy, paging ?? this.gridDataSource.queryPaging);
  }

  public getEntitiesFromLocalCache(predicates: breeze.Predicate = null, expands: string[] = null, orderBy?: OrderByOption, paging?: PagingOption) {
    return this.repository.getEntitiesFromLocalCache(predicates, expands, orderBy, paging);
  }

  public getCount(predicates: breeze.Predicate = null, parameters: any = null) {
    return this.repository.getCount(predicates, parameters);
  }

  public firstEntity(predicates: breeze.Predicate = null, expands: string[] = null, parameters: any = null, orderBy?: OrderByOption, skip?: number) {
    return this.repository.firstEntity(predicates, expands, parameters, orderBy, skip);
  }

  public save(silentSave = true, byPassLocking?: boolean) {
    return this.repository.saveChanges(null, silentSave, byPassLocking).then(() => {
      UIInternal.broadcast(EVT_REFRESH_HISTORY);
    });
  }

  public saveEntity(entityToSave: breeze.Entity, silentSave = false) {
    return this.repository.saveChanges([entityToSave], silentSave).then(() => {
      UIInternal.broadcast(EVT_REFRESH_HISTORY);
    });
  }

  public saveEntities(entitiesToSave: breeze.Entity[], silentSave = false) {
    return this.repository.saveChanges(entitiesToSave, silentSave).then(() => {
      UIInternal.broadcast(EVT_REFRESH_HISTORY);
    });
  }

  public lockRecordForEditing(entity: T) {
    return this.repository.lockRecordForEditing(entity);
  }

  public cancel(silentMode = true) {
    return this.repository.cancelChanges(silentMode);
  }

  public getDefaultPredicates(filterText): any {
    var entityType = this.manager.metadataStore.getEntityType(this.entityTypeName);
    var predicates: breeze.Predicate = null;
    if (filterText != null && filterText.length > 0) {
      entityType.dataProperties.forEach((prop: breeze.DataProperty) => {
        if (prop.dataType === breeze.DataType.String && (<any>prop).enumType == null) {
          if (predicates == null) {
            predicates = new breeze.Predicate(prop.name, breeze.FilterQueryOp.Contains, filterText);
          } else {
            predicates = predicates.or(new breeze.Predicate(prop.name, breeze.FilterQueryOp.Contains, filterText));
          }
        }
      }, this);
    }
    return predicates;
  }

  public rejectAnyEntityChangesFromCache(entityTypeName: string) {
    var addedEntities = this.manager.getEntities(entityTypeName, breeze.EntityState.Added);
    var modifiedEntities = this.manager.getEntities(entityTypeName, breeze.EntityState.Modified);
    var deletedEntities = this.manager.getEntities(entityTypeName, breeze.EntityState.Deleted);
    addedEntities.concat(modifiedEntities).concat(deletedEntities).forEach(elm => { elm.entityAspect.rejectChanges(); });
  }

  private unselectAll(selectedEntities: T[]) {
    selectedEntities = [];
  }

  private getDisplayNames(entityTypeName: string) {
    let metadata = this.manager.metadataStore.getEntityType(entityTypeName, true);
    if (metadata != null) {
      if (this.displayNames == null) {
        this.displayNames = new Array<string>();
      }
      if (metadata.getProperties()?.some(x => this.hasCustomDisplayNameAttribute(x))) {
        let property = metadata.getProperties().find(x => this.hasCustomDisplayNameAttribute(x));
        this.displayNames.push(property.name);
        if (property.isNavigationProperty) {
          this.getDisplayNames((property as breeze.NavigationProperty).entityType.shortName);
        }
      }
    }
  }
  private hasCustomDisplayNameAttribute(property: breeze.IProperty): boolean {
    return property?.custom?.some(y => y?.Name == "DisplayNameAttribute");
  }

  public getDisplayNameValue(entity: T) {
    let displayNameValue: string = null;
    if (this.displayNames != null && entity != null) {
      try {
        let entityDisplay: any = entity;
        this.displayNames.forEach(displayName => {
          entityDisplay = entityDisplay[displayName];
        });

        displayNameValue = entityDisplay;
      }
      catch { }
    }

    if (displayNameValue?.hasOwnProperty('_translation')) {
      displayNameValue = (<any>displayNameValue)._translation;
    }

    return displayNameValue;
  }

  public setAllDetached(entities: breeze.Entity[]) {
    let length: number = entities.length;
    while (length > 0) {
      let entity = entities[length - 1];
      if (entity.entityAspect.entityState.isAdded()) {
        entity.entityAspect.setDetached();
      }
      length--;
    }
  }
}

export class AgGridToBreezeTransformer {
  constructor(private resourceName: string) {
    this.manager = (<EntityManager>Container.instance.get(EntityManager)).manager;
  }

  public getRequestObject(ds: IAgGridDataSource, request: any): IRequestObject {
    const predicates = this.getPredicatesFromParams(request.filterModel, ds.filterText);
    const sortClause = this.getSortClause(request.sortModel);
    return {
      predicates: predicates,
      sortClause: sortClause,
      expands: ds.expands,
      queryParameters: ds.queryParameters,
      start: request.startRow,
      end: request.endRow
    };
  }

  private manager: breeze.EntityManager;
  private getPredicatesFromParams(filterModel: any, filterText: string) {
    var predicates: breeze.Predicate = null;

    var filterPresent = filterModel && Object.keys(filterModel).length > 0;
    if (filterPresent) {
      Object.keys(filterModel).forEach(f => {
        var filter = filterModel[f];
        var pred1, pred2, predX: breeze.Predicate = null;
        // Double filtre sur la même propriété ? 
        if (filter.hasOwnProperty("condition1") &&
          filter.hasOwnProperty("condition2") &&
          filter.hasOwnProperty("operator")) {
          pred1 = this.extractPredicatesFromFilter(filter["condition1"], f);
          pred2 = this.extractPredicatesFromFilter(filter["condition2"], f);
          predX = (filter["operator"] == "AND") ? pred1.and(pred2) : pred1.or(pred2);
        } else {
          predX = this.extractPredicatesFromFilter(filter, f);
        }
        predicates = (predicates == null) ? predX : predicates.and(predX);
      });
    }

    predicates = this.fillPredicatesWithQuickFilter(predicates, filterText);
    return predicates;
  }

  private getSortClause(sortModel: any) {
    if (sortModel && sortModel.length > 0) {
      var sortArray = <any[]>sortModel;
      let orderByClause = "";
      sortArray.forEach(c => {
        if (orderByClause.length > 0) orderByClause += ", ";
        orderByClause += c.colId;
        orderByClause += " " + c.sort;
      });
      return orderByClause;
    } else {
      let entityTypeName = this.manager.metadataStore.getEntityTypeNameForResourceName(this.resourceName);
      let entityType = this.manager.metadataStore.getEntityType(entityTypeName);
      let idDp = entityType.dataProperties.filter(dp => dp.isPartOfKey == true && dp.name == 'id');
      if (idDp != null && idDp.length > 0) {
        return "id asc";
      }
      return null;
    }
    return null;
  }

  private extractPredicatesFromFilter(filter, f) {
    var predicates: breeze.Predicate = null;
    if (filter) {
      if (filter.filterType == 'date') {
        var operator = this.getBreezeOperatorFromFilterType(filter.type);
        if (operator != null) {
          if (operator === breeze.FilterQueryOp.Equals) {
            var dateFrom = moment(filter.dateFrom).toDate();
            var dateTo = filter.type === 'inRange' ? moment(filter.dateTo).add(moment.duration(24 * 3600 * 1000)).toDate() : moment(filter.dateFrom).add(moment.duration(24 * 3600 * 1000)).toDate();
            if (predicates == null) predicates = new breeze.Predicate(f, breeze.FilterQueryOp.GreaterThanOrEqual, dateFrom).and(new breeze.Predicate(f, breeze.FilterQueryOp.LessThanOrEqual, dateTo));
            else predicates = predicates.and(new breeze.Predicate(f, breeze.FilterQueryOp.GreaterThanOrEqual, dateFrom).and(new breeze.Predicate(f, breeze.FilterQueryOp.LessThanOrEqual, dateTo)));
          }
          else if (operator === breeze.FilterQueryOp.NotEquals) {
            var dateFrom = moment(filter.dateFrom).toDate();
            var dateTo = moment(filter.dateFrom).add(moment.duration(24 * 3600 * 1000)).toDate();
            if (predicates == null) predicates = new breeze.Predicate(f, breeze.FilterQueryOp.LessThan, dateFrom).or(new breeze.Predicate(f, breeze.FilterQueryOp.GreaterThanOrEqual, dateTo));
            else predicates = predicates.and(new breeze.Predicate(f, breeze.FilterQueryOp.LessThan, dateFrom).or(new breeze.Predicate(f, breeze.FilterQueryOp.GreaterThanOrEqual, dateTo)));
          }
          else {
            if (predicates == null) predicates = new breeze.Predicate(f, operator, filter.dateFrom);
            else predicates = predicates.and(new breeze.Predicate(f, operator, filter.dateFrom))
          }
        }
      }
      else if (filter.filterType == 'set') {
        var pred: breeze.Predicate = null;
        filter.values.forEach(v => {
          if (pred == null) pred = new breeze.Predicate(f, breeze.FilterQueryOp.Equals, v);
          else pred = pred.or(new breeze.Predicate(f, breeze.FilterQueryOp.Equals, v));
        });
        if (predicates == null) predicates = pred;
        else predicates = predicates.and(pred)
      }
      else if (filter.type != null && filter.filterType == 'text') {
        var operator = this.getBreezeOperatorFromFilterType(filter.type);
        if (predicates == null) predicates = new breeze.Predicate(f, operator, filter.filter);
        else predicates = predicates.and(new breeze.Predicate(f, operator, filter.filter));
      }
      else if (filter.type == null && filter.length > 0) {
        var filterArray = filter as Array<string>;
        var newPredicates: breeze.Predicate = null;
        filterArray.forEach((value) => {
          if (newPredicates == null) {
            newPredicates = new breeze.Predicate(f, breeze.FilterQueryOp.Equals, value);
          }
          else {
            newPredicates = newPredicates.or(new breeze.Predicate(f, breeze.FilterQueryOp.Equals, value));
          }
        });
        if (predicates == null) predicates = newPredicates
        else predicates = predicates.and(newPredicates);
      }
    }
    return predicates;
  }

  private getBreezeOperatorFromFilterType(filterType: string) {
    switch (filterType) {
      case 'contains':
        return breeze.FilterQueryOp.Contains;
      case 'notEqual':
        return breeze.FilterQueryOp.NotEquals;
      case 'inRange':
      case 'equals':
        return breeze.FilterQueryOp.Equals;
      case 'startsWith':
        return breeze.FilterQueryOp.StartsWith;
      case 'endsWith':
        return breeze.FilterQueryOp.EndsWith;
      case 'greaterThan':
        return breeze.FilterQueryOp.GreaterThanOrEqual;
      case 'lessThan':
        return breeze.FilterQueryOp.LessThanOrEqual;
      default:
        return null;
    }
  }

  private fillPredicatesWithQuickFilter(predicates: breeze.Predicate, filterText: string) {
    if (filterText != null && filterText.length > 0) {
      let entityTypeName = this.manager.metadataStore.getEntityTypeNameForResourceName(this.resourceName);
      let entityType = this.manager.metadataStore.getEntityType(entityTypeName);
      var newPredicates: breeze.Predicate = null;
      var index = 0;
      entityType.dataProperties.forEach(dp => {
        if (dp.dataType == breeze.DataType.String && (<any>dp).enumType == null) {
          if (index == 0) {
            newPredicates = new breeze.Predicate(dp.name, breeze.FilterQueryOp.Contains, filterText);
          }
          else {
            newPredicates = newPredicates.or(new breeze.Predicate(dp.name, breeze.FilterQueryOp.Contains, filterText))
          }
          index++;
        }
      });

      if (predicates == null) {
        predicates = newPredicates;
      }
      else {
        predicates = predicates.and(newPredicates);
      }
    }
    return predicates;
  }
}