import { immutable, circular, excludedInJSON, action } from '../decorators';
import {
    config, utils, LEVEL_ENUM, Vertex, DataNode, Service, Structure,
    StructureProperty, MicroService, Schema, Interface, Param, Return,
    Variable, ObjectSchema, dataTypesMap, convert2RefType,
    updateDataTypeList, EntityProperty, ActionOptions, EntityIndex,
} from '..';
import { entityService, interfaceService, structureService } from '../../service/data';

import { vertexsMap } from '../cacheData';

export interface Resolver {
    level: 'resolver';
    name: string;
    interface: Interface;
    entity: Entity;
    isLeaf: boolean;
}

export const systemProperty: { [name: string]: any, } = {
    id: {
        name: 'id',
        label: '主键',
        description: '主键',
        primaryKey: true,
        typeKey: '#/basicTypes/Long',
        type: 'integer',
        format: 'long',
        editable: false,
        display: {
            inTable: false,
            inFilter: false,
            inForm: false,
            inDetail: false,
        },
    },
    createdTime: {
        name: 'createdTime',
        label: '创建时间',
        description: '创建时间',
        typeKey: '#/basicTypes/DateTime',
        type: 'string',
        format: 'date-time',
        editable: false,
        display: {
            inTable: true,
            inFilter: false,
            inForm: false,
            inDetail: false,
        },
    },
    updatedTime: {
        name: 'updatedTime',
        label: '更新时间',
        description: '更新时间',
        typeKey: '#/basicTypes/DateTime',
        type: 'string',
        format: 'date-time',
        editable: false,
        display: {
            inTable: true,
            inFilter: false,
            inForm: false,
            inDetail: false,
        },
    },
    createdBy: {
        name: 'createdBy',
        label: '创建者',
        description: '创建者',
        typeKey: '#/basicTypes/String',
        type: 'string',
        format: '',
        editable: false,
        display: {
            inTable: false,
            inFilter: false,
            inForm: false,
            inDetail: false,
        },
    },
    updatedBy: {
        name: 'updatedBy',
        label: '更新者',
        description: '更新者',
        typeKey: '#/basicTypes/String',
        type: 'string',
        format: '',
        editable: false,
        display: {
            inTable: false,
            inFilter: false,
            inForm: false,
            inDetail: false,
        },
    },
};

/**
 * 实体类
 */
export class Entity extends Vertex implements ObjectSchema {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.entity;
    /**
     * 实体 Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * dataTypes 中的唯一标识
     */
    @immutable()
    public readonly schemaRef: string = undefined;
    /**
     * 实体名称
     */
    @immutable()
    public readonly name: string = undefined;
    /**
     * 实体描述
     */
    @immutable()
    public readonly description: string = undefined;
    /**
     * 实体类型（如enum）
     */
    @immutable()
    public readonly type: 'object' = 'object';
    /**
     * 实体属性列表
     */
    @immutable()
    public readonly propertyList: Array<EntityProperty> = [];
    /**
     * 实体索引列表
     */
    @immutable()
    public readonly indexList: Array<EntityIndex> = [];

    @excludedInJSON()
    @immutable()
    public readonly resolvers: Array<Resolver> = [];
    /**
     * 所属服务 Id
     */
    @immutable()
    public readonly serviceId: string = undefined;
    /**
     * 所属服务类型
     */
    @immutable()
    public readonly serviceType: string = undefined;
    /**
     * 所属服务
     */
    @immutable()
    public readonly service: MicroService = undefined;
    /**
     * 上个版本数据
     */
    @immutable()
    @excludedInJSON()
    public readonly lastVersionDef: any = undefined;
    /**
     * 父节点
     */
    @circular()
    @immutable()
    public readonly dataNode: DataNode = undefined;
    /**
     * 周边存在的名称
     */
    @excludedInJSON()
    public existingNames: Array<string> = [];
    static properties2PropertyList: any;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<Entity>) {
        super();
        source && this.assign(source);
    }
    /**
     * 按当前 id 加载实体数据
     * @requires this.id
     */
    async load() {
        const result = await entityService.loadDetail({
            query: {
                id: this.id,
            },
            config: {
                mock: config.mock,
            },
        });

        this.assign(result);
        return this;
    }
    /**
     * 添加实体
     */
    @action('添加实体')
    async create(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving', true);

        Object.keys(systemProperty).forEach((propertyKey) => {
            this.propertyList.push(new EntityProperty(Object.assign({ root: this }, systemProperty[propertyKey])));
        });

        const body = this.toJSON();
        body.propertyList.forEach((property: any, index: number) => property._posIndex = index);

        utils.logger.debug('添加实体', body);
        try {
            const result: Entity = await entityService.create({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'Entity.create',
                    operationDesc: actionOptions?.actionDesc || `添加实体"${this.name}"`,
                },
                body,
            });
            this.deepPick(result, ['id']);
            this.emit('created');

            this.assign({
                schemaRef: `#/${this.dataNode.service.id}/${result.id}`,
            });
            dataTypesMap[this.schemaRef] = this;

            // 先改成异步的
            Promise.all([
                this.syncInterfaces(),
            ]).then(() => {
                updateDataTypeList();
                this.dataNode.service.emit('dataTypesChange');
                this.dataNode.service.emit('vertexIdToNameChange', this.id, this.name);
            });

            await config.defaultApp?.history.load();
            config.defaultApp?.emit('saved');
            return this;
        } catch (err) {
            config.defaultApp?.emit('saved');
            throw err;
        }
    }

    /**
     * 修改实体
     */
    async update(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving', true);

        const body = this.toJSON('', ['propertyList', 'schemaRef']);
        utils.logger.debug('修改实体', body);
        const result: Entity = await entityService.update({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'Entity.update',
                operationDesc: actionOptions?.actionDesc || `修改实体"${this.name}"`,
            },
            body,
        });
        await then?.();
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 删除实体
     */
    @action('删除实体')
    async delete(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving', true);

        if (this.id) {
            await entityService.delete({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'Entity.delete',
                    operationDesc: actionOptions?.actionDesc || `删除实体"${this.name}"`,
                },
                query: {
                    id: this.id,
                },
            });
        }

        const index = this.dataNode.entities.indexOf(this);
        ~index && this.dataNode.entities.splice(index, 1);
        delete dataTypesMap[this.schemaRef];
        if (this.id) {
            await this.syncInterfaces(true);
        }

        updateDataTypeList();

        this.destroy();
        this.dataNode.service.emit('dataTypesChange');

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
    }
    /**
     * 设置实体名称
     * @param name 名称
     */
    @action('设置实体名称')
    async setName(name: string) {
        const oldName = this.name;
        this.assign({ name });
        await this.update(undefined, {
            actionDesc: `设置实体"${oldName}"的名称为"${name}"`,
        }, async () => {
            await this.syncInterfaces();
        });

        updateDataTypeList();
        this.dataNode.service.emit('dataTypesChange');
        this.dataNode.service.emit('vertexIdToNameChange', this.id, this.name);
    }
    /**
     * 设置实体描述
     * @param description 描述
     */
    @action('设置实体描述')
    async setDescription(description: string) {
        this.assign({ description });
        await this.update(undefined, {
            actionDesc: `设置实体"${this.name}"的描述为"${description}"`,
        });
    }
    /**
     * 同步interfaces
     * interface、logic、params、returns、variabes采用plainAssign
     * 创建、删除实体，修改实体名称、增删改实体property需要同步
    */
    async syncInterfaces(isDelete?: boolean) {
        if (isDelete) {
            const service = this.dataNode.service;
            const newInterfaces = service.interfaces.filter((itface) => itface.logic.entityId !== this.id);
            service.assign({ interfaces: newInterfaces });
        } else {
            const service = this.dataNode.service;
            const interfaces: Array<Interface> = await interfaceService.loadList({
                query: {
                    serviceId: service.id,
                    entityId: this.id,
                },
            });

            const entity = this;
            entity.assign({ resolvers: [] });

            let newInterfaces: Array<Interface> = [];

            interfaces.forEach((itface) => {
                if (itface.serviceType === 'export') {
                    return;
                }
                let tempItface = <Interface>vertexsMap.get(itface.id);
                if (tempItface) {
                    tempItface.plainAssign(itface);
                    if (tempItface.logic) {
                        tempItface.logic.plainAssign(itface.logic);
                        const params: Array<Param> = [];
                        const returns: Array<Return> = [];
                        const variables: Array<Variable> = [];
                        itface.logic.params.forEach((param) => {
                            const tempParam = <Param>vertexsMap.get(param.id);
                            if (tempParam) {
                                tempParam.plainAssign(param);
                                tempParam.genSchemaChildren();
                                params.push(tempParam);
                            } else {
                                params.push(Param.from(param, tempItface.logic));
                            }
                        });
                        tempItface.logic.assign({ params });
                        itface.logic.returns.forEach((ret) => {
                            const tempRet = <Return>vertexsMap.get(ret.id);
                            if (tempRet) {
                                tempRet.plainAssign(ret);
                                tempRet.genSchemaChildren();
                                returns.push(tempRet);
                            } else {
                                returns.push(Return.from(ret, tempItface.logic));
                            }
                        });
                        tempItface.logic.assign({ returns });
                        itface.logic.variables.forEach((variable) => {
                            const tempVariable = <Variable>vertexsMap.get(variable.id);
                            if (tempVariable) {
                                tempVariable.plainAssign(variable);
                                tempVariable.genSchemaChildren();
                                variables.push(tempVariable);
                            } else {
                                variables.push(Variable.from(variable, tempItface.logic));
                            }
                        });
                        tempItface.logic.assign({ variables });
                    }
                } else {
                    tempItface = Interface.from(itface, service);
                    tempItface.entityId = entity.id;
                    newInterfaces.push(tempItface);
                }
                // 重新生成resolvers
                // const RESOLVER_NAMES = ['getAll', 'get', 'create', 'update', 'delete', 'count', 'import', 'export', 'batchDelete', 'batchCreate', 'batchUpdate'];
                // if (tempItface.logic && tempItface.logic.entityId) {
                //     entity.assign({ resolvers: entity.resolvers || [] });
                //     const resolverName = RESOLVER_NAMES.find((name) => tempItface.name.startsWith(name));
                //     entity.resolvers.push({
                //         level: 'resolver',
                //         name: resolverName,
                //         interface: tempItface,
                //         entity,
                //         isLeaf: true,
                //     });
                // }
            });
            newInterfaces = newInterfaces.concat(service.interfaces);
            service.assign({ interfaces: newInterfaces });
            (service as MicroService).mountResolverOnEntity();
            (service as MicroService).mountResolverOnInterface();
        }
        this.dataNode.service.emit('interfacesChange');
    }
    /**
     * 同步structures
     * 创建、删除实体，修改实体名称需要同步
    */
    async syncStructures(isDelete?: boolean) {
        if (isDelete) {
            const service = this.dataNode.service;
            const newStructures = service.data.structures.filter((structure) => structure.entityId !== this.id);
            service.data.assign({ structures: newStructures });
            Object.keys(dataTypesMap).forEach((dataTypeKey) => {
                if ((dataTypesMap[dataTypeKey] as any).entityId === this.id) {
                    delete dataTypesMap[dataTypeKey];
                }
            });
        } else {
            const service = this.dataNode.service;
            const structures: Array<Structure> = await structureService.loadList({
                query: {
                    id: service.id,
                },
            });

            const newStructures: Array<Structure> = [];
            structures.forEach((structure) => {
                let structureInstance = <Structure>vertexsMap.get(structure.id);
                if (structureInstance) {
                    structureInstance.plainAssign(structure);
                } else {
                    convert2RefType(structure as any);
                    structureInstance = new Structure(structure);
                    structureInstance.assign({
                        dataNode: this.dataNode,
                        schemaRef: `#/${service.id}/${structure.id}`,
                    });

                    const propertyList = structureInstance.propertyList as Array<StructureProperty>;
                    propertyList.forEach((property, index) => {
                        property = propertyList[index] = new StructureProperty(property);
                        property.assign({ root: structureInstance });
                    });
                    structureInstance.assign({ resolvers: undefined });

                    dataTypesMap[structureInstance.schemaRef] = structureInstance as Schema;
                }
                newStructures.push(structureInstance);
            });
            service.data.assign({ structures: newStructures });
            return newStructures;
        }
    }

    /**
     * 从后端 JSON 生成规范的 Entity 对象
     */
    public static from(source: any, service: Service) {
        convert2RefType(source);
        const entity = new Entity(source);

        entity.assign({
            dataNode: service.data,
            schemaRef: `#/${service.id}/${entity.id}`,
        });

        const propertyList = entity.propertyList as Array<EntityProperty>;
        propertyList.forEach((property, index) => {
            property = propertyList[index] = new EntityProperty(property);
            property.assign({ root: entity });
        });
        entity.assign({ resolvers: undefined });

        const indexList = entity.indexList as Array<EntityIndex>;
        indexList.forEach((indexItem, index) => {
            indexItem = indexList[index] = new EntityIndex(indexItem);
            indexItem.assign({ root: entity });
        });

        dataTypesMap[entity.schemaRef] = entity;
        return entity;
    }
}

export default Entity;
