"use strict";
/**
 * CLIProxyAPI Stats Fetcher
 *
 * Fetches usage statistics from CLIProxyAPI's management API.
 * Requires usage-statistics-enabled: true in config.yaml.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCliproxyRunning = exports.fetchCliproxyErrorLogContent = exports.fetchCliproxyErrorLogs = exports.fetchCliproxyModels = exports.__testExports = exports.fetchCliproxyUsageRaw = exports.fetchCliproxyStats = void 0;
const auth_token_manager_1 = require("../auth/auth-token-manager");
const proxy_target_resolver_1 = require("../proxy/proxy-target-resolver");
const stats_transformer_1 = require("./stats-transformer");
const oauth_usage_log_transformer_1 = require("./oauth-usage-log-transformer");
const usage_compatibility_transformer_1 = require("./usage-compatibility-transformer");
const cachedUsageQueueResponses = new Map();
const USAGE_QUEUE_BATCH_SIZE = 1000;
const USAGE_QUEUE_MAX_BATCHES = 100;
const USAGE_QUEUE_DRAIN_TIMEOUT_MS = 30000;
/**
 * Fetch usage statistics from CLIProxyAPI management API
 * @param port CLIProxyAPI port (default: 8317)
 * @returns Stats object or null if unavailable
 */
async function fetchCliproxyStats(port) {
    try {
        const [data, authFiles] = await Promise.all([
            fetchCliproxyUsageRaw(port),
            fetchCliproxyAuthFiles(port),
        ]);
        if (!data) {
            return null;
        }
        return (0, stats_transformer_1.buildCliproxyStatsFromUsageResponse)(data, { authFiles: authFiles ?? [] });
    }
    catch {
        // CLIProxyAPI not running or stats endpoint not available
        return null;
    }
}
exports.fetchCliproxyStats = fetchCliproxyStats;
/**
 * Fetch raw usage response from CLIProxyAPI management API
 * Returns the unprocessed API response for transformation by cliproxy-usage-transformer
 */
async function fetchCliproxyUsageRaw(port) {
    let localLogUsage;
    const getLocalLogUsage = () => {
        if (localLogUsage !== undefined) {
            return localLogUsage;
        }
        localLogUsage = (0, proxy_target_resolver_1.getProxyTarget)().isRemote ? null : (0, oauth_usage_log_transformer_1.buildUsageResponseFromCliproxyMainLog)();
        return localLogUsage;
    };
    const mergeLocalLogUsage = (response) => (0, usage_compatibility_transformer_1.mergeUsageResponseWithMissingDetails)(response, getLocalLogUsage());
    const mergeAggregateWithDetails = (aggregate, details) => (0, usage_compatibility_transformer_1.mergeUsageResponseWithMissingDetails)(aggregate, details, { appendExtraDetails: false });
    let legacyAggregateUsage = null;
    const legacyUsage = await fetchManagementJson('/v0/management/usage', port);
    if (legacyUsage?.ok && legacyUsage.data) {
        if ((0, usage_compatibility_transformer_1.hasUsageDetails)(legacyUsage.data)) {
            return mergeLocalLogUsage(legacyUsage.data);
        }
        legacyAggregateUsage = legacyUsage.data;
    }
    let cachedUsageQueueResponse;
    const usageQueue = await fetchUsageQueueRecords(port);
    if (usageQueue?.cacheKey) {
        cachedUsageQueueResponse = cachedUsageQueueResponses.get(usageQueue.cacheKey);
    }
    if (usageQueue && Array.isArray(usageQueue.data)) {
        const queueResponse = (0, usage_compatibility_transformer_1.buildUsageResponseFromQueueRecords)(usageQueue.data);
        if ((0, usage_compatibility_transformer_1.hasUsageDetails)(queueResponse)) {
            const mergedUsageQueueResponse = cachedUsageQueueResponse
                ? (0, usage_compatibility_transformer_1.mergeUsageResponses)(cachedUsageQueueResponse, queueResponse)
                : queueResponse;
            cachedUsageQueueResponses.set(usageQueue.cacheKey, mergedUsageQueueResponse);
            cachedUsageQueueResponse = mergedUsageQueueResponse;
            if (usageQueue.ok) {
                const response = legacyAggregateUsage
                    ? mergeAggregateWithDetails(legacyAggregateUsage, mergedUsageQueueResponse)
                    : mergedUsageQueueResponse;
                return mergeLocalLogUsage(response);
            }
        }
    }
    if (legacyAggregateUsage) {
        const response = cachedUsageQueueResponse
            ? mergeAggregateWithDetails(legacyAggregateUsage, cachedUsageQueueResponse)
            : legacyAggregateUsage;
        return mergeLocalLogUsage(response);
    }
    const apiKeyUsage = await fetchManagementJson('/v0/management/api-key-usage', port);
    if (apiKeyUsage?.ok && apiKeyUsage.data) {
        const apiKeyResponseWithCachedDetails = cachedUsageQueueResponse
            ? mergeAggregateWithDetails((0, usage_compatibility_transformer_1.buildUsageResponseFromApiKeyUsage)(apiKeyUsage.data), cachedUsageQueueResponse)
            : (0, usage_compatibility_transformer_1.buildUsageResponseFromApiKeyUsage)(apiKeyUsage.data);
        const apiKeyResponse = mergeLocalLogUsage(apiKeyResponseWithCachedDetails);
        if ((0, usage_compatibility_transformer_1.hasUsageTotals)(apiKeyResponse)) {
            return apiKeyResponse;
        }
    }
    if (cachedUsageQueueResponse) {
        return mergeLocalLogUsage(cachedUsageQueueResponse);
    }
    const logUsage = getLocalLogUsage();
    if (logUsage && (0, usage_compatibility_transformer_1.hasUsageDetails)(logUsage)) {
        return logUsage;
    }
    if (usageQueue?.ok) {
        return (0, usage_compatibility_transformer_1.buildUsageResponseFromQueueRecords)([]);
    }
    return null;
}
exports.fetchCliproxyUsageRaw = fetchCliproxyUsageRaw;
async function fetchUsageQueueRecords(port) {
    const records = [];
    const seenFullBatchSignatures = new Set();
    let cacheKey = '';
    let status = 0;
    const drainStartedAt = Date.now();
    for (let batchCount = 0; batchCount < USAGE_QUEUE_MAX_BATCHES; batchCount++) {
        const result = await fetchManagementJson(`/v0/management/usage-queue?count=${USAGE_QUEUE_BATCH_SIZE}`, port);
        if (!result) {
            return records.length > 0 ? { ok: false, status, data: records, cacheKey } : null;
        }
        cacheKey || (cacheKey = result.cacheKey);
        status = result.status;
        if (!result.ok || !Array.isArray(result.data)) {
            return records.length > 0 ? { ok: false, status, data: records, cacheKey } : result;
        }
        if (result.data.length === USAGE_QUEUE_BATCH_SIZE) {
            const batchSignature = createUsageQueueBatchSignature(result.data);
            if (seenFullBatchSignatures.has(batchSignature)) {
                return { ok: false, status, data: records, cacheKey };
            }
            seenFullBatchSignatures.add(batchSignature);
        }
        records.push(...result.data);
        if (result.data.length < USAGE_QUEUE_BATCH_SIZE) {
            return { ok: true, status, data: records, cacheKey };
        }
        if (Date.now() - drainStartedAt >= USAGE_QUEUE_DRAIN_TIMEOUT_MS) {
            return { ok: false, status, data: records, cacheKey };
        }
    }
    return { ok: false, status, data: records, cacheKey };
}
function createUsageQueueBatchSignature(records) {
    return JSON.stringify(records);
}
async function fetchManagementJson(endpointPath, port, timeoutMs = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
    try {
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        if (port !== undefined && !target.isRemote) {
            target.port = port;
        }
        const url = (0, proxy_target_resolver_1.buildProxyUrl)(target, endpointPath);
        const headers = target.isRemote
            ? (0, proxy_target_resolver_1.buildManagementHeaders)(target)
            : { Accept: 'application/json', Authorization: `Bearer ${(0, auth_token_manager_1.getEffectiveManagementSecret)()}` };
        const response = await fetch(url, {
            signal: controller.signal,
            headers,
        });
        if (!response.ok) {
            return { ok: false, status: response.status, data: null, cacheKey: url };
        }
        return {
            ok: true,
            status: response.status,
            data: (await response.json()),
            cacheKey: url,
        };
    }
    catch {
        return null;
    }
    finally {
        clearTimeout(timeoutId);
    }
}
async function fetchCliproxyAuthFiles(port) {
    try {
        const result = await fetchManagementJson('/v0/management/auth-files', port);
        if (!result?.ok || !result.data) {
            return null;
        }
        const data = result.data;
        return Array.isArray(data.files) ? data.files : null;
    }
    catch {
        return null;
    }
}
exports.__testExports = {
    clearCachedUsageQueueResponse() {
        cachedUsageQueueResponses.clear();
    },
};
/**
 * Fetch available models from CLIProxyAPI /v1/models endpoint
 * @param port CLIProxyAPI port (default: 8317)
 * @returns Categorized models or null if unavailable
 */
async function fetchCliproxyModels(port) {
    try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 3000);
        // Dynamic target resolution
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        // Allow port override for local testing only
        if (port !== undefined && !target.isRemote) {
            target.port = port;
        }
        const url = (0, proxy_target_resolver_1.buildProxyUrl)(target, '/v1/models');
        // For /v1 endpoints: use remote auth token for remote, effective API key for local
        const headers = target.isRemote
            ? (0, proxy_target_resolver_1.buildProxyHeaders)(target)
            : { Accept: 'application/json', Authorization: `Bearer ${(0, auth_token_manager_1.getEffectiveApiKey)()}` };
        const response = await fetch(url, {
            signal: controller.signal,
            headers,
        });
        clearTimeout(timeoutId);
        if (!response.ok) {
            return null;
        }
        const data = (await response.json());
        // Group models by owned_by field
        const byCategory = {};
        for (const model of data.data) {
            const category = model.owned_by || 'other';
            if (!byCategory[category]) {
                byCategory[category] = [];
            }
            byCategory[category].push(model);
        }
        // Sort models within each category alphabetically
        for (const category of Object.keys(byCategory)) {
            byCategory[category].sort((a, b) => a.id.localeCompare(b.id));
        }
        return {
            models: data.data,
            byCategory,
            totalCount: data.data.length,
        };
    }
    catch {
        return null;
    }
}
exports.fetchCliproxyModels = fetchCliproxyModels;
/**
 * Fetch error log file list from CLIProxyAPI management API
 * @param port CLIProxyAPI port (default: 8317)
 * @returns Array of error log metadata or null if unavailable
 */
async function fetchCliproxyErrorLogs(port) {
    try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 3000);
        // Dynamic target resolution
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        // Allow port override for local testing only
        if (port !== undefined && !target.isRemote) {
            target.port = port;
        }
        const url = (0, proxy_target_resolver_1.buildProxyUrl)(target, '/v0/management/request-error-logs');
        // For management endpoints, use management key for remote, local management secret for local
        const headers = target.isRemote
            ? (0, proxy_target_resolver_1.buildManagementHeaders)(target)
            : { Accept: 'application/json', Authorization: `Bearer ${(0, auth_token_manager_1.getEffectiveManagementSecret)()}` };
        const response = await fetch(url, {
            signal: controller.signal,
            headers,
        });
        clearTimeout(timeoutId);
        if (!response.ok) {
            return null;
        }
        const data = (await response.json());
        return data.files ?? [];
    }
    catch {
        return null;
    }
}
exports.fetchCliproxyErrorLogs = fetchCliproxyErrorLogs;
/**
 * Fetch error log file content from CLIProxyAPI management API
 * @param name Error log filename
 * @param port CLIProxyAPI port (default: 8317)
 * @returns Log file content as string or null if unavailable
 */
async function fetchCliproxyErrorLogContent(name, port) {
    try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 5000);
        // Dynamic target resolution
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        // Allow port override for local testing only
        if (port !== undefined && !target.isRemote) {
            target.port = port;
        }
        const url = (0, proxy_target_resolver_1.buildProxyUrl)(target, `/v0/management/request-error-logs/${encodeURIComponent(name)}`);
        // For management endpoints, use management key for remote, local management secret for local
        const headers = target.isRemote
            ? (0, proxy_target_resolver_1.buildManagementHeaders)(target)
            : { Authorization: `Bearer ${(0, auth_token_manager_1.getEffectiveManagementSecret)()}` };
        const response = await fetch(url, {
            signal: controller.signal,
            headers,
        });
        clearTimeout(timeoutId);
        if (!response.ok) {
            return null;
        }
        return await response.text();
    }
    catch {
        return null;
    }
}
exports.fetchCliproxyErrorLogContent = fetchCliproxyErrorLogContent;
/**
 * Check if CLIProxyAPI is running and responsive
 * @param port CLIProxyAPI port (default: 8317)
 * @returns true if proxy is running
 */
async function isCliproxyRunning(port) {
    try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 1000); // 1s timeout
        // Dynamic target resolution
        const target = (0, proxy_target_resolver_1.getProxyTarget)();
        // Allow port override for local testing only
        if (port !== undefined && !target.isRemote) {
            target.port = port;
        }
        const url = (0, proxy_target_resolver_1.buildProxyUrl)(target, '/');
        // Health check - no auth needed for root endpoint
        const response = await fetch(url, {
            signal: controller.signal,
        });
        clearTimeout(timeoutId);
        return response.ok;
    }
    catch {
        return false;
    }
}
exports.isCliproxyRunning = isCliproxyRunning;
//# sourceMappingURL=stats-fetcher.js.map