/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import SysTestKit from './module/kit/SysTestKit';
import { TAG } from './Constant';
import LogExpectError from './module/report/LogExpectError';
import { NestFilter } from './module/config/Filter';

function assertTrueFun(actualValue) {
    let result = {
        pass: (actualValue) === true,
        message: 'expect true, actualValue is ' + actualValue
    };
    return result;
}

function assertEqualFun(actualValue, args) {
    let msg = 'expect ' + actualValue + ' equals ' + args[0];
    if (actualValue === args[0]) { // 数值相同,提示数据类型
        const aClassName = Object.prototype.toString.call(actualValue);
        const bClassName = Object.prototype.toString.call(args[0]);
        msg = 'expect ' + actualValue + aClassName + ' equals ' + args[0] + bClassName + 'strict mode inspect type';
    }
    let result = {
        pass: (actualValue) === args[0],
        expectValue: args[0],
        message: msg
    };
    return result;
}

function assertThrowFun(actual, args) {
    const result = {
        pass: false
    };
    if (typeof actual !== 'function') {
        result.message = 'toThrow\'s Actual should be a Function';
    } else {
        let hasThrow = false;
        let throwError;
        try {
            actual();
        } catch (e) {
            hasThrow = true;
            throwError = e;
        }
        if (!hasThrow) {
            result.message = 'function did not throw an exception';
        } else if (throwError && throwError.message === args[0]) {
            result.pass = true;
        } else {
            result.message = `expect to throw ${args[0]} , actual throw ${throwError.message}`;
        }
    }
    return result;
}

class AssertException extends Error {
    constructor(message) {
        super();
        this.name = 'AssertException';
        this.message = message;
    }
}

function getFuncWithArgsZero(func, timeout, isStressTest) {
    return new Promise(async (resolve, reject) => {
        let timer = null;
        if (!isStressTest) {
            timer = setTimeout(() => {
                reject(new Error('execute timeout ' + timeout + 'ms'));
            }, timeout);
        }
        try {
            await func();
        } catch (err) {
            reject(err);
        }
        timer !== null ? clearTimeout(timer) : null;
        resolve();
    });
}

function getFuncWithArgsOne(func, timeout, isStressTest) {
    return new Promise(async (resolve, reject) => {
        let timer = null;
        if (!isStressTest) {
            timer = setTimeout(() => {
                reject(new Error('execute timeout ' + timeout + 'ms'));
            }, timeout);
        }

        function done() {
            timer !== null ? clearTimeout(timer) : null;
            resolve();
        }

        try {
            await func(done);
        } catch (err) {
            timer !== null ? clearTimeout(timer) : null;
            reject(err);
        }
    });
}

function getFuncWithArgsTwo(func, timeout, paramItem, isStressTest) {
    return new Promise(async (resolve, reject) => {
        let timer = null;
        if (!isStressTest) {
            timer = setTimeout(() => {
                reject(new Error('execute timeout ' + timeout + 'ms'));
            }, timeout);
        }

        function done() {
            timer !== null ? clearTimeout(timer) : null;
            resolve();
        }

        try {
            await func(done, paramItem);
        } catch (err) {
            timer !== null ? clearTimeout(timer) : null;
            reject(err);
        }
    });
}

function processFunc(coreContext, func) {
    let argNames = ((func || '').toString()
        .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '')
        .match(/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m) || ['', '', ''])[2]
        .split(',') // split parameters
        .map(item => item.replace(/^\s*(_?)(.+?)\1\s*$/, name => name.split('=')[0].trim()))
        .filter(String);
    let funcLen = func.length;
    let processedFunc;
    const config = coreContext.getDefaultService('config');
    config.setSupportAsync(true);
    const timeout = + (config.timeout === undefined ? 5000 : config.timeout);
    const isStressTest = (coreContext.getServices('dataDriver') !== undefined || config.getStress() > 1);
    switch (funcLen) {
        case 0: {
            processedFunc = function () {
                return getFuncWithArgsZero(func, timeout, isStressTest);
            };
            break;
        }
        case 1: {
            processedFunc = function () {
                return getFuncWithArgsOne(func, timeout, isStressTest);
            };
            break;
        }
        default: {
            processedFunc = function (paramItem) {
                return getFuncWithArgsTwo(func, timeout, paramItem, isStressTest);
            };
            break;
        }
    }
    return processedFunc;
}

function secureRandomNumber() {
    return crypto.randomBytes(8).readUInt32LE() / 0xffffffff;
}



class SuiteService {
    constructor(attr) {
        this.id = attr.id;
        this.rootSuite = new SuiteService.Suite({});
        this.currentRunningSuite = this.rootSuite;
        this.suitesStack = [this.rootSuite];
        this.targetSuiteArray = [];
        this.targetSpecArray = [];
        this.currentRunningSuiteDesc = null;
        this.fullRun = false;
        this.isSkipSuite = false;
        this.suiteSkipReason = null;
    }

    describe(desc, func) {
        const configService = this.coreContext.getDefaultService('config');
        if (this.suitesStack.some(suite => { return suite.description === desc })) {
            console.error(`${TAG} Loop nesting occurs : ${desc}`);
            this.suiteSkipReason = '';
            this.isSkipSuite = false;
            return;
        }
        let isFilter = this.analyzeConfigServiceClass(configService.class, desc);
        if (configService.filterSuite(desc) && isFilter) {
            if (this.currentRunningSuite.description === '' || this.currentRunningSuite.description == null) {
                console.info(`${TAG}filter suite : ${desc}`);
                this.suiteSkipReason = '';
                this.isSkipSuite = false;
                return;
            }
        }
        const suite = new SuiteService.Suite({ description: desc });
        if (this.isSkipSuite) {
            suite.isSkip = true;
            suite.skipReason = this.suiteSkipReason;
        }
        this.suiteSkipReason = '';
        this.isSkipSuite = false;
        if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') {
            let suiteStress = this.coreContext.getServices('dataDriver').dataDriver.getSuiteStress(desc);
            for (let i = 1; i < suiteStress; i++) {
                this.currentRunningSuite.childSuites.push(suite);
            }
        }
        this.currentRunningSuite.childSuites.push(suite);
        this.currentRunningSuite = suite;
        this.suitesStack.push(suite);
        const res = func.call();
        if (Object.prototype.toString.call(res) === '[object Promise]') {
            suite.isPromiseError = true;
        }
        this.suitesStack.pop();
        this.currentRunningSuite = this.suitesStack.pop();
        this.suitesStack.push(this.currentRunningSuite);
    }
    xdescribe(desc, func, reason) {
        const configService = this.coreContext.getDefaultService('config');
        if (!configService.skipMessage && configService.runSkipped !== 'all') {
            if (configService.runSkipped != null && configService.runSkipped !== '') {
                let finalDesc = '';
                this.suitesStack.map(suite => {
                    finalDesc = finalDesc + '.' + suite.description;
                });
                finalDesc = (finalDesc + '.' + desc).substring(2);
                console.info(`${TAG} finalDesc ${finalDesc}`);
                if (configService.checkIfSuiteInSkipRun(finalDesc)) {
                    console.info(`${TAG} runSkipped suite: ${desc}`);
                } else {
                    console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`);
                    return;
                }
            } else {
                console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`);
                return;
            }
        }
        this.isSkipSuite = true;
        this.suiteSkipReason = reason;
        this.describe(desc, func);
    }

    beforeAll(func) {
        this.currentRunningSuite.beforeAll.push(processFunc(this.coreContext, func));
    }

    beforeEach(func) {
        this.currentRunningSuite.beforeEach.push(processFunc(this.coreContext, func));
    }

    beforeItSpecified(itDescs, func) {
        this.currentRunningSuite.beforeItSpecified.set(itDescs, processFunc(this.coreContext, func));
    }

    afterItSpecified(itDescs, func) {
        this.currentRunningSuite.afterItSpecified.set(itDescs, processFunc(this.coreContext, func));
    }

    afterAll(func) {
        this.currentRunningSuite.afterAll.push(processFunc(this.coreContext, func));
    }

    afterEach(func) {
        this.currentRunningSuite.afterEach.push(processFunc(this.coreContext, func));
    }

    getCurrentRunningSuite() {
        return this.currentRunningSuite;
    }

    setCurrentRunningSuite(suite) {
        this.currentRunningSuite = suite;
    }

    getRootSuite() {
        return this.rootSuite;
    }

    getCurrentRunningSuiteDesc() {
        return this.currentRunningSuiteDesc;
    }


    setCurrentRunningSuiteDesc(suite, currentSuite, prefix) {
        if (suite != null && suite === currentSuite) {
            this.currentRunningSuiteDesc = prefix;
        } else if (suite != null && suite !== currentSuite) {
            suite.childSuites.forEach(it => {
                let temp = prefix;
                if (it.description != null || it.description !== '') {
                    temp = prefix === '' ? it.description : prefix + '.' + it.description;
                }
                this.setCurrentRunningSuiteDesc(it, currentSuite, temp);
            }
            );
        }
    }
    analyzeConfigServiceClass(configServiceClass, desc) {
        if (configServiceClass == null || configServiceClass === '') {
            this.fullRun = true;
            return false;
        }
        const targetArray = configServiceClass.split(',').map(item => item.trim()).filter(item => item !== '');
        if (this.targetSuiteArray.length === 0) {
            for (let index in targetArray) {
                if (targetArray[index].includes('#')) {
                    this.targetSpecArray.push(targetArray[index]);
                } else {
                    this.targetSuiteArray.push(targetArray[index]);
                }
            }

        }
        return targetArray.indexOf(desc) === -1;

    }
    traversalResults(suite, obj, breakOnError) {
        if (suite.childSuites.length === 0 && suite.specs.length === 0) {
            return;
        }
        if (suite.specs.length > 0) {
            for (const itItem of suite.specs) {
                obj.total++;
                let itInfo = {
                    currentThreadName: 'mainThread',
                    description: suite.description + '#' + itItem.description,
                    result: -3
                };
                if (SysTestKit.workerPort !== null) {
                    itInfo.currentThreadName = SysTestKit.workerPort.name;
                }
                obj.itItemList.push(itInfo);
                if (breakOnError && (obj.error > 0 || obj.failure > 0)) { // breakOnError模式
                    continue;
                }
                if (itItem.error) {
                    obj.error++;
                    itInfo.result = -1;
                } else if (itItem.fail) {
                    obj.failure++;
                    itInfo.result = -2;
                } else if (itItem.pass === true) {
                    obj.pass++;
                    itInfo.result = 0;
                }
            }
        }

        obj.duration += suite.duration;

        if (suite.childSuites.length > 0) {
            for (const suiteItem of suite.childSuites) {
                this.traversalResults(suiteItem, obj, breakOnError);
            }
        }

    }

    async setSuiteResults(suite, error, coreContext) {
        if (suite.childSuites.length === 0 && suite.specs.length === 0) {
            return;
        }
        if (suite.specs.length > 0) {
            const specService = coreContext.getDefaultService('spec');
            for (const specItem of suite.specs) {
                specService.setCurrentRunningSpec(specItem);
                if (error instanceof AssertException) {
                    specItem.fail = error;
                } else {
                    specItem.error = error;
                }
                await coreContext.fireEvents('spec', 'specStart', specItem);
                await coreContext.fireEvents('spec', 'specDone', specItem);
            }
        }
        if (suite.childSuites.length > 0) {
            for (const suiteItem of suite.childSuites) {
                await this.setSuiteResults(suiteItem, error, coreContext);
            }
        }
    }

    getSummary() {
        let suiteService = this.coreContext.getDefaultService('suite');
        let rootSuite = suiteService.rootSuite;
        const specService = this.coreContext.getDefaultService('spec');
        const configService = this.coreContext.getDefaultService('config');
        let breakOnError = configService.isBreakOnError();
        let isError = specService.getStatus();
        let isBreaKOnError = breakOnError && isError;
        // itItemList 保存当前用例执行情况, 发送到主线程用例计算最终结果
        let obj = { total: 0, failure: 0, error: 0, pass: 0, ignore: 0, duration: 0, itItemList: []};
        for (const suiteItem of rootSuite.childSuites) {
            this.traversalResults(suiteItem, obj, isBreaKOnError);
        }
        obj.ignore = obj.total - obj.pass - obj.failure - obj.error;
        return obj;
    }

    init(coreContext) {
        this.coreContext = coreContext;
    }

    traversalSuites(suite, obj, configService) {
        if (suite.childSuites.length === 0 && suite.specs.length === 0) {
            return [];
        }
        if (suite.specs.length > 0) {
            let itArray = [];
            for (const itItem of suite['specs']) {
                if (!configService.filterDesc(suite.description, itItem.description, itItem.fi, null)) {
                    itArray.push({ 'itName': itItem.description });
                }
            }
            obj[suite.description] = itArray;
        }
        if (suite.childSuites.length > 0) {
            let suiteArray = [];
            for (const suiteItem of suite.childSuites) {
                let suiteObj = {};
                this.traversalSuites(suiteItem, suiteObj, configService);
                if (!configService.filterSuite(suiteItem.description)) {
                    suiteArray.push(suiteObj);
                }
            }
            obj.suites = suiteArray;
        }
    }

    async dryRun(abilityDelegator) {
        console.info(`${TAG} rootSuite : ` + JSON.stringify(this.rootSuite));
        let obj = this.rootSuite;
        let prefixStack = [];
        let suiteArray = [];
        let skipSuiteArray = [];
        this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj);
        const configService = this.coreContext.getDefaultService('config');
        let result;
        if (configService.skipMessage) {
            result = { 'suites': suiteArray, 'skipSuites': skipSuiteArray };
        } else {
            result = { 'suites': suiteArray };
        }
        let strJson = JSON.stringify(result);
        let strLen = strJson.length;
        let maxLen = 500;
        let maxCount = Math.floor(strLen / maxLen);
        for (let count = 0; count <= maxCount; count++) {
            await SysTestKit.print(strJson.substring(count * maxLen, (count + 1) * maxLen));
        }
        console.info(`${TAG}dryRun print success`);
        abilityDelegator.finishTest('dry run finished!!!', 0, () => { });
    }

    //将suitesArray的嵌套结构展开成三层结构
    analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj) {
        obj.childSuites.map(suite => {
            if (suite.description != null && suite.description !== '') {
                let prefix = '';
                if (prefixStack.length > 0) {
                    prefix = prefixStack.join('.') + '.' + suite.description;
                } else {
                    prefix = suite.description;
                }
                prefixStack.push(suite.description);
                let temp = {};
                temp[prefix] = [];
                let skipTemp = {};
                skipTemp[prefix] = [];
                suite.specs.map(spec => {
                    let it = { 'itName': spec.description };
                    spec.isSkip ? skipTemp[prefix].push(it) : temp[prefix].push(it);
                });
                suiteArray.push(temp);
                skipSuiteArray.push(skipTemp);
            }
            this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, suite);
            prefixStack.pop();
        });
    }
    //获取当前测试套下的所有测试用例数量
    getAllChildSuiteNum(suite, specArray) {
        if (suite.specs != null) {
            suite.specs.forEach(spec => specArray.push(spec));
        }
        if (suite.childSuites != null) {
            suite.childSuites.forEach(it => this.getAllChildSuiteNum(it, specArray));
        }
    }

    execute() {
        const configService = this.coreContext.getDefaultService('config');
        if (configService.filterValid.length !== 0) {
            this.coreContext.fireEvents('task', 'incorrectFormat');
            return;
        }
        if (configService.filterXdescribe.length !== 0) {
            this.coreContext.fireEvents('task', 'incorrectTestSuiteFormat');
            return;
        }
        if (configService.isRandom() && this.rootSuite.childSuites.length > 0) {
            this.rootSuite.childSuites.sort(function () {
                return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1;
            });
            this.currentRunningSuite = this.rootSuite.childSuites[0];
        }
        if (configService.isSupportAsync()) {
            console.info(`${TAG} rootSuite:` + JSON.stringify(this.rootSuite));
            let asyncExecute = async () => {
                await this.coreContext.fireEvents('task', 'taskStart');
                await this.rootSuite.asyncRun(this.coreContext);
            };
            asyncExecute().then(async () => {
                await this.coreContext.fireEvents('task', 'taskDone');
            });
        } else {
            console.info('${TAG} rootSuite:' + JSON.stringify(this.rootSuite));
            this.coreContext.fireEvents('task', 'taskStart');
            this.rootSuite.run(this.coreContext);
            this.coreContext.fireEvents('task', 'taskDone');
        }
    }

    apis() {
        const _this = this;
        return {
            describe: function (desc, func) {
                return _this.describe(desc, func);
            },
            xdescribe: function (desc, func, reason) {
                return _this.xdescribe(desc, func, reason);
            },
            beforeItSpecified: function (itDescs, func) {
                return _this.beforeItSpecified(itDescs, func);
            },
            afterItSpecified: function (itDescs, func) {
                return _this.afterItSpecified(itDescs, func);
            },
            beforeAll: function (func) {
                return _this.beforeAll(func);
            },
            beforeEach: function (func) {
                return _this.beforeEach(func);
            },
            afterAll: function (func) {
                return _this.afterAll(func);
            },
            afterEach: function (func) {
                return _this.afterEach(func);
            }
        };
    }
}

SuiteService.Suite = class {
    constructor(attrs) {
        this.description = attrs.description || '';
        this.childSuites = [];
        this.specs = [];
        this.beforeAll = [];
        this.afterAll = [];
        this.beforeItSpecified = new Map();
        this.afterItSpecified = new Map();
        this.beforeEach = [];
        this.afterEach = [];
        this.duration = 0;
        this.hookError = null;
        this.isSkip = false;
        this.skipReason = '';
    }

    pushSpec(spec) {
        this.specs.push(spec);
    }

    removeSpec(desc) {
        this.specs = this.specs.filter((item, index) => {
            return item.description !== desc;
        });
    }

    getSpecsNum() {
        return this.specs.length;
    }

    isRun(coreContext) {
        const configService = coreContext.getDefaultService('config');
        const suiteService = coreContext.getDefaultService('suite');
        const specService = coreContext.getDefaultService('spec');
        let breakOnError = configService.isBreakOnError();
        let isError = specService.getStatus();
        return breakOnError && isError;
    }

    run(coreContext) {
        const suiteService = coreContext.getDefaultService('suite');
        suiteService.setCurrentRunningSuite(this);
        if (this.description !== '') {
            coreContext.fireEvents('suite', 'suiteStart', this);
        }
        this.runHookFunc('beforeAll');
        if (this.specs.length > 0) {
            const configService = coreContext.getDefaultService('config');
            if (configService.isRandom()) {
                this.specs.sort(function () {
                    return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1;
                });
            }
            for (let spec in this.specs) {
                let isBreakOnError = this.isRun(coreContext);
                if (isBreakOnError) {
                    break;
                }
                this.runHookFunc('beforeEach');
                spec.run(coreContext);
                this.runHookFunc('afterEach');
            }
        }
        if (this.childSuites.length > 0) {
            for (let suite in this.childSuites) {
                let isBreakOnError = this.isRun(coreContext);
                if (isBreakOnError) {
                    break;
                }
                suite.run(coreContext);
                suiteService.setCurrentRunningSuite(suite);
            }
        }
        this.runHookFunc('afterAll');
        if (this.description !== '') {
            coreContext.fireEvents('suite', 'suiteDone');
        }
    }

    async runBeforeItSpecified(beforeItSpecified, specItem) {
        for (const [itNames, hookFunc] of beforeItSpecified) {
            if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) ||
                (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) {
                await Reflect.apply(hookFunc, null, []);
            }
            break;
        }
    }

    async runAfterItSpecified(beforeItSpecified, specItem) {
        for (const [itNames, hookFunc] of beforeItSpecified) {
            if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) ||
                (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) {
                await Reflect.apply(hookFunc, null, []);
            }
            break;
        }
    }

    async asyncRunSpecs(coreContext) {
        const configService = coreContext.getDefaultService('config');
        if (configService.isRandom()) {
            this.specs.sort(function () {
                return +('0.' + (+ new Date() + '').split('').reverse().join('')) > 0.5 ? -1 : 1;
            });
        }
        const specService = coreContext.getDefaultService('spec');
        for (let specItem of this.specs) {
            specService.setCurrentRunningSpec(specItem);
            // 遇错即停模式,发现用例有问题，直接返回，不在执行后面的it
            let isBreakOnError = this.isRun(coreContext);
            if (isBreakOnError) {
                console.info('break description :' + this.description);
                break;
            }
            await coreContext.fireEvents('spec', 'specStart', specItem);
            try {
                await this.runBeforeItSpecified(this.beforeItSpecified, specItem);
                await this.runAsyncHookFunc('beforeEach');
                await specItem.asyncRun(coreContext);
                await this.runAfterItSpecified(this.afterItSpecified, specItem);
                await this.runAsyncHookFunc('afterEach');
            } catch (e) {
                console.error(`${TAG}stack:${e?.stack}`);
                console.error(`${TAG}stack end`);
                if (e instanceof AssertException) {
                    specItem.fail = e;
                } else {
                    specItem.error = e;
                }
                specService.setStatus(true);
            }
            specItem.setResult();
            await coreContext.fireEvents('spec', 'specDone', specItem);
            specService.setCurrentRunningSpec(null);
        }
    }

    async asyncRunChildSuites(coreContext) {
        for (let i = 0; i < this.childSuites.length; i++) {
            // 遇错即停模式, 发现用例有问题，直接返回，不在执行后面的description
            let isBreakOnError = this.isRun(coreContext);
            if (isBreakOnError) {
                console.info(`${TAG}break description : ${this.description}`);
                break;
            }
            await this.childSuites[i].asyncRun(coreContext);
        }
    }

    async asyncRun(coreContext) {
        const suiteService = coreContext.getDefaultService('suite');
        const specService = coreContext.getDefaultService('spec');

        suiteService.setCurrentRunningSuite(this);
        suiteService.suitesStack.push(this);
        if (this.description !== '') {
            await coreContext.fireEvents('suite', 'suiteStart', this);
        }

        try {
            await this.runAsyncHookFunc('beforeAll');
        } catch (error) {
            console.error(`${TAG}${error?.stack}`);
            this.hookError = error;
        }

        if (this.hookError !== null) {
            specService.setStatus(true);
            await suiteService.setSuiteResults(this, this.hookError, coreContext);
        }

        if (this.specs.length > 0 && this.hookError === null) {
            await this.asyncRunSpecs(coreContext);
        }

        if (this.childSuites.length > 0 && this.hookError === null) {
            await this.asyncRunChildSuites(coreContext);
        }

        try {
            await this.runAsyncHookFunc('afterAll');
        } catch (error) {
            console.error(`${TAG}${error?.stack}`);
            this.hookError = error;
            specService.setStatus(true);
        }

        if (this.description !== '') {
            await coreContext.fireEvents('suite', 'suiteDone');
            let childSuite = suiteService.suitesStack.pop();
            let currentRunningSuite = suiteService.suitesStack.pop();
            suiteService.setCurrentRunningSuite(currentRunningSuite);
            suiteService.suitesStack.push(currentRunningSuite);
        }
    }

    runHookFunc(hookName) {
        if (this[hookName] && this[hookName].length > 0) {
            this[hookName].forEach(func => {
                try {
                    func();
                } catch (e) {
                    console.error(`${TAG}${e.stack}`);
                }
            });
        }
    }

    async runAsyncHookFunc(hookName) {
        for (const hookItem of this[hookName]) {
            try {
                await hookItem();
            } catch (error) {
                error['message'] += `, error in ${hookName} function`;
                throw error;
            }

        }
    }
};

class SpecService {
    constructor(attr) {
        this.id = attr.id;
        this.totalTest = 0;
        this.hasError = false;
        this.skipSpecNum = 0;
        this.isSkipSpec = false;
        this.specSkipReason = '';
    }

    init(coreContext) {
        this.coreContext = coreContext;
    }

    setCurrentRunningSpec(spec) {
        this.currentRunningSpec = spec;
    }

    setStatus(obj) {
        this.hasError = obj;
    }

    getStatus() {
        return this.hasError;
    }

    getTestTotal() {
        return this.totalTest;
    }

    getCurrentRunningSpec() {
        return this.currentRunningSpec;
    }


    getSkipSpecNum() {
        return this.skipSpecNum;
    }

    initSpecService() {
        this.isSkipSpec = false;
        this.specSkipReason = '';
    }

    it(desc, filter, func) {
        const suiteService = this.coreContext.getDefaultService('suite');
        const configService = this.coreContext.getDefaultService('config');
        let isFilter = new NestFilter().filterNestName(suiteService.targetSuiteArray, suiteService.targetSpecArray, suiteService.suitesStack, desc);
        if (configService.filterWithNest(desc, filter)) {
            console.info(`${TAG}filter it :${desc}`);
            this.initSpecService();
            return;
        }
        if (configService.filterDesc(suiteService.currentRunningSuite.description, desc, filter, this.coreContext) && isFilter && !suiteService.fullRun) {
            console.info(`${TAG}filter it :${desc}`);
            this.initSpecService();
        } else {
            let processedFunc = processFunc(this.coreContext, func);
            const spec = new SpecService.Spec({ description: desc, fi: filter, fn: processedFunc });
            if (this.isSkipSpec) {
                spec.isSkip = true;
                spec.skipReason = this.specSkipReason;
            }
            this.initSpecService();
            if (configService.runSkipped === 'skipped' && !spec.isSkip) {
                console.info(`${TAG} runSkipped is skipped , just run xit, don't run it: ${spec.description}`);
                return;
            }
            if (suiteService.getCurrentRunningSuite().isSkip && !spec.isSkip) {
                configService.filterXdescribe.push(suiteService.getCurrentRunningSuite().description);
            }
            if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') {
                let specStress = this.coreContext.getServices('dataDriver').dataDriver.getSpecStress(desc);
                for (let i = 1; i < specStress; i++) {
                    this.totalTest++;
                    suiteService.getCurrentRunningSuite().pushSpec(spec);
                }
            }
            // dryRun 状态下不统计压力测试重复数据
            if (configService['dryRun'] !== 'true') {
                let stress = configService.getStress(); // 命令配置压力测试
                console.info(`${TAG}stress length : ${stress}`);
                for (let i = 1; i < stress; i++) {
                    this.totalTest++;
                    suiteService.getCurrentRunningSuite().pushSpec(spec);
                }
            }
            this.totalTest++;
            suiteService.getCurrentRunningSuite().pushSpec(spec);
        }
    }

    xit(desc, filter, func, reason) {
        const configService = this.coreContext.getDefaultService('config');
        const suiteService = this.coreContext.getDefaultService('suite');
        if (!configService.skipMessage && configService.runSkipped !== 'all') {
            if (configService.runSkipped != null && configService.runSkipped !== '') {
                let finalDesc = '';
                suiteService.suitesStack.map(suite => {
                    finalDesc = finalDesc + '.' + suite.description;
                });
                finalDesc = (finalDesc + '#' + desc).substring(2);
                if (configService.checkIfSpecInSkipRun(finalDesc)) {
                    console.info(`${TAG} runSkipped spec: ${desc}`);
                } else {
                    console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`);
                    return;
                }
            } else {
                console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`);
                return;
            }
        }
        this.skipSpecNum++;
        this.isSkipSpec = true;
        this.specSkipReason = reason;
        this.it(desc, filter, func);
    }

    apis() {
        const _this = this;
        return {
            it: function (desc, filter, func) {
                return _this.it(desc, filter, func);
            },
            xit: function (desc, filter, func, reason) {
                return _this.xit(desc, filter, func, reason);
            }
        };
    }
}

SpecService.Spec = class {
    constructor(attrs) {
        this.description = attrs.description || '';
        this.fi = attrs.fi;
        this.fn = attrs.fn || function () {
        };
        this.fail = undefined;
        this.error = undefined;
        this.duration = 0;
        this.startTime = 0;
        this.isExecuted = false; // 当前用例是否执行
        this.isSkip = false;
        this.skipReason = '';
        this.expectMsg = '';
    }

    setResult() {
        if (this.fail) {
            this.pass = false;
        } else {
            this.pass = true;
        }
    }

    run(coreContext) {
        const specService = coreContext.getDefaultService('spec');
        specService.setCurrentRunningSpec(this);
        coreContext.fireEvents('spec', 'specStart', this);
        this.isExecuted = true;
        try {
            let dataDriver = coreContext.getServices('dataDriver');
            if (typeof dataDriver === 'undefined') {
                this.fn();
            } else {
                let suiteParams = dataDriver.dataDriver.getSuiteParams();
                let specParams = dataDriver.dataDriver.getSpecParams();
                console.info(`${TAG}[suite params] ${JSON.stringify(suiteParams)}`);
                console.info(`${TAG}[spec params] ${JSON.stringify(specParams)}`);
                if (this.fn.length === 0) {
                    this.fn();
                } else if (specParams.length === 0) {
                    this.fn(suiteParams);
                } else {
                    specParams.forEach(paramItem => this.fn(Object.assign({}, paramItem, suiteParams)));
                }
            }
            this.setResult();
        } catch (e) {
            this.error = e;
            specService.setStatus(true);
        }
        coreContext.fireEvents('spec', 'specDone', this);
    }

    async asyncRun(coreContext) {
        const dataDriver = coreContext.getServices('dataDriver');
        if (typeof dataDriver === 'undefined') {
            await this.fn();
        } else {
            const suiteParams = dataDriver.dataDriver.getSuiteParams();
            const specParams = dataDriver.dataDriver.getSpecParams();
            console.info(`[suite params] ${JSON.stringify(suiteParams)}`);
            console.info(`[spec params] ${JSON.stringify(specParams)}`);
            if (this.fn.length === 0) {
                await this.fn();
            } else if (specParams.length === 0) {
                await this.fn(suiteParams);
            } else {
                for (const paramItem of specParams) {
                    await this.fn(Object.assign({}, paramItem, suiteParams));
                }
            }
        }

        this.isExecuted = true;
    }

    filterCheck(coreContext) {
        const specService = coreContext.getDefaultService('spec');
        specService.setCurrentRunningSpec(this);
        return true;
    }
};

class ExpectService {
    constructor(attr) {
        this.id = attr.id;
        this.matchers = {};
        this.customMatchers = [];
    }

    expect(actualValue) {
        return this.wrapMatchers(actualValue);
    }

    init(coreContext) {
        this.coreContext = coreContext;
        this.addMatchers(this.basicMatchers());
    }

    addMatchers(matchers) {
        for (const matcherName in matchers) {
            if (Object.prototype.hasOwnProperty.call(matchers, matcherName)) {
                this.matchers[matcherName] = matchers[matcherName];
            }
        }
    }

    removeMatchers(customAssertionName) {
        if (customAssertionName === 'all') {
            for (const matcherName in this.matchers) {
                this.matchers[matcherName] = this.customMatchers.includes(matcherName)
                    ? (() => {throw new Error(`${matcherName} is unregistered`)}) : undefined;
            }
        } else {
            this.matchers[customAssertionName] = () => {
                throw new Error(`${customAssertionName} is unregistered`);
            };
        }
    }

    basicMatchers() {
        return {
            assertTrue: assertTrueFun,
            assertEqual: assertEqualFun,
            assertThrow: assertThrowFun
        };
    }

    initWrapMatchers(currentRunningSpec) {
        return {
            // 翻转标识
            isNot: false,
            // 翻转方法
            not: function () {
                this.isNot = true;
                return this;
            },
            message: function (msg) {
                currentRunningSpec.expectMsg = msg;
                console.info(`${TAG} msg: ${msg}`);
                return this;
            }
        };

    }

    handleWithAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite) {
        wrappedMatchers[matcherName] = async function (...args) {
            await _this.matchers[matcherName](actualValue, args).then(function (result) {
                if (wrappedMatchers.isNot) {
                    result.pass = !result.pass;
                }
                result.actualValue = actualValue;
                result.checkFunc = matcherName;
                if (!result.pass) {
                    const assertError = new AssertException(result.message);
                    currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError;
                    throw assertError;
                }
            });
        };
    }

    handleWithoutAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite) {
        wrappedMatchers[matcherName] = function (...args) {
            const result = _this.customMatchers.includes(matcherName)
                ? _this.matchers[matcherName](actualValue, args[0]) : _this.matchers[matcherName](actualValue, args);
            if (wrappedMatchers.isNot) {
                result.pass = !result.pass;
                result.message = LogExpectError.getErrorMsg(matcherName, actualValue, args[0], result.message);
            }
            result.actualValue = actualValue;
            result.checkFunc = matcherName;
            if (!result.pass) {
                const assertError = new AssertException(result.message);
                currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError;
                throw assertError;
            }
        };
    }

    addAssert(wrappedMatchers, matcherName, actualValue) {
        const _this = this;
        const specService = _this.coreContext.getDefaultService('spec');
        const currentRunningSpec = specService.getCurrentRunningSpec();
        const currentRunningSuite = _this.coreContext.getDefaultService('suite').getCurrentRunningSuite();
        if (matcherName.search('assertPromise') === 0) {
            this.handleWithAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite);
        } else {
            this.handleWithoutAssertPromise(_this, wrappedMatchers, matcherName, actualValue, currentRunningSpec, currentRunningSuite);
        }
    }

    wrapMatchers(actualValue) {
        const _this = this;
        const specService = _this.coreContext.getDefaultService('spec');
        const currentRunningSpec = specService.getCurrentRunningSpec();
        const wrappedMatchers = this.initWrapMatchers(currentRunningSpec);
        const currentRunningSuite = _this.coreContext.getDefaultService('suite').getCurrentRunningSuite();
        for (const matcherName in this.matchers) {
            let result = Object.prototype.hasOwnProperty.call(this.matchers, matcherName);
            if (!result) {
                continue;
            }
            this.addAssert(wrappedMatchers, matcherName, actualValue);
        }
        return wrappedMatchers;
    }

    apis() {
        const _this = this;
        return {
            expect: function (actualValue) {
                return _this.expect(actualValue);
            }
        };
    }
}

class ReportService {
    constructor(attr) {
        this.id = attr.id;
    }

    init(coreContext) {
        this.coreContext = coreContext;
        this.specService = this.coreContext.getDefaultService('spec');
        this.suiteService = this.coreContext.getDefaultService('suite');
        this.duration = 0;
    }

    taskStart() {
        console.info(`${TAG}[start] start run suites`);
    }

    async suiteStart() {
        console.info(`${TAG}[suite start]${this.suiteService.getCurrentRunningSuite().description}`);
    }

    async specStart() {
        console.info(`${TAG}start running case '${this.specService.currentRunningSpec.description}'`);
        this.index = this.index + 1;
        let spec = this.specService.currentRunningSpec;
        spec.startTime = await SysTestKit.getRealTime();
    }

    async specDone() {
        let msg = '';
        let spec = this.specService.currentRunningSpec;
        let suite = this.suiteService.currentRunningSuite;
        spec.duration = await SysTestKit.getRealTime() - spec.startTime;
        suite.duration += spec.duration;
        if (spec.error) {
            this.formatPrint('error', spec.description + ' ; consuming ' + spec.duration + 'ms');
            this.formatPrint('errorDetail', spec.error);
        } else if (spec.fail) {
            this.formatPrint('fail', spec.description + ' ; consuming ' + spec.duration + 'ms');
            this.formatPrint('failDetail', spec.fail?.message);
        } else {
            this.formatPrint('pass', spec.description + ' ; consuming ' + spec.duration + 'ms');
        }
        this.formatPrint(this.specService.currentRunningSpec.error, msg);
    }

    suiteDone() {
        let suite = this.suiteService.currentRunningSuite;
        let message = suite.hookError ? `, ${suite.hookError?.message}` : '';
        console.info(`[suite end] ${suite.description} consuming ${suite.duration} ms${message}`);
    }

    taskDone() {
        let msg = '';
        let summary = this.suiteService.getSummary();
        msg = 'total cases:' + summary.total + ';failure ' + summary.failure + ',' + 'error ' + summary.error;
        msg += ',pass ' + summary.pass + '; consuming ' + summary.duration + 'ms';
        console.info(`${TAG}${msg}`);
        console.info(`${TAG}[end] run suites end`);
    }

    incorrectFormat() {
        if (this.coreContext.getDefaultService('config').filterValid.length !== 0) {
            this.coreContext.getDefaultService('config').filterValid.forEach(function (item) {
                console.info(`${TAG}this param ${item} is invalid`);
            });
        }
    }

    incorrectTestSuiteFormat() {
        if (this.coreContext.getDefaultService('config').filterXdescribe.length !== 0) {
            this.coreContext.getDefaultService('config').filterXdescribe.forEach(function (item) {
                console.info(`${TAG}xdescribe: ${item} should not contain it`);
            });
        }
    }

    formatPrint(type, msg) {
        switch (type) {
            case 'pass':
                console.info(`${TAG}[pass]${msg}`);
                break;
            case 'fail':
                console.info(`${TAG}[fail]${msg}`);
                break;
            case 'failDetail':
                console.info(`${TAG}[failDetail]${msg}`);
                break;
            case 'error':
                console.info(`${TAG}[error]${msg}`);
                break;
            case 'errorDetail':
                console.info(`${TAG}[errorDetail]${msg}`);
                break;
        }
    }

    sleep(numberMillis) {
        var now = new Date();
        var exitTime = now.getTime() + numberMillis;
        while (true) {
            now = new Date();
            if (now.getTime() > exitTime) {
                return;
            }
        }
    }
}

export {
    SuiteService,
    SpecService,
    ExpectService,
    ReportService
};
