import { HttpClient } from 'aurelia-fetch-client';
import * as breeze from 'breeze-client';
import { autoinject, inject, Container, computedFrom } from 'aurelia-framework';
import { I18N } from 'aurelia-i18n';
import { BreezeDeletedSignal, BreezeSyncHubClient } from '../services/signalr/breeze-sync-hub-client';
import { UIInternal } from '../utils/ui-internal';

@inject(I18N)
@autoinject
export class EntityManager {
    private instance: breeze.EntityManager;
    constructor(private i18n: I18N, private client: HttpClient) { }

    public initialize(uri: string, metadata: string = null) {
        breeze.NamingConvention.camelCase.setAsDefault();
        // Traduction des messages d'erreur dans la langue de l'utilisateur
        breeze.Validator.messageTemplates.required = this.i18n.tr("validation.required", { ns: "common" });
        breeze.Validator.messageTemplates.date = this.i18n.tr("validation.date", { ns: "common" });
        breeze.Validator.messageTemplates.emailAddress = this.i18n.tr("validation.emailAddress", { ns: "common" });
        breeze.Validator.messageTemplates.number = this.i18n.tr("validation.number", { ns: "common" });
        breeze.Validator.messageTemplates.string = this.i18n.tr("validation.string", { ns: "common" });
        breeze.Validator.messageTemplates.phone = this.i18n.tr("validation.phone", { ns: "common" });
        this.instance = this.createEntityManager(uri, metadata);
        UIInternal.subscribe(UIInternal.EVT_CHANGE_LANGUAGE, () => {
            this.setTranslationMessagesIntoMetaData();
        });
    }

    public setTranslationMessagesIntoMetaData() {
        this.instance.metadataStore.getEntityTypes().forEach(entityType => {
            entityType.getProperties().forEach(prop => {
                prop.validators.filter(v => v.context?.messageTemplate != null && v.name != null).forEach(v => {
                    v.context.messageTemplate = this.i18n.tr("validation." + v.name, { ns: "common" });
                });
            });
        });
    }

    createEntityManager(uri: string, metadata: string) {
        // define the Breeze `DataService` for this app
        var dataService = new breeze.DataService({
            serviceName: uri,
            hasServerMetadata: metadata == null  // don't ask the server for metadata
        });

        // create the metadataStore 
        var metadataStore = new breeze.MetadataStore();

        if (metadata != null) metadataStore.importMetadata(metadata);

        // create a new EntityManager that uses this metadataStore
        return new breeze.EntityManager({
            dataService: dataService,
            metadataStore: metadataStore
        });
    }

    public async initializeMetadataStore(configureMetadataStore: (metaDatastore, manager) => any = null) {
        await this._initializeMetadataStore(configureMetadataStore);
    }
    private async _initializeMetadataStore(configureMetadataStore: (metaDatastore, manager) => any = null, nbrRetriesRemaining: number = 10) {
        if (this.instance.metadataStore.isEmpty()) {
            if (this.client == null) {
                this.client = <HttpClient>Container.instance.get(HttpClient);
            }
            try {
                var metaReq = await this.client.fetch(this.manager.dataService.serviceName + 'Metadata');
                if (metaReq.ok) {
                    var metadata = await metaReq.json();
                    var metadataText = JSON.stringify(metadata);
                    this.instance.metadataStore.importMetadata(metadataText);
                    if (metadata.altMetadata && !metadata.schema) {
                        this.instance.metadataStore.importMetadata(metadata.altMetadata, true);
                    }
                    if (this.instance.metadataStore.getDataService(this.manager.dataService.serviceName) == null) {
                        this.instance.metadataStore.addDataService(new breeze.DataService({
                            serviceName: this.manager.dataService.serviceName,
                            hasServerMetadata: true // don't ask the server for metadata
                        }));
                    }
                }
                if (configureMetadataStore != null) configureMetadataStore(this.instance.metadataStore, this.instance);
            } catch (ex) {
                // Sometimes the initialization fails (network tab shows answer, but JS fails)
                // Retrying seems to solve the problem (as if the initialization was done too soon)
                if (nbrRetriesRemaining > 0) {
                    console.warn("InitializeMetadataStore failed, retrying. Exception: ", ex);
                    await this._initializeMetadataStore(configureMetadataStore, --nbrRetriesRemaining);
                } else {
                    throw ex;
                }
            }
        }
    }

    @computedFrom("instance")
    public get manager(): breeze.EntityManager {
        if (this.instance == null) throw new Error("Please initialize EntityManager before using it");

        return this.instance;
    }

    // Because of cyclic dependencies, the hub cannot be instatiated on the entity-manager class
    public async subscribeToBreezeSyncHub(hub: BreezeSyncHubClient) {
        await hub.whenReady();
        hub.onDeleted((entities: BreezeDeletedSignal[]) => this.remoteEntitiesDeleted(entities));
    }

    private remoteEntitiesDeleted(entities: BreezeDeletedSignal[]) {
        entities.forEach(signal => {
            let entity = this.manager.getEntityByKey(signal.EntityName, signal.Id);
            if (entity != null && false == entity.entityAspect.isBeingSaved) {
                entity.entityAspect.setDetached();
            }
        })
    }
}