"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkError = exports.NetworkErrorCode = exports._intercept = void 0;
exports.resolveProxyUrl = resolveProxyUrl;
exports.generateProxyCacheKey = generateProxyCacheKey;
exports.createDispatcher = createDispatcher;
exports.execute = execute;
const undici_1 = require("undici");
// @ts-ignore
const proto_1 = require("./payments/generated/proto");
const Defaults = proto_1.types.HttpDefault;
/** Optional mock intercept — set by smoke test in mock mode only. */
exports._intercept = null;
/** Network error codes from proto (single source of truth). */
exports.NetworkErrorCode = proto_1.types.NetworkErrorCode;
/**
 * Network error for HTTP transport failures (timeouts, connection errors, config).
 * Uses proto-generated NetworkErrorCode for cross-SDK parity with IntegrationError/ConnectorError.
 */
class NetworkError extends Error {
    code;
    statusCode;
    body;
    headers;
    constructor(message, code = proto_1.types.NetworkErrorCode.NETWORK_ERROR_CODE_UNSPECIFIED, statusCode, body, headers) {
        super(message);
        this.code = code;
        this.statusCode = statusCode;
        this.body = body;
        this.headers = headers;
        this.name = "NetworkError";
    }
    /**
     * String error code for parity with IntegrationError/ConnectorError (e.g. "CONNECT_TIMEOUT").
     * Use for logging, display, and simple comparisons.
     */
    get errorCode() {
        return proto_1.types.NetworkErrorCode[this.code] ?? "NETWORK_ERROR_CODE_UNSPECIFIED";
    }
}
exports.NetworkError = NetworkError;
/**
 * Resolve proxy URL, honoring bypass rules.
 */
function resolveProxyUrl(url, proxy) {
    if (!proxy)
        return null;
    const shouldBypass = Array.isArray(proxy.bypassUrls) && proxy.bypassUrls.includes(url);
    if (shouldBypass)
        return null;
    return proxy.httpsUrl || proxy.httpUrl || null;
}
/**
 * Generate a cache key from proxy configuration for HTTP client caching.
 * Returns empty string when no proxy is configured.
 */
function generateProxyCacheKey(proxy) {
    if (!proxy)
        return "";
    const httpUrl = proxy.httpUrl || "";
    const httpsUrl = proxy.httpsUrl || "";
    const bypassUrls = Array.isArray(proxy.bypassUrls)
        ? [...proxy.bypassUrls].sort().join(",")
        : "";
    return `${httpUrl}|${httpsUrl}|${bypassUrls}`;
}
/**
 * Creates a high-performance dispatcher with specialized fintech timeouts.
 * (The instance-level connection pool)
 */
function createDispatcher(config) {
    let ca;
    if (config.caCert) {
        if (config.caCert.pem) {
            ca = config.caCert.pem;
        }
        else if (config.caCert.der) {
            ca = config.caCert.der;
        }
    }
    const connectOptions = {
        timeout: config.connectTimeoutMs ?? Defaults.CONNECT_TIMEOUT_MS,
        ca,
    };
    const commonOptions = {
        headersTimeout: config.responseTimeoutMs ?? Defaults.RESPONSE_TIMEOUT_MS,
        bodyTimeout: config.responseTimeoutMs ?? Defaults.RESPONSE_TIMEOUT_MS,
        keepAliveTimeout: config.keepAliveTimeoutMs ?? Defaults.KEEP_ALIVE_TIMEOUT_MS,
    };
    const proxyUrl = config.proxy?.httpsUrl || config.proxy?.httpUrl;
    try {
        if (proxyUrl) {
            // For a CONNECT proxy:
            //   - `connect`     governs TLS to the proxy itself (HTTP proxy → no TLS needed)
            //   - `requestTls`  governs TLS for the tunneled origin connection (the CONNECT tunnel)
            //                   This is where the custom CA must live so Node trusts the
            //                   mitmproxy-issued leaf certificate for api.stripe.com, etc.
            return new undici_1.ProxyAgent({
                uri: proxyUrl,
                ...commonOptions,
                connect: { timeout: config.connectTimeoutMs ?? Defaults.CONNECT_TIMEOUT_MS },
                requestTls: { ca },
            });
        }
        return new undici_1.Agent({ ...commonOptions, connect: connectOptions });
    }
    catch (error) {
        const code = proxyUrl ? proto_1.types.NetworkErrorCode.INVALID_PROXY_CONFIGURATION : proto_1.types.NetworkErrorCode.CLIENT_INITIALIZATION_FAILURE;
        throw new NetworkError(`Internal HTTP setup failed: ${error.message}`, code, 500);
    }
}
/**
 * Standardized network execution engine for Unified Connector Service.
 */
async function execute(request, options = {}, dispatcher // Pass the instance-owned pool here
) {
    // Check for mock intercept (used in smoke test mock mode)
    if (exports._intercept) {
        return (0, exports._intercept)(request);
    }
    const { url, method, headers, body } = request;
    try {
        new URL(url);
    }
    catch {
        throw new NetworkError(`Invalid URL: ${url}`, proto_1.types.NetworkErrorCode.URL_PARSING_FAILED);
    }
    const totalTimeout = options.totalTimeoutMs ?? Defaults.TOTAL_TIMEOUT_MS;
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), totalTimeout);
    const startTime = Date.now();
    try {
        const response = await fetch(url, {
            method: method.toUpperCase(),
            headers: headers || {},
            body: body ? Buffer.from(body) : undefined,
            redirect: "manual",
            signal: controller.signal,
            // @ts-ignore
            dispatcher,
        });
        const responseHeaders = {};
        response.headers.forEach((v, k) => { responseHeaders[k.toLowerCase()] = v; });
        let responseBody;
        try {
            responseBody = new Uint8Array(await response.arrayBuffer());
        }
        catch (e) {
            throw new NetworkError(`Failed to read response body: ${e?.message || e}`, proto_1.types.NetworkErrorCode.RESPONSE_DECODING_FAILED, response.status);
        }
        return {
            statusCode: response.status,
            headers: responseHeaders,
            body: responseBody,
            latencyMs: Date.now() - startTime
        };
    }
    catch (error) {
        if (error instanceof NetworkError)
            throw error;
        if (error.name === 'AbortError') {
            throw new NetworkError(`Total Request Timeout: ${method} ${url} exceeded ${totalTimeout}ms`, proto_1.types.NetworkErrorCode.TOTAL_TIMEOUT_EXCEEDED, 504);
        }
        const cause = error.cause;
        if (cause) {
            if (cause.code === 'UND_ERR_CONNECT_TIMEOUT') {
                throw new NetworkError(`Connection Timeout: Failed to connect to ${url}`, proto_1.types.NetworkErrorCode.CONNECT_TIMEOUT_EXCEEDED, 504);
            }
            if (cause.code === 'UND_ERR_BODY_TIMEOUT' || cause.code === 'UND_ERR_HEADERS_TIMEOUT') {
                throw new NetworkError(`Response Timeout: Gateway ${url} accepted connection but failed to respond`, proto_1.types.NetworkErrorCode.RESPONSE_TIMEOUT_EXCEEDED, 504);
            }
        }
        throw new NetworkError(`Network Error: ${error.message}`, proto_1.types.NetworkErrorCode.NETWORK_FAILURE, 500);
    }
    finally {
        clearTimeout(timeoutId);
    }
}
//# sourceMappingURL=http_client.js.map