import { env } from "../../../env.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { buildSpanLookupPayload, enforceSecurityFilters } from "../../../utils/signozHelpers.js";
import { SPAN_KEYS, 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;
	return {
		endpoint: `${url}/api/v5/query_range`,
		headers: {
			"Content-Type": "application/json",
			"SIGNOZ-API-KEY": apiKey
		},
		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({
					tenantId,
					projectId,
					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 KEY_NOT_FOUND_RE = /key `(.+)` not found/i;
function getMissingKeys(error) {
	if (!(error instanceof FetchResponseError) || error.status !== 400) return null;
	const errors = error.data?.error?.errors ?? [];
	const keys = errors.map((e) => KEY_NOT_FOUND_RE.exec(e?.message)?.[1]).filter(Boolean);
	if (keys.length === 0 || keys.length !== errors.length) return null;
	const unique = [...new Set(keys)];
	logger.warn({ missingKeys: unique }, "SigNoz attributes not yet ingested");
	return unique;
}
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)));
}
const EMPTY_RESPONSE = { data: {
	status: "success",
	data: { data: { results: [] } }
} };
async function signozPost(endpoint, body, headers$1, timeout) {
	const controller = new AbortController();
	const timeoutId = setTimeout(() => controller.abort(), timeout);
	try {
		const response = await fetch(endpoint, {
			method: "POST",
			headers: headers$1,
			body: JSON.stringify(body),
			signal: controller.signal
		});
		const data = await response.json();
		if (!response.ok) throw new FetchResponseError(response.status, data);
		return { data };
	} finally {
		clearTimeout(timeoutId);
	}
}
async function queryWithRetry(signoz, payload) {
	try {
		return {
			data: await signozPost(signoz.endpoint, payload, signoz.headers, 3e4),
			retried: false
		};
	} catch (error) {
		const missing = getMissingKeys(error);
		if (!missing) throw error;
		const queries = payload.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
		}, "Retrying SigNoz query without queries referencing missing keys");
		if (kept.length === 0) return {
			data: EMPTY_RESPONSE,
			retried: true
		};
		const stripped = {
			...payload,
			compositeQuery: {
				...payload.compositeQuery,
				queries: kept
			}
		};
		return {
			data: await signozPost(signoz.endpoint, stripped, signoz.headers, 3e4),
			retried: true
		};
	}
}
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 = `${SPAN_KEYS.CONVERSATION_ID} IN (${conversationIds.map((id) => `'${id}'`).join(", ")})`;
		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 { signoz_default as default };