import { env } from "../../../env.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { buildSpanLookupPayload, enforceSecurityFilters } from "../../../utils/signozHelpers.js";
import { SPAN_KEYS, buildFilterExpression, canViewProject, createApiError } from "@inkeep/agents-core";
import { Hono } from "hono";

//#region src/domains/manage/routes/signoz.ts
const logger = getLogger$1("signoz-proxy");
function getSignozConfig() {
	const url = env.SIGNOZ_URL;
	const apiKey = env.SIGNOZ_API_KEY;
	if (!url || !apiKey) return null;
	const headers$1 = { "Content-Type": "application/json" };
	if (apiKey.startsWith("eyJ")) headers$1.Authorization = `Bearer ${apiKey}`;
	else headers$1["SIGNOZ-API-KEY"] = apiKey;
	return {
		endpoint: `${url}/api/v5/query_range`,
		headers: headers$1,
		healthUrl: url
	};
}
async function authorizeProject(c, projectId) {
	const tenantId = c.get("tenantId");
	const userId = c.get("userId");
	const tenantRole = c.get("tenantRole");
	if (!userId || !tenantId) throw createApiError({
		code: "unauthorized",
		message: "User or organization context not found",
		instance: c.req.path
	});
	if (projectId) {
		if (!(userId === "system" || userId?.startsWith("apikey:"))) {
			if (!await canViewProject({
				userId,
				tenantId,
				projectId,
				orgRole: tenantRole
			})) {
				logger.warn({ userId }, "Project not found or access denied");
				return c.json({
					error: "Forbidden",
					message: "You do not have access to this project"
				}, 403);
			}
		}
	}
	return {
		tenantId,
		userId
	};
}
var FetchResponseError = class extends Error {
	status;
	data;
	constructor(status, data) {
		super(`Request failed with status ${status}`);
		this.name = "FetchResponseError";
		this.status = status;
		this.data = data;
	}
};
const MISSING_KEY_RE = /(?:key|field) `(.+?)` not found/i;
function extractMissingKeysFromBody(data) {
	const errorData = data;
	const errors = errorData?.error?.errors ?? [];
	if (errors.length > 0) {
		const keys = errors.map((e) => MISSING_KEY_RE.exec(e?.message)?.[1]).filter(Boolean);
		if (keys.length > 0 && keys.length === errors.length) return [...new Set(keys)];
		return [];
	}
	const flat = MISSING_KEY_RE.exec(errorData?.error?.message)?.[1];
	return flat ? [flat] : [];
}
function getMissingKeys(error) {
	if (!(error instanceof FetchResponseError)) return null;
	if (error.status !== 400 && error.status !== 500) return null;
	const keys = extractMissingKeysFromBody(error.data);
	if (keys.length === 0) return null;
	logger.warn({ missingKeys: keys }, "SigNoz attributes not yet ingested");
	return keys;
}
function queryReferencesKeys(query, keys) {
	const spec = query?.spec;
	const searchable = [
		spec?.filter?.expression ?? "",
		...(spec?.selectFields ?? []).map((f) => f.name),
		...(spec?.groupBy ?? []).map((g) => g.name)
	];
	return keys.some((k) => searchable.some((t) => t.includes(k)));
}
function stripSelectFields(payload, keys) {
	const queries = payload.compositeQuery?.queries ?? [];
	let strippedAny = false;
	const next = queries.map((q) => {
		const fields = q?.spec?.selectFields ?? [];
		const kept = fields.filter((f) => !keys.some((k) => f?.name === k));
		if (kept.length === fields.length) return q;
		strippedAny = true;
		return {
			...q,
			spec: {
				...q.spec,
				selectFields: kept
			}
		};
	});
	if (!strippedAny) return null;
	return {
		...payload,
		compositeQuery: {
			...payload.compositeQuery,
			queries: next
		}
	};
}
function withUntypedAttributeSelects(payload) {
	const untypedQueries = (payload.compositeQuery?.queries ?? []).map((q) => {
		const untyped = (q?.spec?.selectFields ?? []).map((f) => f?.fieldContext === "attribute" ? { name: f.name } : f);
		return {
			...q,
			spec: {
				...q.spec,
				selectFields: untyped
			}
		};
	});
	return {
		...payload,
		compositeQuery: {
			...payload.compositeQuery,
			queries: untypedQueries
		}
	};
}
async function discoverMissingKeysUntyped(signoz, payload, maxRetries, deadlineSignal) {
	const discovered = [];
	let probe = withUntypedAttributeSelects(payload);
	for (let i = 0; i < maxRetries; i++) try {
		await signozPost(signoz.endpoint, probe, signoz.headers, 3e4, deadlineSignal);
		return discovered;
	} catch (probeError) {
		if (!(probeError instanceof FetchResponseError)) return discovered;
		const keys = extractMissingKeysFromBody(probeError.data);
		if (keys.length === 0) return discovered;
		for (const k of keys) if (!discovered.includes(k)) discovered.push(k);
		const stripped = stripSelectFields(probe, keys);
		if (!stripped) return discovered;
		probe = stripped;
	}
	return discovered;
}
const EMPTY_RESPONSE = { data: {
	status: "success",
	data: { data: { results: [] } }
} };
async function signozPost(endpoint, body, headers$1, timeout, deadlineSignal) {
	const controller = new AbortController();
	const timeoutId = setTimeout(() => controller.abort(), timeout);
	const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
	try {
		const response = await fetch(endpoint, {
			method: "POST",
			headers: headers$1,
			body: JSON.stringify(body),
			signal
		});
		const rawBody = await response.text();
		let data;
		try {
			data = rawBody ? JSON.parse(rawBody) : null;
		} catch {
			throw new FetchResponseError(response.status, rawBody);
		}
		if (!response.ok) throw new FetchResponseError(response.status, data);
		return { data };
	} finally {
		clearTimeout(timeoutId);
	}
}
const MISSING_KEY_RETRY_FLOOR = 8;
const MISSING_KEY_RETRY_CEILING = 80;
const QUERY_WALL_CLOCK_DEADLINE_MS = 12e4;
function countSelectFields(payload) {
	return (payload?.compositeQuery?.queries ?? []).reduce((total, q) => total + (q?.spec?.selectFields?.length ?? 0), 0);
}
function effectiveRetryCap(payload) {
	const proportional = 2 * countSelectFields(payload);
	return Math.min(MISSING_KEY_RETRY_CEILING, Math.max(MISSING_KEY_RETRY_FLOOR, proportional));
}
async function queryWithRetry(signoz, payload) {
	let current = payload;
	let retried = false;
	const maxRetries = effectiveRetryCap(payload);
	const deadline = new AbortController();
	const deadlineId = setTimeout(() => deadline.abort(), QUERY_WALL_CLOCK_DEADLINE_MS);
	try {
		for (let attempt = 0; attempt <= maxRetries; attempt++) try {
			return {
				data: await signozPost(signoz.endpoint, current, signoz.headers, 3e4, deadline.signal),
				retried
			};
		} catch (error) {
			let missing = getMissingKeys(error);
			if (!missing && error instanceof FetchResponseError && error.status === 500) {
				const probed = await discoverMissingKeysUntyped(signoz, current, maxRetries, deadline.signal);
				if (probed.length > 0) missing = probed;
			}
			if (!missing) throw error;
			const fieldStripped = stripSelectFields(current, missing);
			if (fieldStripped) {
				logger.info({
					missingKeys: missing,
					strategy: "strip-select-fields"
				}, "Retrying SigNoz query without missing attribute selectFields");
				current = fieldStripped;
				retried = true;
				continue;
			}
			const queries = current.compositeQuery?.queries ?? [];
			const kept = queries.filter((q) => !queryReferencesKeys(q, missing));
			if (kept.length === queries.length) throw error;
			logger.info({
				removedCount: queries.length - kept.length,
				remaining: kept.length,
				missingKeys: missing,
				strategy: "drop-queries"
			}, "Retrying SigNoz query without queries referencing missing keys");
			if (kept.length === 0) return {
				data: EMPTY_RESPONSE,
				retried: true
			};
			current = {
				...current,
				compositeQuery: {
					...current.compositeQuery,
					queries: kept
				}
			};
			retried = true;
		}
		return {
			data: await signozPost(signoz.endpoint, current, signoz.headers, 3e4, deadline.signal),
			retried
		};
	} finally {
		clearTimeout(deadlineId);
	}
}
function handleSignozError(error, operation) {
	if (error instanceof FetchResponseError) {
		if (error.status === 401 || error.status === 403) {
			logger.error({ status: error.status }, "SigNoz authentication failed");
			return {
				body: {
					error: "Internal Server Error",
					message: "SigNoz authentication failed"
				},
				status: 500
			};
		}
		if (error.status === 400) {
			logger.warn({
				status: 400,
				responseData: error.data
			}, `Invalid SigNoz ${operation}`);
			return {
				body: {
					error: "Bad Request",
					message: error.data?.error ?? "Invalid query parameters"
				},
				status: 400
			};
		}
	}
	if (error instanceof TypeError || error instanceof DOMException && error.name === "AbortError") {
		logger.error({ error: error instanceof Error ? error.message : error }, "SigNoz service unavailable");
		return {
			body: {
				error: "Service Unavailable",
				message: "SigNoz service is unavailable"
			},
			status: 503
		};
	}
	logger.error({
		error,
		responseData: error instanceof FetchResponseError ? error.data : void 0
	}, `SigNoz ${operation} failed`);
	return {
		body: {
			error: "Internal Server Error",
			message: "Failed to query SigNoz"
		},
		status: 500
	};
}
function extractResults(fetchResponse) {
	return fetchResponse.data?.data?.data?.results ?? [];
}
const app = new Hono();
app.post("/query", async (c) => {
	const payload = await c.req.json();
	const requestedProjectId = payload.projectId;
	delete payload.projectId;
	const auth = await authorizeProject(c, requestedProjectId);
	if (auth instanceof Response) return auth;
	const signoz = getSignozConfig();
	if (!signoz) return c.json({
		error: "Service Unavailable",
		message: "SigNoz is not configured"
	}, 500);
	try {
		enforceSecurityFilters(payload, auth.tenantId, requestedProjectId);
	} catch (error) {
		return c.json({
			error: "Bad Request",
			message: error instanceof Error ? error.message : "Invalid query"
		}, 400);
	}
	try {
		const { data: response } = await queryWithRetry(signoz, payload);
		return c.json(response.data);
	} catch (error) {
		const { body, status } = handleSignozError(error, "query");
		return c.json(body, status);
	}
});
app.post("/query-batch", async (c) => {
	const { paginationPayload, detailPayloadTemplate } = await c.req.json();
	if (!paginationPayload || !detailPayloadTemplate) return c.json({
		error: "Bad Request",
		message: "paginationPayload and detailPayloadTemplate are required"
	}, 400);
	const requestedProjectId = paginationPayload.projectId;
	delete paginationPayload.projectId;
	delete detailPayloadTemplate.projectId;
	const auth = await authorizeProject(c, requestedProjectId);
	if (auth instanceof Response) return auth;
	const signoz = getSignozConfig();
	if (!signoz) return c.json({
		error: "Service Unavailable",
		message: "SigNoz is not configured"
	}, 500);
	try {
		enforceSecurityFilters(paginationPayload, auth.tenantId, requestedProjectId);
		enforceSecurityFilters(detailPayloadTemplate, auth.tenantId, requestedProjectId);
	} catch (error) {
		return c.json({
			error: "Bad Request",
			message: error instanceof Error ? error.message : "Invalid query"
		}, 400);
	}
	try {
		const { data: step1Data } = await queryWithRetry(signoz, paginationPayload);
		const pageResult = extractResults(step1Data).find((r) => r?.queryName === "pageConversations");
		const columns = pageResult?.columns ?? [];
		const rows = pageResult?.data ?? [];
		const convIdColIdx = columns.findIndex((col) => col.name === SPAN_KEYS.CONVERSATION_ID);
		const tsColIdx = columns.findIndex((col) => col.columnType === "aggregation");
		const conversationIds = convIdColIdx >= 0 ? rows.map((row) => row[convIdColIdx]).filter(Boolean) : [];
		if (conversationIds.length === 0) return c.json({
			paginationResponse: step1Data.data,
			detailResponse: null
		});
		if (tsColIdx >= 0 && rows.length > 0) {
			const BUFFER_MS = 36e5;
			let minTs = Number.POSITIVE_INFINITY;
			let maxTs = 0;
			for (const row of rows) {
				const ts = new Date(String(row[tsColIdx])).getTime();
				if (ts > 0 && ts < minTs) minTs = ts;
				if (ts > maxTs) maxTs = ts;
			}
			if (minTs < Number.POSITIVE_INFINITY && maxTs > 0) {
				detailPayloadTemplate.start = minTs - BUFFER_MS;
				detailPayloadTemplate.end = maxTs + BUFFER_MS;
			}
		}
		const convIdExpr = buildFilterExpression([{
			key: SPAN_KEYS.CONVERSATION_ID,
			op: "in",
			value: conversationIds
		}]);
		for (const { spec } of detailPayloadTemplate.compositeQuery.queries) spec.filter = { expression: `(${spec.filter.expression}) AND ${convIdExpr}` };
		const { data: step2Data } = await queryWithRetry(signoz, detailPayloadTemplate);
		return c.json({
			paginationResponse: step1Data.data,
			detailResponse: step2Data.data
		});
	} catch (error) {
		const { body, status } = handleSignozError(error, "query-batch");
		return c.json(body, status);
	}
});
app.post("/span-lookup", async (c) => {
	const { conversationId, spanId } = await c.req.json();
	if (!conversationId || !spanId) return c.json({
		error: "Bad Request",
		message: "conversationId and spanId are required"
	}, 400);
	const auth = await authorizeProject(c, void 0);
	if (auth instanceof Response) return auth;
	const signoz = getSignozConfig();
	if (!signoz) return c.json({
		error: "Service Unavailable",
		message: "SigNoz is not configured"
	}, 500);
	const now = Date.now();
	const payload = buildSpanLookupPayload(auth.tenantId, conversationId, spanId, now - 4320 * 60 * 60 * 1e3, now);
	try {
		const resp = await signozPost(signoz.endpoint, payload, signoz.headers, 15e3);
		return c.json(resp.data);
	} catch (error) {
		const { body: errBody, status } = handleSignozError(error, "span-lookup");
		return c.json(errBody, status);
	}
});
app.get("/health", async (c) => {
	const signoz = getSignozConfig();
	if (!signoz) {
		logger.warn("SigNoz credentials not set");
		return c.json({
			status: "not_configured",
			configured: false,
			error: "SIGNOZ_URL or SIGNOZ_API_KEY not set"
		});
	}
	try {
		const controller = new AbortController();
		const timeoutId = setTimeout(() => controller.abort(), 5e3);
		const response = await fetch(signoz.endpoint, {
			method: "POST",
			headers: signoz.headers,
			body: JSON.stringify({
				start: Date.now() - 3e5,
				end: Date.now(),
				requestType: "scalar",
				compositeQuery: { queries: [] }
			}),
			signal: controller.signal
		});
		clearTimeout(timeoutId);
		if (response.status === 200 || response.status === 400) return c.json({
			status: "ok",
			configured: true
		});
		if (response.status === 401 || response.status === 403) {
			logger.error({ error: "Invalid API key" }, "SigNoz connection test failed");
			return c.json({
				status: "connection_failed",
				configured: false,
				error: "Invalid SIGNOZ_API_KEY"
			});
		}
		logger.error({ error: `Unexpected status ${response.status}` }, "SigNoz connection test failed");
		return c.json({
			status: "connection_failed",
			configured: false,
			error: "Failed to connect to SigNoz"
		});
	} catch (error) {
		let errorMessage = "Failed to connect to SigNoz";
		if (error instanceof DOMException && error.name === "AbortError") errorMessage = "SigNoz connection timed out";
		else if (error instanceof TypeError) errorMessage = "Check SIGNOZ_URL";
		logger.error({ error: error instanceof Error ? error.message : error }, "SigNoz connection test failed");
		return c.json({
			status: "connection_failed",
			configured: false,
			error: errorMessage
		});
	}
});
var signoz_default = app;

//#endregion
export { FetchResponseError, signoz_default as default, effectiveRetryCap, getMissingKeys, queryReferencesKeys, queryWithRetry, stripSelectFields };