"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const urllib_1 = require("urllib");
const uplog_1 = require("./uplog");
const stream_buffers_1 = require("stream-buffers");
const utils_1 = require("./utils");
const kodo_auth_1 = require("./kodo-auth");
const req_id_1 = require("./req_id");
class HttpClient {
    constructor(clientOptions, uplogBuffer) {
        this.clientOptions = clientOptions;
        this.uplogBuffer = uplogBuffer;
    }
    call(urls, options) {
        var _a, _b;
        const urlString = urls.shift();
        if (!urlString) {
            return Promise.reject(new Error('urls is empty'));
        }
        let url;
        if ((_a = options.fullUrl) !== null && _a !== void 0 ? _a : false) {
            url = new URL(urlString);
        }
        else {
            url = this.makeUrl(urlString, options);
        }
        const headers = {
            'user-agent': this.clientOptions.userAgent,
        };
        if ((_b = options.appendAuthorization) !== null && _b !== void 0 ? _b : true) {
            headers['authorization'] = this.makeAuthorization(url, options);
        }
        if (options.contentType) {
            headers['content-type'] = options.contentType;
        }
        if (options.headers) {
            for (const [headerName, headerValue] of Object.entries(options.headers)) {
                headers[headerName] = headerValue;
            }
        }
        const reqId = req_id_1.generateReqId({
            url: url.toString(),
            method: options.method,
            dataType: options.dataType,
            contentType: options.contentType,
            headers: headers,
        });
        headers['x-reqid'] = reqId;
        return new Promise((resolve, reject) => {
            var _a, _b;
            let requestInfo = undefined;
            let multiJsonEncoded = false;
            if (options.dataType === 'multijson') {
                multiJsonEncoded = true;
                delete options.dataType;
            }
            const data = (_a = options.data) !== null && _a !== void 0 ? _a : (_b = options.form) === null || _b === void 0 ? void 0 : _b.getBuffer();
            const requestOption = {
                method: options.method,
                dataType: options.dataType,
                contentType: options.contentType,
                headers: headers,
                timeout: this.clientOptions.timeout,
                followRedirect: true,
                streaming: options.streaming,
                retry: this.clientOptions.retry,
                retryDelay: this.clientOptions.retryDelay,
                isRetry: this.isRetry,
                beforeRequest: (info) => {
                    if (options.stats) {
                        options.stats.requestsCount += 1;
                    }
                    requestInfo = {
                        url: url.toString(),
                        method: info.method,
                        headers: info.headers,
                        data: data,
                    };
                    if (this.clientOptions.requestCallback) {
                        this.clientOptions.requestCallback(requestInfo);
                    }
                },
            };
            let callbackError = undefined;
            if (data) {
                if (options.uploadProgress) {
                    const stream = new stream_buffers_1.ReadableStreamBuffer({ initialSize: data.length, chunkSize: 1 << 20 });
                    stream.put(data);
                    stream.stop();
                    let uploaded = 0;
                    let total = data.length;
                    stream.on('data', (chunk) => {
                        uploaded += chunk.length;
                        try {
                            options.uploadProgress(uploaded, total);
                        }
                        catch (err) {
                            if (!stream.destroyed) {
                                stream.destroy(err);
                            }
                            callbackError = err;
                            reject(err);
                        }
                    });
                    if (options.uploadThrottle) {
                        requestOption.stream = stream.pipe(options.uploadThrottle);
                    }
                    else {
                        requestOption.stream = stream;
                    }
                }
                else if (options.uploadThrottle) {
                    const stream = new stream_buffers_1.ReadableStreamBuffer({ initialSize: data.length, chunkSize: 1 << 20 });
                    stream.put(data);
                    stream.stop();
                    requestOption.stream = stream.pipe(options.uploadThrottle);
                }
                else {
                    requestOption.data = data;
                }
            }
            const uplog = {
                log_type: uplog_1.LogType.Request,
                host: url.hostname,
                port: utils_1.parsePort(url),
                method: options.method || 'GET',
                path: url.pathname,
                total_elapsed_time: 0,
            };
            const beginTime = new Date().getTime();
            HttpClient.httpClient.request(url.toString(), requestOption).then((response) => {
                if (multiJsonEncoded && response.data && response.data instanceof Buffer) {
                    try {
                        response.data = response.data.toString().split(/\s*\n+\s*/).
                            filter((line) => line.length).
                            map((line) => JSON.parse(line));
                    }
                    catch (_a) {
                        // ignore
                    }
                }
                const responseInfo = {
                    request: requestInfo,
                    statusCode: response.status,
                    headers: response.headers,
                    data: response.data,
                    interval: new Date().getTime() - beginTime,
                };
                uplog.status_code = response.status;
                uplog.total_elapsed_time = responseInfo.interval;
                if (response.headers['x-reqid']) {
                    uplog.req_id = response.headers['x-reqid'].toString();
                }
                if (response.res.remoteAddress) {
                    uplog.remote_ip = response.res.remoteAddress;
                    uplog.port = response.res.remotePort;
                }
                try {
                    if (callbackError) {
                        return;
                    }
                    else if (response.status >= 200 && response.status < 400) {
                        this.uplogBuffer.log(uplog).finally(() => {
                            resolve(response);
                        });
                    }
                    else if (response.data && response.data.error) {
                        const error = new Error(response.data.error);
                        responseInfo.error = error;
                        uplog.error_type = uplog_1.getErrorTypeFromStatusCode(response.status);
                        uplog.error_description = error.message;
                        if (options.stats) {
                            options.stats.errorType = uplog.error_type;
                            options.stats.errorDescription = uplog.error_description;
                        }
                        this.uplogBuffer.log(uplog).finally(() => {
                            if (urls.length > 0) {
                                this.call(urls, options).then(resolve, reject);
                            }
                            else {
                                reject(error);
                            }
                        });
                    }
                    else {
                        let error = undefined;
                        if (response.data) {
                            try {
                                const data = JSON.parse(response.data);
                                if (data.error) {
                                    error = new Error(data.error);
                                }
                            }
                            catch (_b) {
                                // Ignore
                            }
                        }
                        error || (error = new Error(response.res.statusMessage));
                        responseInfo.error = error;
                        uplog.error_type = uplog_1.getErrorTypeFromStatusCode(response.status);
                        uplog.error_description = error.message;
                        if (options.stats) {
                            options.stats.errorType = uplog.error_type;
                            options.stats.errorDescription = uplog.error_description;
                        }
                        this.uplogBuffer.log(uplog).finally(() => {
                            if (urls.length > 0) {
                                this.call(urls, options).then(resolve, reject);
                            }
                            else {
                                reject(error);
                            }
                        });
                    }
                }
                finally {
                    if (this.clientOptions.responseCallback) {
                        this.clientOptions.responseCallback(responseInfo);
                    }
                }
            }).catch((err) => {
                const responseInfo = {
                    request: requestInfo,
                    interval: new Date().getTime() - beginTime,
                    error: err,
                };
                if (this.clientOptions.responseCallback) {
                    this.clientOptions.responseCallback(responseInfo);
                }
                uplog.total_elapsed_time = responseInfo.interval;
                uplog.error_type = uplog_1.getErrorTypeFromRequestError(err);
                uplog.error_description = err.message;
                if (options.stats) {
                    options.stats.errorType = uplog.error_type;
                    options.stats.errorDescription = uplog.error_description;
                }
                this.uplogBuffer.log(uplog).finally(() => {
                    if (callbackError) {
                        return;
                    }
                    else if (urls.length > 0) {
                        this.call(urls, options).then(resolve, reject);
                    }
                    else {
                        reject(err);
                    }
                });
            });
        });
    }
    makeUrl(base, options) {
        const url = new URL(base);
        if (options.path) {
            url.pathname = options.path;
        }
        let protocol = this.clientOptions.protocol;
        if (protocol) {
            switch (protocol) {
                case "http":
                    url.protocol = "http";
                    break;
                case "https":
                    url.protocol = "https";
                    break;
            }
        }
        if (options.query) {
            options.query.forEach((value, name) => {
                url.searchParams.append(name, value);
            });
        }
        return url;
    }
    makeAuthorization(url, options) {
        var _a;
        let data = undefined;
        if (options.data) {
            if (options.dataType === 'json') {
                data = JSON.stringify(options.data);
            }
            data = options.data.toString();
        }
        return kodo_auth_1.generateAccessTokenV2(this.clientOptions.accessKey, this.clientOptions.secretKey, url.toString(), (_a = options.method) !== null && _a !== void 0 ? _a : 'GET', options.contentType, data);
    }
    isRetry(response) {
        const dontRetryStatusCodes = [501, 579, 599, 608, 612, 614, 616,
            618, 630, 631, 632, 640, 701];
        return !response.headers['x-reqid'] ||
            response.status >= 500 && !dontRetryStatusCodes.find((status) => status === response.status);
    }
}
exports.HttpClient = HttpClient;
HttpClient.httpClient = new urllib_1.HttpClient2();
//# sourceMappingURL=http-client.js.map