# lib/RequestClient/Request.js

    import URLHelper from '../Utils/URLHelper';
    import RegionHelper from '../Utils/RegionHelper';

    function Request(
        config,
        serviceName,
        endpoint,
        methodName,
        httpMethodType,
        limiter,
        body, // POST/PUT
        isTournament = false,
    ) {
        this.payload = {
            method: httpMethodType || 'GET',
            serviceName,
            endpoint,
            query: [],
            region: '',
            body,
            isTournament,
        };
        if (this.payload.method === 'GET') delete this.payload.body;
        this.config = config;
        this.retriesLeft = this.config.requestOptions.numberOfRetriesBeforeAbort;
        this.methodName = methodName;
        this.limiter = limiter;
    }

    Request.prototype.region = function(region) {
        if (this.payload.region)
            throw new Error('Do not call Request.region twice.');
        if (!RegionHelper.isValidRegion(region))
            throw new Error('Bad region value in Request.region');
        if (region) this.payload.region = region;
        return this;
    };

    Request.prototype.query = function(obj) {
        if (typeof obj !== 'object')
            throw new Error('Request.query takes in an object');
        if (obj) this.payload.query.push(obj);
        return this;
    };

    Request.prototype.then = function then(resolve, reject) {
        const self = this;
        return new Promise((innerResolve, innerReject) =>
            self.callback(
                (err, res) => (err ? innerReject(err) : innerResolve(res)),
            ),
        ).then(resolve, reject);
    };

    Request.prototype.catch = callback => this.then(null, callback);

    Request.prototype.callback = function(cb) {
        if (!this.payload.region) this.payload.region = this.config.region;

        const {
            endpoint,
            query,
            region,
            serviceName,
            body,
            isTournament,
        } = this.payload;
        const url = URLHelper.getURLWithQuery(
            RegionHelper.asPlatformID(region),
            serviceName,
            endpoint,
            query,
            isTournament,
        );
        const { key: token } = this.config;

        this.execute(url, body, token, this.retriesLeft, cb);
    };

    Request.prototype.execute = async function(url, body, token, retriesLeft, cb) {
        const { cacheOptions, debugOptions, requestOptions } = this.config;
        const fn = async (err, data) => {
            const debugURL = `${url}${
                debugOptions.showKey ? URLHelper.getAPIKey(url, token) : ''
            }`;
            try {
                if (data) {
                    if (debugOptions.isEnabled) {
                        debugOptions.loggers.cache.get(debugURL);
                    }
                    ok(data)(cb);
                } else {
                    if (debugOptions.isEnabled) {
                        const { method } = this.payload;
                        let debugString = `[${method}] => ${debugURL}`;
                        if (method !== 'GET') debugString += ' with body: %O';
                        if (body)
                            debugOptions.loggers.request.outgoing(
                                debugString,
                                body,
                            );
                        else debugOptions.loggers.request.outgoing(debugString);
                    }
                    const res = await this.limiter.executing({
                        url,
                        method: this.payload.method,
                        body,
                        token,
                    });

                    if (debugOptions.isEnabled) {
                        debugOptions.loggers.request.incoming.success(debugURL);
                    }

                    try {
                        const blob = JSON.parse(res);
                        if (
                            cacheOptions.cache &&
                            cacheOptions.ttls[this.methodName] > 0
                        ) {
                            cacheOptions.cache.set(
                                {
                                    key: url,
                                    ttl: cacheOptions.ttls[this.methodName],
                                },
                                blob,
                            );
                            if (debugOptions.isEnabled) {
                                debugOptions.loggers.cache.set(`${url}`);
                            }
                        }
                        ok(blob)(cb);
                    } catch (ex) {
                        console.log(ex);
                    }
                }
            } catch ({ statusCode }) {
                if (debugOptions.isEnabled) {
                    debugOptions.loggers.request.incoming.failure(
                        `[${statusCode}] ${debugURL}`,
                    );
                }
                if (requestOptions.shouldRetry && shouldRetry(statusCode)) {
                    if (retriesLeft > 0) {
                        return setTimeout(
                            () =>
                                this.execute(url, body, token, retriesLeft - 1, cb),
                            requestOptions.delayBeforeRetry,
                        );
                    }
                }
                error(statusCode)(cb);
            }
        };
        if (cacheOptions.cache) {
            cacheOptions.cache.get({ key: url }, fn);
        } else {
            fn();
        }
    };

    const shouldRetry = statusCode => statusCode === 500 || statusCode === 503;

    const ok = blob => cb => cb(null, blob);
    const error = statusCode => cb => cb(statusCode, null);

    export default Request;