import {ModelInterface} from './ModelInterface';
import {GetItemsAction} from '../Actions/GetItemsAction/GetItemsAction';
import {CRUDAction} from '../Actions/CRUDActions/CRUDAction';
import {CustomAction} from '../Actions/CustomAction/CustomAction';
import {GetModelMetadataAction} from '../Actions/GetMetadataAction/GetModelMetadataAction';
import {MetaDataInterface} from './MetaDataInterface';
import {EventObserver} from '../Actions/NetworkRequests/SocketConnection/Observer';
import {GlobalVariables} from '../GlobalVariables';
import {RoutingKeyParams} from '../Actions/Interfaces/RoutingKeyParams';
import {ModelConnection} from './ModelConnection';

const observer: EventObserver = EventObserver.getInstance();

export class Model implements ModelInterface {
    modelName: string;
    username: string;
    password: string;
    modelMetaData!: MetaDataInterface;
    modelItems: (string | object)[];
    modelActionList: string[];
    modelValidationRules: object;
    modelActionsMetaData: object;
    actionResponse: string | object;
    databaseFields: string[];
    fieldsWithTypes: object[];
    allModelsMetadata: string | object;
    tokenUst: string;
    tokenUmt: boolean;

    constructor(username: string, password: string, modelName: string) {
        this.modelName = modelName;
        this.username = username;
        this.password = password;
        this.modelItems = [];
        this.modelActionList = [];
        this.modelValidationRules = {};
        this.modelActionsMetaData = {};
        this.actionResponse = [];
        this.databaseFields = [];
        this.fieldsWithTypes = [];
        this.allModelsMetadata = {};
        this.tokenUmt = false;
        this.tokenUst = '';
    }

    setAuthToken(token: string): any {
        GlobalVariables.tokenUST = token;
    }

    /**
     * Получение метаданных модели
     * @param microserviceName
     * @param connectionType
     */
    actionGetMetadata(microserviceName: string, connectionType: string): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeGetMetadataRequest = new GetModelMetadataAction(
            microserviceName,
            'getMetadata',
            this.modelName
        );
        new ModelConnection().createConnection(connectionType, initializeGetMetadataRequest);
    }

    /**
     * Получение данных модели с возможными параметрами, юзер передает все данные вместе, но в экшен их нужно передавать отдельно
     * @param microserviceName
     * @param connectionType
     * @param withs
     * @param filter
     * @param orders
     * @param page
     * @param perPage
     * @param actionName
     */
    actionGetItems(
        microserviceName: string,
        connectionType: string,
        perPage?: number,
        page?: number,
        filter?: (string | object)[] | undefined,
        withs?: string | string[],
        orders?: string[][],
        actionName?: string
    ): any {
        let userGetItems = actionName !== undefined ? actionName : 'getItems';
        GlobalVariables.tokenUST = microserviceName;
        const initializeGetItems = new GetItemsAction(microserviceName, this.modelName, userGetItems);
        initializeGetItems.actionParameters.with(withs);
        initializeGetItems.actionParameters.filters(filter);
        initializeGetItems.actionParameters.orders(orders);
        if (perPage !== undefined && page !== undefined) {
            initializeGetItems.actionParameters.setPagination(perPage, page);
        }
        new ModelConnection().createConnection(connectionType, initializeGetItems);
    }

    /**
     * Отличается от getItems тем, что отдельно должен быть передан айди нужной записи
     * @param microserviceName
     * @param connectionType
     * @param id
     * @param withs
     * @param filter
     * @param orders
     */
    actionGetItem(
        microserviceName: string,
        connectionType: string,
        id: string,
        filter?: (string | object)[] | undefined,
        withs?: [],
        orders?: string[][]
    ): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeGetItem = new GetItemsAction(microserviceName, this.modelName, 'getItem');
        initializeGetItem.actionParameters.with(withs);
        initializeGetItem.actionParameters.filters(filter);
        initializeGetItem.actionParameters.orders(orders);
        initializeGetItem.actionParameters.setId(id);
        new ModelConnection().createConnection(connectionType, initializeGetItem);
    }

    /**
     * Экшен обновления записи
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */
    actionUpdate(microserviceName: string, connectionType: string, actionParams: object): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionUpdate = new CRUDAction(
            microserviceName,
            this.modelName,
            'update',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionUpdate);
    }

    /**
     *
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */

    actionUpdateMany(microserviceName: string, connectionType: string, actionParams: object): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionUpdate = new CRUDAction(
            microserviceName,
            this.modelName,
            'updateMany',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionUpdate);
    }

    /**
     * Экшен обновляет записи, соответствующие заданным фильтрам
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */
    actionUpdateManyWithFilter(
        microserviceName: string,
        connectionType: string,
        actionParams: object
    ): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionUpdateManyWithFilter = new CRUDAction(
            microserviceName,
            this.modelName,
            'updateManyRaw',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionUpdateManyWithFilter);
    }

    /**
     * Экшен создания записи
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     * @param channelParameters
     */
    actionCreate(
        microserviceName: string,
        connectionType: string,
        actionParams: object,
        channelParameters?: RoutingKeyParams
    ): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionCreate = new CRUDAction(
            microserviceName,
            this.modelName,
            'create',
            actionParams,
            channelParameters
        );
        new ModelConnection().createConnection(connectionType, initializeActionCreate);
    }

    /**
     *
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */

    actionCreateMany(microserviceName: string, connectionType: string, actionParams: object): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionCreate = new CRUDAction(
            microserviceName,
            this.modelName,
            'createMany',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionCreate);
    }

    /**
     * Экшен удаления записи
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */
    actionDelete(microserviceName: string, connectionType: string, actionParams: string[]): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionDelete = new CRUDAction(
            microserviceName,
            this.modelName,
            'delete',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionDelete);
    }

    /**
     *
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */

    actionDeleteMany(microserviceName: string, connectionType: string, actionParams: string[]): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionDelete = new CRUDAction(
            microserviceName,
            this.modelName,
            'deleteMany',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionDelete);
    }

    /**
     * Экшен удаляет записи, соответствующие заданным фильтрам
     * @param microserviceName
     * @param connectionType
     * @param actionParams
     */
    actionDeleteManyWithFilter(
        microserviceName: string,
        connectionType: string,
        actionParams: object
    ): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionDeleteManyWithFilter = new CRUDAction(
            microserviceName,
            this.modelName,
            'deleteManyRaw',
            actionParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionDeleteManyWithFilter);
    }

    /**
     *
     * @param microserviceName
     * @param actionName
     * @param connectionType
     * @param actionParams
     */

    actionCustom(
        microserviceName: string,
        actionName: string,
        connectionType: string,
        actionParams?: object,
        additionalParams?: any
    ): any {
        GlobalVariables.tokenUST = microserviceName;
        const initializeActionCustom = new CustomAction(
            microserviceName,
            this.modelName,
            actionName,
            actionParams,
            additionalParams
        );
        new ModelConnection().createConnection(connectionType, initializeActionCustom);
    }

    /**
     *   позволяет получить метадату текущей модели
     */
    getModelMetadata(): any {
        return this.modelMetaData;
    }

    /**
     * позволяет получить список доступных модели экшенов (получается с бэка в составе метадаты)
     */
    getModelActionList(): any {
        this.modelActionList = Object.keys(this.modelMetaData.actions_metadata);
        return this.modelActionList;
    }

    /**
     *  позволяет получить правила валидации для всех доступных филдов
     */
    getModelValidationRules(): any {
        return this.modelValidationRules;
    }

    /**
     *  позволяет получить расширинную информацию по каждому экшену
     */
    getModelActionsMetaData(): any {
        this.modelActionsMetaData = this.modelMetaData.actions_metadata;
        return this.modelActionsMetaData;
    }

    /**
     *  позволяет получить филды с типом base
     */
    getModelDataBaseFields(): any {
        this.databaseFields = this.modelMetaData.database_fields;
        return this.databaseFields;
    }

    /**
     *  позволяет получить филды с указанными типами
     */
    getModelFieldsWithTypes(): any {
        this.fieldsWithTypes = this.modelMetaData.fields_with_types;
        return this.fieldsWithTypes;
    }

    /**
     * позволяет получить отфильтрованные список айтемов.
     * в функцию передается массив с названиями филдов от пользователя (fields), массив всех айтемов,
     * которые нужно отфильтровать (dataToFilter) и указание типа фильтрации (filterType): includes возвращаяет только филды,
     * указанные в массиве, excludes исключает филды, указанные в массиве
     *
     * @param fields
     * @param filterType
     * @param dataToFilter
     */

    // getSpecificFields(fields: string[], filterType: string, dataToFilter: object[]) {
    // if (filterType === 'includes') return new DataFormatter(fields, dataToFilter).include();
    // return new DataFormatter(fields, dataToFilter).exclude();
    // }

    /**
     * позволяет получить уже запрошенные айтемы модели
     */
    getItems(): any {
        return this.modelItems;
    }

    /**
     * позволяет получить уже запрошенную метадату всех моделей приложения
     */
    getAllModelsMetadata(): any {
        return this.allModelsMetadata;
    }

    /**
     * Функция используется для установки основного домена при начале работы с моделью
     * @param URL
     */

    setBaseUrl(URL: string): void {
        GlobalVariables.httpBaseUrl = URL;
    }

    socketDisconnect(): any {
        observer.broadcastSocketDisconnect('disconnect');
    }
}
