import { request as httpRequest } from 'node:http';
import { request as httpsRequest } from 'node:https';
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
const MAX_REDIRECTS = 5;
const log = (...args) => console.debug('[fetchURL]', ...args);
export class FetchCachedFileError extends Error {
    constructor(message) {
        super(message);
        this.name = 'FetchCachedFileError';
    }
}
const isRedirectStatus = (statusCode) => {
    if (typeof statusCode !== 'number')
        return false;
    return statusCode >= 300 && statusCode < 400;
};
const isSuccessStatus = (statusCode) => {
    if (typeof statusCode !== 'number')
        return false;
    return statusCode >= 200 && statusCode < 300;
};
const normalizeHeaderValue = (headers, key) => {
    const value = headers[key];
    if (Array.isArray(value)) {
        return value[0];
    }
    return value ?? undefined;
};
const fetchResponse = async (url, redirectCount = 0) => {
    log('fetchResponse invoked', { url: url.toString(), redirectCount });
    if (redirectCount > MAX_REDIRECTS) {
        throw new FetchCachedFileError('Too many redirects while fetching cached file');
    }
    const requestFn = url.protocol === 'https:' ? httpsRequest : httpRequest;
    return await new Promise((resolve, reject) => {
        const request = requestFn(url, response => {
            if (isSuccessStatus(response.statusCode)) {
                log('fetchResponse received success status', { statusCode: response.statusCode, url: url.toString() });
                resolve(response);
                return;
            }
            if (isRedirectStatus(response.statusCode)) {
                log('fetchResponse handling redirect', { statusCode: response.statusCode, url: url.toString() });
                const locationHeader = normalizeHeaderValue(response.headers, 'location');
                if (!locationHeader) {
                    reject(new FetchCachedFileError('Redirect location header missing while fetching cached file'));
                    return;
                }
                try {
                    const redirectedUrl = new URL(locationHeader, url);
                    log('fetchResponse following redirect', { redirectedTo: redirectedUrl.toString() });
                    fetchResponse(redirectedUrl, redirectCount + 1).then(resolve).catch(reject);
                }
                catch (error) {
                    reject(new FetchCachedFileError(`Invalid redirect location: ${error.message}`));
                }
                return;
            }
            log('fetchResponse received error status', { statusCode: response.statusCode, statusMessage: response.statusMessage });
            reject(new FetchCachedFileError(`Failed to fetch cached file. Status: ${response.statusCode ?? 'unknown'} ${response.statusMessage ?? ''}`.trim()));
        });
        request.on('error', (error) => {
            log('fetchResponse network error', { message: error.message });
            reject(new FetchCachedFileError(`Network error fetching cached file: ${error.message}`));
        });
        request.end();
    });
};
const collectBody = async (response) => {
    const chunks = [];
    return await new Promise((resolve, reject) => {
        log('collectBody streaming response');
        response.on('data', (chunk) => {
            chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : chunk);
        });
        response.on('end', () => {
            const buffer = Buffer.concat(chunks);
            log('collectBody completed', { byteLength: buffer.length });
            resolve(buffer);
        });
        response.on('error', error => {
            log('collectBody stream error', { message: error.message });
            reject(new FetchCachedFileError(`Error reading cached file stream: ${error.message}`));
        });
    });
};
export const fetchURL = async (sourceUrl) => {
    if (!sourceUrl || !sourceUrl.trim()) {
        throw new FetchCachedFileError('A non-empty sourceUrl must be provided');
    }
    let parsedUrl;
    try {
        parsedUrl = new URL(sourceUrl);
    }
    catch {
        throw new FetchCachedFileError('The provided sourceUrl is not a valid URL');
    }
    if (!ALLOWED_PROTOCOLS.has(parsedUrl.protocol)) {
        throw new FetchCachedFileError('sourceUrl must use the http or https protocol');
    }
    const response = await fetchResponse(parsedUrl);
    const body = await collectBody(response);
    const contentLengthHeader = normalizeHeaderValue(response.headers, 'content-length');
    const parsedContentLength = contentLengthHeader ? Number(contentLengthHeader) : undefined;
    const result = {
        body,
        contentType: normalizeHeaderValue(response.headers, 'content-type'),
        contentEncoding: normalizeHeaderValue(response.headers, 'content-encoding'),
        contentDisposition: normalizeHeaderValue(response.headers, 'content-disposition'),
        cacheControl: normalizeHeaderValue(response.headers, 'cache-control'),
        contentLength: Number.isFinite(parsedContentLength) ? parsedContentLength : undefined,
        eTag: normalizeHeaderValue(response.headers, 'etag'),
        lastModified: normalizeHeaderValue(response.headers, 'last-modified')
    };
    log('fetchURL returning result', {
        sourceUrl,
        contentLength: result.contentLength ?? result.body.length,
        contentType: result.contentType
    });
    return result;
};
