"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const _ = require("lodash");
const os_1 = require("os");
const util = require("util");
const yok_1 = require("./yok");
const axios_1 = require("axios");
const constants_1 = require("./constants");
const tunnel = require("tunnel");
class HttpClient {
    constructor($logger, $proxyService, $staticConfig) {
        this.$logger = $logger;
        this.$proxyService = $proxyService;
        this.$staticConfig = $staticConfig;
    }
    httpRequest(options, proxySettings) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const result = yield this.httpRequestCore(options, proxySettings);
                return {
                    response: result,
                    body: result.body,
                    headers: result.headers,
                };
            }
            catch (err) {
                if (err.message === HttpClient.STUCK_REQUEST_ERROR_MESSAGE ||
                    err.message === HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) {
                    this.$logger.warn("%s Retrying request to %s...", err.message, options.url || options);
                    const retryResult = yield this.httpRequestCore(options, proxySettings);
                    return {
                        response: retryResult,
                        body: retryResult.body,
                        headers: retryResult.headers,
                    };
                }
                throw err;
            }
        });
    }
    httpRequestCore(options, proxySettings) {
        return __awaiter(this, void 0, void 0, function* () {
            if (_.isString(options)) {
                options = {
                    url: options,
                    method: "GET",
                };
            }
            const cliProxySettings = yield this.$proxyService.getCache();
            const requestProto = options.proto || "http";
            options.headers = options.headers || {};
            const headers = options.headers;
            yield this.useProxySettings(proxySettings, cliProxySettings, options, headers, requestProto);
            if (!headers["User-Agent"]) {
                if (!this.defaultUserAgent) {
                    this.defaultUserAgent = `${this.$staticConfig.USER_AGENT_NAME}/${this.$staticConfig.version} (Node.js ${process.versions.node}; ${process.platform}; ${process.arch})`;
                    this.$logger.debug("User-Agent: %s", this.defaultUserAgent);
                }
                headers["User-Agent"] = this.defaultUserAgent;
            }
            if (!headers["Accept-Encoding"]) {
                headers["Accept-Encoding"] = "gzip,deflate";
            }
            this.$logger.trace("httpRequest: %s", util.inspect(options));
            let agent;
            if (cliProxySettings) {
                agent = tunnel.httpsOverHttp({
                    proxy: {
                        host: cliProxySettings.hostname,
                        port: parseInt(cliProxySettings.port),
                    },
                });
            }
            const result = yield (0, axios_1.default)({
                url: options.url,
                headers: options.headers,
                method: options.method,
                proxy: false,
                httpAgent: agent,
                data: options.body,
            }).catch((err) => {
                this.$logger.trace("An error occurred while sending the request:", err);
                if (err.response) {
                    const errorMessage = this.getErrorMessage(err.response.status, null);
                    err.proxyAuthenticationRequired =
                        err.response.status === constants_1.HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED;
                    err.message = errorMessage || err.message;
                }
                else if (err.request) {
                }
                else {
                }
                throw err;
            });
            if (result) {
                this.$logger.trace("httpRequest: Done. code = %d", result.status.toString());
                return {
                    response: result,
                    body: JSON.stringify(result.data),
                    headers: result.headers,
                };
            }
        });
    }
    getErrorMessage(statusCode, body) {
        if (statusCode === constants_1.HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED) {
            const clientNameLowerCase = this.$staticConfig.CLIENT_NAME.toLowerCase();
            this.$logger.error(`You can run ${os_1.EOL}\t${clientNameLowerCase} proxy set <url> <username> <password>.${os_1.EOL}In order to supply ${clientNameLowerCase} with the credentials needed.`);
            return "Your proxy requires authentication.";
        }
        else if (statusCode === constants_1.HttpStatusCodes.PAYMENT_REQUIRED) {
            return "Your subscription has expired.";
        }
        else if (statusCode === constants_1.HttpStatusCodes.CONFLICTING_RESOURCE) {
            return "The request conflicts with the current state of the server.";
        }
        else {
            this.$logger.trace("Request was unsuccessful. Server returned: ", body);
            try {
                const err = JSON.parse(body);
                if (_.isString(err)) {
                    return err;
                }
                if (err && err.ExceptionMessage) {
                    return err.ExceptionMessage;
                }
                if (err && err.Message) {
                    return err.Message;
                }
            }
            catch (parsingFailed) {
                this.$logger.trace("Failed to get error from http request: ", parsingFailed);
                return `The server returned unexpected response: ${body}`;
            }
            return body;
        }
    }
    useProxySettings(proxySettings, cliProxySettings, options, headers, requestProto) {
        return __awaiter(this, void 0, void 0, function* () {
            const isLocalRequest = options.host === "localhost" || options.host === "127.0.0.1";
            if (!isLocalRequest && (proxySettings || cliProxySettings)) {
                const proto = (proxySettings && proxySettings.protocol) ||
                    cliProxySettings.protocol ||
                    "http:";
                const host = (proxySettings && proxySettings.hostname) || cliProxySettings.hostname;
                const port = (proxySettings && proxySettings.port) || cliProxySettings.port;
                let credentialsPart = "";
                if (cliProxySettings.username && cliProxySettings.password) {
                    credentialsPart = `${cliProxySettings.username}:${cliProxySettings.password}@`;
                }
                options.proxy = `${proto}//${credentialsPart}${host}:${port}`;
                options.rejectUnauthorized = proxySettings
                    ? proxySettings.rejectUnauthorized
                    : cliProxySettings.rejectUnauthorized;
                this.$logger.trace("Using proxy: %s", options.proxy);
            }
        });
    }
}
exports.HttpClient = HttpClient;
HttpClient.STUCK_REQUEST_ERROR_MESSAGE = "The request can't receive any response.";
HttpClient.STUCK_RESPONSE_ERROR_MESSAGE = "Can't receive all parts of the response.";
yok_1.injector.register("httpClient", HttpClient);
