import { existsSync, readFileSync } from 'node:fs';
import { getCACertificates } from 'node:tls';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { isLocalHostname, readPemEntriesFromDirectory, resolvePathFromGoodVibesRoot } from './shared.js';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
const NETWORK_FETCH_WRAPPER = Symbol.for('goodvibes.network.fetch-wrapper');
const NETWORK_FETCH_MANAGER = Symbol.for('goodvibes.network.fetch-manager');
function readMode(configManager) {
    return configManager.get('network.outboundTls.mode');
}
function readAllowInsecureLocalhost(configManager) {
    return Boolean(configManager.get('network.outboundTls.allowInsecureLocalhost'));
}
function readCustomCaFile(configManager) {
    return resolvePathFromGoodVibesRoot(configManager.get('network.outboundTls.customCaFile'), configManager);
}
function readCustomCaDir(configManager) {
    return resolvePathFromGoodVibesRoot(configManager.get('network.outboundTls.customCaDir'), configManager);
}
function loadCustomCaEntries(configManager) {
    const errors = [];
    const entries = [];
    const customCaFile = readCustomCaFile(configManager);
    const customCaDir = readCustomCaDir(configManager);
    if (customCaFile) {
        if (existsSync(customCaFile)) {
            entries.push(readFileSync(customCaFile, 'utf-8'));
        }
        else {
            errors.push(`Custom CA file not found: ${customCaFile}`);
        }
    }
    if (customCaDir) {
        if (existsSync(customCaDir)) {
            for (const path of readPemEntriesFromDirectory(customCaDir)) {
                entries.push(readFileSync(path, 'utf-8'));
            }
        }
        else {
            errors.push(`Custom CA directory not found: ${customCaDir}`);
        }
    }
    return {
        entries,
        errors,
        ...(customCaFile ? { customCaFile } : {}),
        ...(customCaDir ? { customCaDir } : {}),
    };
}
function getBundledCaEntries() {
    return getCACertificates('bundled');
}
export function inspectOutboundTls(configManager) {
    const mode = readMode(configManager);
    const allowInsecureLocalhost = readAllowInsecureLocalhost(configManager);
    const custom = loadCustomCaEntries(configManager);
    return {
        mode,
        allowInsecureLocalhost,
        ...(custom.customCaFile ? { customCaFile: custom.customCaFile } : {}),
        ...(custom.customCaDir ? { customCaDir: custom.customCaDir } : {}),
        customCaEntryCount: custom.entries.length,
        effectiveCaStrategy: mode === 'bundled'
            ? 'bun-default'
            : mode === 'bundled+custom'
                ? 'bundled+custom'
                : 'custom',
        errors: custom.errors,
    };
}
function resolveOutboundTlsContext(configManager) {
    const snapshot = inspectOutboundTls(configManager);
    const custom = loadCustomCaEntries(configManager);
    const caEntries = snapshot.mode === 'bundled'
        ? undefined
        : snapshot.mode === 'bundled+custom'
            ? [...getBundledCaEntries(), ...custom.entries]
            : [...custom.entries];
    return { snapshot, ...(caEntries ? { caEntries } : {}) };
}
function extractRequestUrl(input) {
    try {
        if (typeof input === 'string')
            return new URL(input);
        if (input instanceof URL)
            return input;
        if (typeof Request !== 'undefined' && input instanceof Request)
            return new URL(input.url);
        return null;
    }
    catch {
        return null;
    }
}
function extractRequestMethod(input, init) {
    if (init?.method)
        return init.method.toUpperCase();
    if (typeof Request !== 'undefined' && input instanceof Request)
        return input.method.toUpperCase();
    return 'GET';
}
function extractHeaderValue(headers, name) {
    if (!headers)
        return undefined;
    if (headers instanceof Headers)
        return headers.get(name) ?? undefined;
    if (Array.isArray(headers)) {
        const loweredName = name.toLowerCase();
        const match = headers.find(([key]) => key.toLowerCase() === loweredName);
        return match?.[1];
    }
    for (const [key, value] of Object.entries(headers)) {
        if (key.toLowerCase() !== name.toLowerCase())
            continue;
        if (Array.isArray(value))
            return value.join(', ');
        return value;
    }
    return undefined;
}
function shouldTraceProviderRequest(url, method) {
    if (method !== 'POST')
        return false;
    return (url.pathname.endsWith('/chat/completions') ||
        url.pathname.endsWith('/responses') ||
        url.pathname.endsWith('/messages'));
}
async function executeNetworkFetch(fetchImpl, input, init, configManager) {
    const nextInit = applyOutboundTlsToFetchInit(input, init, configManager);
    const url = extractRequestUrl(input);
    const method = extractRequestMethod(input, nextInit);
    const shouldTrace = url ? shouldTraceProviderRequest(url, method) : false;
    if (shouldTrace && url) {
        logger.debug('Outbound provider request', {
            method,
            host: url.host,
            path: url.pathname,
            contentType: extractHeaderValue(nextInit.headers, 'content-type'),
            contentLength: extractHeaderValue(nextInit.headers, 'content-length'),
        });
    }
    try {
        const response = await fetchImpl(input, nextInit);
        if (shouldTrace && url) {
            logger.debug('Outbound provider response', {
                method,
                host: url.host,
                path: url.pathname,
                status: response.status,
                requestId: response.headers.get('x-request-id') ?? response.headers.get('request-id') ?? undefined,
            });
        }
        return response;
    }
    catch (error) {
        if (shouldTrace && url) {
            logger.error('Outbound provider request failed', {
                method,
                host: url.host,
                path: url.pathname,
                error: summarizeError(error),
            });
        }
        throw error;
    }
}
export function applyOutboundTlsToFetchInit(input, init, configManager) {
    const url = extractRequestUrl(input);
    const nextInit = { ...(init ?? {}) };
    if (!url || url.protocol !== 'https:')
        return nextInit;
    const context = resolveOutboundTlsContext(configManager);
    const existingTls = nextInit.tls ?? {};
    const nextTls = { ...existingTls };
    if (context.snapshot.mode === 'custom' && !nextTls.ca && (!context.caEntries || context.caEntries.length === 0)) {
        throw new Error('network.outboundTls.mode is custom, but no custom CA entries were loaded. Configure network.outboundTls.customCaFile or network.outboundTls.customCaDir.');
    }
    if (!nextTls.ca && context.caEntries && context.caEntries.length > 0) {
        nextTls.ca = [...context.caEntries];
    }
    if (context.snapshot.allowInsecureLocalhost
        && nextTls.rejectUnauthorized === undefined
        && isLocalHostname(url.hostname)) {
        nextTls.rejectUnauthorized = false;
    }
    return Object.keys(nextTls).length > 0
        ? { ...nextInit, tls: nextTls }
        : nextInit;
}
export function createNetworkFetch(fetchImpl, configManager) {
    const wrapped = (async (input, init) => executeNetworkFetch(fetchImpl, input, init, configManager));
    Object.assign(wrapped, fetchImpl);
    return wrapped;
}
export class GlobalNetworkTransportInstaller {
    originalFetchRef = null;
    configManager = null;
    setConfigManager(configManager) {
        this.configManager = configManager;
    }
    install(configManager) {
        const currentFetch = globalThis.fetch;
        if (currentFetch[NETWORK_FETCH_MANAGER]) {
            currentFetch[NETWORK_FETCH_MANAGER].setConfigManager(configManager);
            logger.debug('Updated global network transport', { ...inspectOutboundTls(configManager) });
            return;
        }
        this.configManager = configManager;
        this.originalFetchRef = globalThis.fetch.bind(globalThis);
        const wrapped = (async (input, init) => {
            if (!this.originalFetchRef || !this.configManager) {
                throw new Error('Global network transport is not initialized correctly.');
            }
            return executeNetworkFetch(this.originalFetchRef, input, init, this.configManager);
        });
        Object.assign(wrapped, globalThis.fetch);
        wrapped[NETWORK_FETCH_WRAPPER] = true;
        wrapped[NETWORK_FETCH_MANAGER] = this;
        globalThis.fetch = wrapped;
        logger.debug('Installed global network transport', { ...inspectOutboundTls(configManager) });
    }
}
