"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestInterceptor = exports.MAX_SERIALIZED_BODY_SIZE_BYTES = void 0;
exports.headerStringToHeaders = headerStringToHeaders;
exports.headerStringToProtocol = headerStringToProtocol;
const loggingProvider_1 = require("../providers/logging/loggingProvider");
const uuid_1 = require("uuid");
const httpUtil_1 = require("./httpUtil");
const pureUtil_1 = require("../util/pureUtil");
exports.MAX_SERIALIZED_BODY_SIZE_BYTES = 1024 * 1024;
const HEADER_EXTRACT_REGEXP = /^(?<rawKey>[^:]+)(:)(?<value>.*)$/;
function headerStringToHeaders(rawHeader) {
    const headers = {};
    if (!rawHeader) {
        return headers;
    }
    rawHeader
        .split(/\r?\n/)
        .filter((value) => value !== '')
        .filter((value) => HEADER_EXTRACT_REGEXP.test(value))
        .map((value) => value.match(HEADER_EXTRACT_REGEXP).groups)
        .forEach(({ rawKey, value }) => {
        const normalizedHeader = rawKey.toLowerCase().trim();
        const values = headers[normalizedHeader] ?? [];
        if (value?.trim()) {
            values.push(value.trim());
        }
        headers[normalizedHeader] = values;
    });
    return headers;
}
function headerStringToProtocol(rawHeader) {
    if (!rawHeader) {
        return;
    }
    const firstRowTrimmed = rawHeader.split(/\r?\n/, 2)?.[0]?.trim();
    return firstRowTrimmed?.match(/\s(\S+)$/)?.[1];
}
function headerRecordToHeaderMultiMap(input) {
    const result = {};
    Object.entries(input).forEach(([key, value]) => {
        result[key] = [value];
    });
    return result;
}
function handleRawResponseBody(body) {
    body = body ?? '';
    switch (typeof body) {
        case 'number':
            return { body: body.toString(), responseSize: 8 };
        case 'boolean':
            return { body: body.toString(), responseSize: 4 };
        case 'string':
            return {
                body: body.substring(0, exports.MAX_SERIALIZED_BODY_SIZE_BYTES),
                responseSize: body.length,
            };
        case 'object':
            const serializedBody = JSON.stringify(body);
            if (serializedBody.length <= exports.MAX_SERIALIZED_BODY_SIZE_BYTES) {
                return { body, responseSize: serializedBody.length };
            }
            return {
                body: serializedBody.substring(0, exports.MAX_SERIALIZED_BODY_SIZE_BYTES),
                responseSize: serializedBody.length,
            };
        default:
            throw new Error(`Unhandled request intercept body type: [${typeof body}]`);
    }
}
function getContentLength(headers) {
    const contentLengthAsString = (0, pureUtil_1.getCaseInsensitiveProperty)(headers, 'content-length');
    return contentLengthAsString ? Number(contentLengthAsString) : undefined;
}
class RequestInterceptor {
    constructor() {
        this.interceptedRequests = [];
        this.handleRequest = this.handleRequest.bind(this);
        this.handleResponse = this.handleResponse.bind(this);
        this.handleError = this.handleError.bind(this);
    }
    get interceptedRequestBuffer() {
        return [...this.interceptedRequests];
    }
    clearInterceptBuffer() {
        this.interceptedRequests = [];
    }
    registerInterceptors(axiosInstance) {
        this.requestInterceptorId = axiosInstance.interceptors.request.use(this.handleRequest, this.handleError);
        this.responseInterceptorId = axiosInstance.interceptors.response.use(this.handleResponse, this.handleError);
    }
    ejectInterceptors(axiosInstance) {
        try {
            if (this.requestInterceptorId !== undefined) {
                axiosInstance.interceptors.request.eject(this.requestInterceptorId);
            }
        }
        catch (e) {
            loggingProvider_1.logger.warn(`Unable to eject request interceptor ${this.requestInterceptorId}`);
        }
        try {
            if (this.responseInterceptorId !== undefined) {
                axiosInstance.interceptors.response.eject(this.responseInterceptorId);
            }
        }
        catch (e) {
            loggingProvider_1.logger.warn(`Unable to eject request interceptor ${this.responseInterceptorId}`);
        }
    }
    handleRequest(config) {
        try {
            const requestId = (0, uuid_1.v4)();
            config.mablRequestId = requestId;
            const requestResponseChain = {
                requestId,
                request: {
                    headers: (config.headers?.toJSON() ?? {}),
                    url: config?.url ?? '<Unknown URL>',
                    method: config.method?.toUpperCase(),
                    request_sent_time: Date.now(),
                },
            };
            if (typeof config.data === 'string') {
                requestResponseChain.request.body = config.data.substring(0, exports.MAX_SERIALIZED_BODY_SIZE_BYTES);
            }
            this.interceptedRequests.push(requestResponseChain);
        }
        catch (e) {
            loggingProvider_1.logger.warn('Error in the response interceptor', e);
        }
        return config;
    }
    handleResponse(response) {
        try {
            const responseReceivedTime = Date.now();
            const chainEntry = this.getOrCreateEntry(response.config);
            const request = response.request;
            if (request) {
                chainEntry.request.headers = headerStringToHeaders(request._header);
                chainEntry.request.protocol = request?._header
                    ? headerStringToProtocol(request._header)
                    : '<Unknown protocol>';
            }
            const { body, responseSize } = handleRawResponseBody(response.data);
            const headersRecord = response.headers;
            const contentLength = getContentLength(headersRecord);
            chainEntry.response = {
                headers: headerRecordToHeaderMultiMap(headersRecord),
                status: response.status,
                response_received_time: responseReceivedTime,
                response_size: contentLength ?? responseSize,
                body,
            };
        }
        catch (e) {
            loggingProvider_1.logger.warn('Error in the response interceptor', e);
        }
        return response;
    }
    handleError(error) {
        try {
            const config = error.config;
            let chainEntry;
            if (config) {
                chainEntry = this.getOrCreateEntry(config);
            }
            else if (this.interceptedRequests.length) {
                chainEntry =
                    this.interceptedRequests[this.interceptedRequests.length - 1];
            }
            const anyError = error;
            if (anyError?.name === 'AggregateError' &&
                (!error.message || error.message.trim() === '') &&
                Array.isArray(anyError.errors)) {
                const underlying = anyError.errors.find((e) => e?.message);
                if (underlying?.message) {
                    anyError.message = underlying.message;
                }
            }
            if (chainEntry) {
                chainEntry.error = {
                    code: error.code ?? httpUtil_1.GENERAL_API_STEP_EXECUTION_ERROR_CODE,
                    message: error.message,
                    error_received_time: Date.now(),
                };
            }
            if (error.response) {
                this.handleResponse(error.response);
            }
        }
        catch (e) {
            loggingProvider_1.logger.warn('Error in the error interceptor', e);
        }
        return Promise.reject(error);
    }
    getOrCreateEntry(config) {
        const existingEntry = this.findEntry(config);
        if (existingEntry) {
            return existingEntry;
        }
        this.handleRequest(config);
        return this.findEntry(config);
    }
    findEntry(config) {
        return this.interceptedRequests.find((chain) => chain.requestId === config.mablRequestId);
    }
}
exports.RequestInterceptor = RequestInterceptor;
