import { immutable, circular, excludedInJSON, action } from '../decorators';
import {
    config, utils, LEVEL_ENUM, Vertex, MicroService, Logic,
    Service, ActionOptions, isBasicType,
} from '..';
import { interfaceService } from '../../service/data';
import { findUsageService } from '../../service/common';

/**
 * 接口类
 */
export class Interface extends Vertex {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.interface;
    /**
     * Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * InterfaceKey
     */
    @immutable()
    public readonly key: string = undefined;
    /**
     * 名称
     */
    @immutable()
    public readonly name: string = undefined;
    /**
     * 协议
     */
    @immutable()
    public readonly protocol: string = undefined;
    /**
     * 地址
     */
    @immutable()
    public readonly host: string = undefined;
    /**
     * 端口
     */
    @immutable()
    public readonly port: string = undefined;
    /**
     * 路径
     */
    @immutable()
    public readonly path: string = undefined;
    /**
     * 方法
     */
    @immutable()
    public readonly method: string = undefined;
    /**
     * 描述
     */
    @immutable()
    public readonly description: string = undefined;
    /**
     * 请求数据
     */
    @excludedInJSON()
    @immutable()
    public exportedInterface: Interface = undefined;
    /**
     * 逻辑 Id
     */
    @immutable()
    public readonly logicId: string = undefined;
    /**
     * 逻辑
     * @TODO 使用 excludedInJSON 有点问题，不同 class 相同字段会串
     */
    @excludedInJSON()
    @immutable()
    public logic: Logic = undefined;
    /**
     * Service 类型
     */
    @immutable()
    public readonly serviceType:
        'micro' | 'entity' | 'process' | 'processComponent' | 'external' | 'export' = undefined;
    /**
     * Service Id
     */
    @immutable()
    public readonly serviceId: string = undefined;
    /**
     * Service Name
     */
    @immutable()
    public readonly category: string = undefined;
    /**
     * 所属实体 Id
     */
    public entityId: string = undefined;
    /**
     * Service 名称
     */
    @immutable()
    public readonly serviceName: string = undefined;
    /**
     * Service
     */
    @circular()
    @immutable()
    public readonly service: MicroService = undefined;
    /**
     * 参数
     */
    @immutable()
    public parameters: string = undefined;
    /**
     * 返回值
     */
    @immutable()
    public responses: string = undefined;
    /**
     * 请求数据
     */
    @immutable()
    public requestBody: string = undefined;
    /**
     * 树组件的子节点字段
     */
    @excludedInJSON()
    @immutable()
    public readonly moreChildrenFields: Array<string> = ['logic.params', 'logic.returns', 'logic.variables'];
    /**
     * 周边存在的名称
     */
    @excludedInJSON()
    public existingNames: Array<string> = [];
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<Interface>) {
        super();
        source && this.assign(source);
    }
    /**
     * 按当前 id 加载接口数据
     * @requires this.id
     */
    async load() {
        const result = await interfaceService.loadDetail({
            path: {
                id: this.id,
            },
        });

        this.assign(result);
        return this;
    }
    /**
     * 添加接口
     */
    @action('添加接口')
    async create(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');
        if (!this.logicId) {
            this.logic.assign({ name: this.name });
            await this.logic.create();

            this.assign({
                path: this.path || '/api/' + this.name,
                logicId: this.logic.id,
            });
        }
        const body = this.toPlainJSON();
        body.logicId = this.logicId || this.logic.id;
        utils.logger.debug('添加接口', body);
        const result: Interface = await interfaceService.create({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'Interface.create',
                operationDesc: actionOptions?.actionDesc || `添加接口"${this.name}"`,
            },
            body,
        });
        this.pick(result, ['id']);
        this.assign({
            key: `#/${this.service.id}/${this.id}`,
        });

        await then?.();
        this.service.emit('interfacesChange');
        this.service.emit('vertexIdToNameChange', this.id, this.name);
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 删除接口
     */
    @action('删除接口')
    async delete(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        if (this.logic)
            await this.logic.delete();

        if (this.id) {
            await interfaceService.delete({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'Interface.delete',
                    operationDesc: actionOptions?.actionDesc || `删除接口"${this.name}"`,
                },
                path: {
                    id: this.id,
                },
            });
            const index = this.service.interfaces.indexOf(this);
            ~index && this.service.interfaces.splice(index, 1);

            this.service.globalLogic.removeInterface(this);
            this.destroy();
            this.service.syncStructures(); // 处理 CallQueryComponent 嵌套的情形
            this.service.emit('interfacesChange');

            await config.defaultApp?.history.load();
            config.defaultApp?.emit('saved');
        } else {
            const index = this.service.interfaces.indexOf(this);
            ~index && this.service.interfaces.splice(index, 1);
            this.service.globalLogic.removeInterface(this);
        }
    }
    /**
     * 更新接口
     */
    async update(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');

        const body = this.toPlainJSON();
        await interfaceService.update({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'Interface.update',
                operationDesc: actionOptions?.actionDesc || `修改接口"${this.name}"`,
            },
            body,
        });
        await then?.();
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 设置接口名称
     */
    @action('设置接口名称')
    async setName(name: string) {
        const originalData = {
            name: this.name,
            path: this.path,
        };
        this.assign({ name, path: '/api/' + name });
        try {
            await this.update(undefined, {
                actionDesc: '设置接口名称',
            });

            this.service.emit('interfacesChange');
            this.service.emit('vertexIdToNameChange', this.id, this.name);
        } catch (err) {
            this.assign(originalData);
            throw err;
        }
    }
    /**
     * 设置接口描述
     */
    @action('设置接口描述')
    async setDescription(description: string) {
        this.assign({ description });
        await this.update(undefined, {
            actionDesc: '设置接口描述',
        });

        this.service.emit('interfacesChange');
    }
    /**
     * 设置接口路径
     */
    @action('设置接口路径')
    async setPath(path: string) {
        this.assign({ path });
        await this.update(undefined, {
            actionDesc: '设置接口路径',
        });

        this.service.emit('interfacesChange');
    }
    /**
     * 设置接口方法
     */
    @action('设置接口方法')
    async setMethod(method: string) {
        this.assign({ method });
        await this.update(undefined, {
            actionDesc: '设置接口方法',
        });

        this.service.emit('interfacesChange');
    }

    /**
     * 设置接口参数和返回值类型
     */
    @action('设置接口参数和返回值')
    async setParamAndReturn() {
        if (this.serviceType === 'micro' && this.exportedInterface) {
            const logic = this.logic;
            const exportedInterface = this.exportedInterface;
            let bodyParameters: Array<utils.ParamStruts> = [];
            if (exportedInterface.requestBody) {
                const body = JSON.parse(exportedInterface.requestBody);
                const schema = body?.content['application/json']?.schema;
                const name = body?.description?.name || 'content';
                const required = body?.required;
                bodyParameters = [{
                    name,
                    schema,
                    in: 'body',
                    required,
                    defaultValue: '',
                }];
            }

            const params: Array<utils.ParamStruts> = Object.values(JSON.parse(exportedInterface.parameters || '{}')).map((p: utils.ParamStruts) => ({
                name: p.name,
                schema: p.schema,
                in: p.in,
                required: true,
                defaultValue: '',
            }));

            const parameters = params.concat(bodyParameters).filter((p) => !!p);

            const finnalParameter: Array<utils.ParamStruts> = [];
            if (logic) {
                logic.params.forEach((p) => {
                    const finded = parameters.find((param) => param.name === p.name);
                    const isBasic = isBasicType(p.schema);
                    if (finded) {
                        finnalParameter.push({
                            name: p.name,
                            schema: p.schema,
                            in: isBasic ? 'query' : 'body',
                            required: finded.required,
                            defaultValue: finded.defaultValue,
                        });
                    } else {
                        finnalParameter.push({
                            name: p.name,
                            schema: p.schema,
                            in: isBasic ? 'query' : 'body',
                            required: !isBasic,
                            defaultValue: '',
                        });
                    }
                });
            }
            let finnalResponses = '';
            if (logic.returns.length > 0) {
                const p = logic.returns[0];
                const payload = {
                    description: {
                        name: p.name,
                    },
                    content: {
                        'application/json': {
                            schema: p.schema,
                        },
                    },
                };
                finnalResponses = JSON.stringify({
                    200: payload,
                });
            }

            const {
                parameters: finnalP,
                requestBody: finnalB,
            }: utils.resolvedParameter = utils.resolveParameter(finnalParameter);

            exportedInterface.parameters = finnalP;
            exportedInterface.requestBody = finnalB;
            exportedInterface.responses = finnalResponses;
            await exportedInterface.update();
        }
        this.service.emit('interfacesChange');
    }

    /**
     * 查找引用
     */
    @action('查找引用')
    async findUsage() {
        let id = this.id;
        if (['entity', 'micro', 'process', 'processComponent'].includes(this.serviceType)) {
            id = this.logicId;
        }
        const result = await findUsageService.findUsage({
            query: {
                id,
            },
        });

        return result;
    }

    /**
     * 从后端 JSON 生成规范的 Interface 对象
     */
    public static from(source: any, service: Service) {
        const item = new Interface(source);
        item.assign({
            service,
            expanded: false,
            key: `#/${service.id}/${item.id}`,
        });
        item.logic && item.assign({ logic: Logic.from(item.logic, item) });

        return item;
    }
}

export default Interface;
