"use strict";
/**
 * Remote Proxy Client for CLIProxyAPI
 *
 * HTTP client for health checks and connection testing against remote CLIProxyAPI instances.
 * Uses native fetch API with aggressive timeout for CLI responsiveness.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.testConnection = exports.checkRemoteProxy = void 0;
const https = __importStar(require("https"));
const config_generator_1 = require("../config/config-generator");
/** Default timeout for remote proxy requests (aggressive for CLI UX) */
const DEFAULT_TIMEOUT_MS = 2000;
/**
 * Get standard web port for protocol (for URL display omission)
 * These are the ports that browsers/HTTP clients use by default.
 * HTTP: 80, HTTPS: 443
 */
function getStandardWebPort(protocol) {
    return protocol === 'https' ? 443 : 80;
}
/**
 * Get effective port for CLIProxyAPI connection.
 * Validates port and uses protocol-based default for invalid/undefined values.
 */
function getEffectivePort(port, protocol) {
    const validatedPort = (0, config_generator_1.validateRemotePort)(port);
    return validatedPort ?? (0, config_generator_1.getRemoteDefaultPort)(protocol);
}
/**
 * Build URL for remote proxy
 * Only omits port from URL if it matches standard web ports (80/443),
 * otherwise always includes the port for clarity.
 */
function buildProxyUrl(host, port, protocol, path) {
    const effectivePort = getEffectivePort(port, protocol);
    const standardWebPort = getStandardWebPort(protocol);
    // Only omit port from URL if it matches the standard web port for the protocol
    // e.g., HTTP on port 80 or HTTPS on port 443
    if (effectivePort === standardWebPort) {
        return `${protocol}://${host}${path}`;
    }
    return `${protocol}://${host}:${effectivePort}${path}`;
}
/**
 * Map error to RemoteProxyErrorCode
 *
 * Handles various error types including:
 * - NodeJS.ErrnoException (ECONNREFUSED, ETIMEDOUT, ENOTFOUND, ENETUNREACH)
 * - Fetch errors (AbortError, TypeError, "fetch failed")
 * - HTTP status codes (401, 403)
 */
function mapErrorToCode(error, statusCode) {
    const message = error.message.toLowerCase();
    // Handle error.code safely - it may be string, number, or undefined
    const rawCode = error.code;
    const code = typeof rawCode === 'string' ? rawCode.toLowerCase() : undefined;
    // DNS resolution failed
    if (code === 'enotfound' ||
        code === 'eai_again' ||
        message.includes('getaddrinfo') ||
        message.includes('dns')) {
        return 'DNS_FAILED';
    }
    // Network unreachable / host unreachable
    if (code === 'enetunreach' ||
        code === 'ehostunreach' ||
        code === 'enetdown' ||
        message.includes('network') ||
        message.includes('unreachable')) {
        return 'NETWORK_UNREACHABLE';
    }
    // Connection refused
    if (code === 'econnrefused' || message.includes('connection refused')) {
        return 'CONNECTION_REFUSED';
    }
    // Timeout
    if (code === 'etimedout' ||
        code === 'timeout' ||
        message.includes('timeout') ||
        message.includes('aborted')) {
        return 'TIMEOUT';
    }
    // Auth failed (401/403)
    if (statusCode === 401 || statusCode === 403) {
        return 'AUTH_FAILED';
    }
    // Generic "fetch failed" - try to extract cause
    if (message.includes('fetch failed') || message.includes('failed to fetch')) {
        // Check if there's a cause property (Node.js 18+)
        const cause = error.cause;
        if (cause) {
            return mapErrorToCode(cause);
        }
        // Likely network/DNS issue if no specific cause
        return 'NETWORK_UNREACHABLE';
    }
    return 'UNKNOWN';
}
/**
 * Get human-readable error message from error code
 */
function getErrorMessage(errorCode, rawError) {
    switch (errorCode) {
        case 'CONNECTION_REFUSED':
            return 'Connection refused - is the proxy running?';
        case 'TIMEOUT':
            return 'Connection timed out - server may be slow or unreachable';
        case 'AUTH_FAILED':
            return 'Authentication failed - check auth token';
        case 'DNS_FAILED':
            return 'DNS lookup failed - check hostname';
        case 'NETWORK_UNREACHABLE':
            return 'Network unreachable - check if host is on same network';
        default:
            return rawError || 'Connection failed';
    }
}
/**
 * Create a custom HTTPS agent for self-signed certificate support
 */
function createHttpsAgent(allowSelfSigned) {
    if (!allowSelfSigned)
        return undefined;
    return new https.Agent({
        rejectUnauthorized: false,
    });
}
/**
 * Check health of remote CLIProxyAPI instance
 *
 * Uses root endpoint (/) for health check since CLIProxyAPI doesn't expose /health.
 * Root is cheap and avoids false negatives from slower model-list endpoints.
 *
 * @param config Remote proxy client configuration
 * @returns RemoteProxyStatus with reachability and latency
 */
async function checkRemoteProxy(config) {
    const { host, port, protocol, authToken, allowSelfSigned = false } = config;
    const timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
    // Validate host is provided
    if (!host || host.trim() === '') {
        return {
            reachable: false,
            error: 'Host is required',
            errorCode: 'UNKNOWN',
        };
    }
    // Use root endpoint for liveness check - cheap and available across deployments
    const url = buildProxyUrl(host, port, protocol, '/');
    const startTime = Date.now();
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    try {
        // Build request options
        const headers = {
            Accept: 'application/json',
        };
        if (authToken) {
            headers['Authorization'] = `Bearer ${authToken}`;
        }
        // For HTTPS with self-signed certs, we need to use native https module
        // Bun's fetch doesn't support custom agents
        let response;
        if (protocol === 'https' && allowSelfSigned) {
            // Warn about security implications
            console.error('[!] Allowing self-signed certificate - not recommended for production');
            // Use native https module for self-signed cert support
            response = await new Promise((resolve, reject) => {
                const agent = createHttpsAgent(true);
                let settled = false;
                const settle = (callback) => {
                    if (settled)
                        return;
                    settled = true;
                    clearTimeout(reqTimeout);
                    callback();
                };
                const reqTimeout = setTimeout(() => {
                    const timeoutError = new Error('Request timeout');
                    req.destroy(timeoutError);
                    settle(() => reject(timeoutError));
                }, timeout);
                const req = https.request(url, {
                    method: 'GET',
                    headers,
                    agent,
                    timeout,
                }, (res) => {
                    // Health check only needs response headers; don't wait for full body.
                    // This avoids timeout false negatives when servers stream slower payloads.
                    res.resume();
                    settle(() => resolve(new Response(null, {
                        status: res.statusCode || 500,
                        statusText: res.statusMessage ?? '',
                    })));
                });
                req.on('error', (err) => {
                    settle(() => reject(err));
                });
                req.on('timeout', () => {
                    const timeoutError = new Error('Request timeout');
                    req.destroy(timeoutError);
                    settle(() => reject(timeoutError));
                });
                req.end();
            });
        }
        else {
            // Standard fetch for HTTP or HTTPS without self-signed
            response = await fetch(url, {
                signal: controller.signal,
                headers,
            });
        }
        const latencyMs = Date.now() - startTime;
        // Check for auth failure
        if (response.status === 401 || response.status === 403) {
            return {
                reachable: false,
                error: getErrorMessage('AUTH_FAILED'),
                errorCode: 'AUTH_FAILED',
            };
        }
        // 200 OK = healthy
        if (response.ok) {
            return {
                reachable: true,
                latencyMs,
            };
        }
        // Non-200 but connected
        return {
            reachable: false,
            error: `Unexpected status: ${response.status}`,
            errorCode: 'UNKNOWN',
        };
    }
    catch (error) {
        const err = error;
        const errorCode = mapErrorToCode(err);
        return {
            reachable: false,
            error: getErrorMessage(errorCode, err.message),
            errorCode,
        };
    }
    finally {
        clearTimeout(timeoutId);
    }
}
exports.checkRemoteProxy = checkRemoteProxy;
/** Alias for dashboard connection tests. */
async function testConnection(config) {
    return checkRemoteProxy(config);
}
exports.testConnection = testConnection;
//# sourceMappingURL=remote-proxy-client.js.map