
function QLTools(url, clientId, clientSecret) {
    return new (class {
        constructor(url, clientId, clientSecret) {
            this.isEnableLog = true;
            this.logSeparator = '\n██'
            this.url = url;
            this.clientId = clientId;
            this.clientSecret = clientSecret;
            this.auth = null;
        }

        log(message) {
            if (this.isEnableLog) console.log(`${this.logSeparator}${message}`);
        }

        isSurge() {
            return typeof $httpClient != "undefined";
        }

        isQuanX() {
            return typeof $task != "undefined";
        }

        isLoon() {
            return typeof $loon != "undefined";
        }

        isJSBox() {
            return typeof $app != "undefined" && typeof $http != "undefined";
        }

        isStash() {
            return 'undefined' !== typeof $environment && $environment['stash-version'];
        }

        isNode() {
            return typeof require == "function" && !this.isJSBox();
        }

        adapterStatus(response) {
            if (response) {
                if (response.status) {
                    response["statusCode"] = response.status
                } else if (response.statusCode) {
                    response["status"] = response.statusCode
                }
            }
            return response
        }

        get(options, callback = () => { }) {
            if (this.isQuanX()) {
                if (typeof options == "string") options = {
                    url: options
                }
                options["method"] = "GET"
                $task.fetch(options).then(response => {
                    callback(null, this.adapterStatus(response), response.body)
                }, reason => callback(reason.error, null, null))
            }
            if (this.isSurge() || this.isLoon() || this.isStash()) $httpClient.get(options, (error, response, body) => {
                callback(error, this.adapterStatus(response), body)
            })
            if (this.isNode()) {
                this.node.request(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
            if (this.isJSBox()) {
                if (typeof options == "string") options = {
                    url: options
                }
                options["header"] = options["headers"]
                options["handler"] = function (resp) {
                    let error = resp.error
                    if (error) error = JSON.stringify(resp.error)
                    let body = resp.data
                    if (typeof body == "object") body = JSON.stringify(resp.data)
                    callback(error, this.adapterStatus(resp.response), body)
                }
                $http.get(options)
            }
        }

        post(options, callback = () => { }) {
            if (this.isQuanX()) {
                if (typeof options == "string") options = {
                    url: options
                }
                options["method"] = "POST"
                $task.fetch(options).then(response => {
                    callback(null, this.adapterStatus(response), response.body)
                }, reason => callback(reason.error, null, null))
            }
            if (this.isSurge() || this.isLoon() || this.isStash()) {
                $httpClient.post(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
            if (this.isNode()) {
                this.node.request.post(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
            if (this.isJSBox()) {
                if (typeof options == "string") options = {
                    url: options
                }
                options["header"] = options["headers"]
                options["handler"] = function (resp) {
                    let error = resp.error
                    if (error) error = JSON.stringify(resp.error)
                    let body = resp.data
                    if (typeof body == "object") body = JSON.stringify(resp.data)
                    callback(error, this.adapterStatus(resp.response), body)
                }
                $http.post(options)
            }
        }

        put(options, callback = () => { }) {
            if (this.isQuanX()) {
                // no test
                if (typeof options == "string") options = {
                    url: options
                }
                options["method"] = "PUT"
                $task.fetch(options).then(response => {
                    callback(null, this.adapterStatus(response), response.body)
                }, reason => callback(reason.error, null, null))
            }
            if (this.isSurge() || this.isLoon() || this.isStash()) {
                options.method = "PUT"
                $httpClient.put(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
            if (this.isNode()) {
                options.method = "PUT"
                this.node.request.put(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
        }

        delete(options, callback = () => { }) {
            if (this.isQuanX()) {
                // no test
                if (typeof options == "string") options = {
                    url: options
                }
                options["method"] = "DELETE"
                $task.fetch(options).then(response => {
                    callback(null, this.adapterStatus(response), response.body)
                }, reason => callback(reason.error, null, null))
            }
            if (this.isSurge() || this.isLoon() || this.isStash()) {
                options.method = "DELETE"
                $httpClient.put(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
            if (this.isNode()) {
                options.method = "DELETE"
                this.node.request.put(options, (error, response, body) => {
                    callback(error, this.adapterStatus(response), body)
                })
            }
        }

        setAuth(auth){
            this.auth = auth;
        }


        async login(){
            let result = await this.getToken();
            if(!result){
                return false;
            }
            if(result.code == 200){
                let data = result.data;
                this.auth = `${data.token_type} ${data.token}`;
                return true;
            }
            return false;
        }

        async getToken(){
            let url = `${this.url}/open/auth/token?client_id=${this.clientId}&client_secret=${this.clientSecret}`;
            let options = {
                url: url,
            };
            return new Promise(resolve => {
                this.get(options, async (error, response, data) => {
                    try {
                        error && this.log(error);
                        let result = JSON.parse(data);
                        resolve(result);
                    } catch (error) {
                        this.log(error);
                    } finally {
                        resolve();
                    }
                });
            });
        }


        async callApi(reqType, optName1, optName2=null , params=null){
            let url = `${this.url}/open/${optName1}`;
            if(optName2 && optName2.length > 0){
                url += `/${optName2}`;
            }
            let body = '';
            if(params){
                body = JSON.stringify(params);
            }
            let options = {
                url: url,
                headers: {
                    'Authorization': this.auth,
                    'Content-Type': `application/json`,
                    'Accept': `application/json`,
                },
                body: body,
            };
            return new Promise(resolve => {
                let func = this[reqType];
                if(!func){
                    resolve();
                    return;
                }
                func.call(this, options, async (error, response, data) => {
                    try {
                        let result = JSON.parse(data);
                        resolve(result);
                    } catch (error) {
                        resolve(data);
                    } finally {
                        resolve();
                    }
                });
            });
        }

        async getEnvs(){
            return this.callApi('get', 'envs');
        }

        async getEnvById(id){
            return this.callApi('get', 'envs', id);
        }

        async deleteEnvs(ids){
            return this.callApi('delete', 'envs', '', ids);
        }

        async addEnvs(envsList){
            return this.callApi('post', 'envs', '', envsList);
        }

        async updateEnv(envObj){
            return this.callApi('put', 'envs', '', envObj);
        }

        async getCrons(){
            return this.callApi('get', 'crons');
        }

        async getCronById(id){
            return this.callApi('get', 'crons', id);
        }

        async deleteCrons(ids){
            return this.callApi('delete', 'crons', '', ids);
        }

        async addCron(cronObj){
            return this.callApi('post', 'crons', '', cronObj);
        }

        async updateCron(cronObj){
            return this.callApi('put', 'crons', '', cronObj);
        }

        async runCrons(ids){
            return this.callApi('put', 'crons', 'run', ids);
        }

        async stopCrons(ids){
            return this.callApi('put', 'crons', 'stop', ids);
        }

        async enableCrons(ids){
            return this.callApi('put', 'crons', 'enable', ids);
        }

        async disableCrons(ids){
            return this.callApi('put', 'crons', 'disable', ids);
        }

        async getCronDetail(id){
            return this.callApi('get', 'crons', id);
        }

        async getCronLog(id){
            return this.callApi('get', 'crons', `${id}/log`);
        }

    })(url, clientId, clientSecret);
}