import { getLogger as getLogger$1 } from "../../../logger.js";
import { env } from "../../../env.js";
import manageDbPool_default from "../../../data/db/manageDbPool.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { flushBatchProcessor } from "../../../instrumentation.js";
import { tracer } from "../utils/tracer.js";
import { createSSEStreamHelper } from "../utils/stream-helpers.js";
import { ExecutionHandler } from "../handlers/executionHandler.js";
import { DEFAULT_NANGO_STORE_ID, JsonTransformer, createKeyChainStore, createMessage, createNangoCredentialStore, createOrGetConversation, createTriggerInvocation, generateId, getConversationId, getCredentialReference, getCredentialStoreLookupKeyFromRetrievalParams, getFullProjectWithRelationIds, getTriggerById, interpolateTemplate, setActiveAgentForConversation, updateTriggerInvocationStatus, verifySignatureWithConfig, verifyTriggerAuth, withRef } from "@inkeep/agents-core";
import { SpanStatusCode, context, propagation } from "@opentelemetry/api";
import Ajv from "ajv";

//#region src/domains/run/services/TriggerService.ts
let _waitUntil;
let _waitUntilResolved = false;
async function getWaitUntil() {
	if (_waitUntilResolved) return _waitUntil;
	_waitUntilResolved = true;
	if (!process.env.VERCEL) return void 0;
	try {
		_waitUntil = (await import("@vercel/functions")).waitUntil;
	} catch (e) {
		console.error("[TriggerService] Failed to import @vercel/functions:", e);
	}
	return _waitUntil;
}
const logger = getLogger$1("TriggerService");
const ajv = new Ajv({ allErrors: true });
const credentialCache = /* @__PURE__ */ new Map();
const CACHE_TTL_MS = 300 * 1e3;
/**
* Process a trigger webhook request.
* Handles validation, transformation, and dispatches async execution.
*/
async function processWebhook(params) {
	const { tenantId, projectId, agentId, triggerId, resolvedRef, rawBody, honoContext } = params;
	const trigger = await loadTrigger({
		tenantId,
		projectId,
		agentId,
		triggerId,
		resolvedRef
	});
	if (!trigger) return {
		success: false,
		error: `Trigger ${triggerId} not found`,
		status: 404
	};
	if (!trigger.enabled) return {
		success: false,
		error: "Trigger is disabled",
		status: 404
	};
	const payload = rawBody ? JSON.parse(rawBody) : {};
	const authResult = await verifyAuthentication(trigger, honoContext);
	if (!authResult.success) return authResult;
	const signatureResult = await verifySignature({
		trigger,
		tenantId,
		projectId,
		resolvedRef,
		honoContext,
		rawBody
	});
	if (!signatureResult.success) return signatureResult;
	const validationResult = validatePayload(trigger, payload);
	if (!validationResult.success) return validationResult;
	const transformResult = await transformPayload(trigger, payload, {
		tenantId,
		projectId,
		triggerId
	});
	if (!transformResult.success) return transformResult;
	const transformedPayload = transformResult.payload;
	const { messageParts, userMessageText } = buildMessage(trigger, transformedPayload, triggerId);
	const { invocationId, conversationId } = await dispatchExecution({
		tenantId,
		projectId,
		agentId,
		triggerId,
		resolvedRef,
		payload,
		transformedPayload,
		messageParts,
		userMessageText
	});
	return {
		success: true,
		invocationId,
		conversationId
	};
}
async function loadTrigger(params) {
	const { tenantId, projectId, agentId, triggerId, resolvedRef } = params;
	return await withRef(manageDbPool_default, resolvedRef, (db) => getTriggerById(db)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		triggerId
	}));
}
async function verifyAuthentication(trigger, honoContext) {
	if (!trigger.authentication) return { success: true };
	const authResult = await verifyTriggerAuth(honoContext, trigger.authentication);
	if (!authResult.success) {
		if (authResult.status === 401) return {
			success: false,
			error: authResult.message || "Unauthorized",
			status: 401
		};
		return {
			success: false,
			error: authResult.message || "Forbidden",
			status: 403
		};
	}
	return { success: true };
}
/**
* Resolve signing secret from credential reference with caching
*/
async function resolveSigningSecret(params) {
	const { tenantId, projectId, credentialReferenceId, resolvedRef } = params;
	const cacheKey = `${tenantId}:${projectId}:${credentialReferenceId}`;
	const cached = credentialCache.get(cacheKey);
	if (cached && cached.expiresAt > Date.now()) return cached.secret;
	const credentialRef = await withRef(manageDbPool_default, resolvedRef, (db) => getCredentialReference(db)({
		scopes: {
			tenantId,
			projectId
		},
		id: credentialReferenceId
	}));
	if (!credentialRef) {
		logger.warn({
			tenantId,
			projectId,
			credentialReferenceId
		}, "Credential reference not found");
		return null;
	}
	const lookupKey = getCredentialStoreLookupKeyFromRetrievalParams({
		retrievalParams: credentialRef.retrievalParams ?? {},
		credentialStoreType: credentialRef.type
	});
	if (!lookupKey) {
		logger.warn({
			tenantId,
			projectId,
			credentialReferenceId,
			retrievalParams: credentialRef.retrievalParams
		}, "Could not determine lookup key from credential reference");
		return null;
	}
	let secret = null;
	if (credentialRef.type === "keychain" || credentialRef.credentialStoreId?.startsWith("keychain")) secret = await createKeyChainStore(credentialRef.credentialStoreId ?? "keychain-default").get(lookupKey);
	else if (credentialRef.type === "nango" || credentialRef.credentialStoreId?.startsWith("nango")) {
		const nangoSecretKey = process.env.NANGO_SECRET_KEY;
		if (!nangoSecretKey) {
			logger.warn({
				tenantId,
				projectId,
				credentialReferenceId
			}, "NANGO_SECRET_KEY not configured, cannot resolve Nango credential");
			return null;
		}
		try {
			secret = await createNangoCredentialStore(credentialRef.credentialStoreId ?? DEFAULT_NANGO_STORE_ID, {
				secretKey: nangoSecretKey,
				apiUrl: process.env.NANGO_SERVER_URL || "https://api.nango.dev"
			}).get(lookupKey);
		} catch (error) {
			logger.error({
				tenantId,
				projectId,
				credentialReferenceId,
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Failed to create or fetch from Nango credential store");
			return null;
		}
	} else {
		logger.warn({
			credentialStoreType: credentialRef.type,
			credentialStoreId: credentialRef.credentialStoreId
		}, "Unsupported credential store type for signing secret");
		return null;
	}
	if (!secret) {
		logger.warn({
			tenantId,
			projectId,
			credentialReferenceId,
			lookupKey
		}, "No secret found in credential store");
		return null;
	}
	if (secret.startsWith("{")) try {
		const parsed = JSON.parse(secret);
		const extractedSecret = parsed.access_token || parsed.secret || parsed.value || parsed.token || parsed.key;
		if (extractedSecret && typeof extractedSecret === "string") secret = extractedSecret;
	} catch {}
	credentialCache.set(cacheKey, {
		secret,
		expiresAt: Date.now() + CACHE_TTL_MS
	});
	return secret;
}
async function verifySignature(params) {
	const { trigger, tenantId, projectId, resolvedRef, honoContext, rawBody } = params;
	if (!trigger.signatureVerification || !trigger.signingSecretCredentialReferenceId) return { success: true };
	try {
		const secret = await resolveSigningSecret({
			tenantId,
			projectId,
			credentialReferenceId: trigger.signingSecretCredentialReferenceId,
			resolvedRef
		});
		if (!secret) return {
			success: false,
			error: "Failed to resolve signing secret from credential reference",
			status: 500
		};
		const result = verifySignatureWithConfig(honoContext, trigger.signatureVerification, secret, rawBody);
		if (!result.success) return {
			success: false,
			error: result.message || "Invalid signature",
			status: 403
		};
		return { success: true };
	} catch (error) {
		const errorMessage = error instanceof Error ? error.message : String(error);
		logger.error({
			error: errorMessage,
			tenantId,
			projectId
		}, "Error during signature verification");
		return {
			success: false,
			error: "Signature verification failed",
			status: 500
		};
	}
}
function validatePayload(trigger, payload) {
	if (!trigger.inputSchema) return { success: true };
	const validate = ajv.compile(trigger.inputSchema);
	if (!validate(payload)) return {
		success: false,
		error: "Payload validation failed",
		status: 400,
		validationErrors: validate.errors?.map((err) => `${err.instancePath} ${err.message}`)
	};
	return { success: true };
}
async function transformPayload(trigger, payload, context$1) {
	if (!trigger.outputTransform) return {
		success: true,
		payload
	};
	try {
		const transformedPayload = await JsonTransformer.transformWithConfig(payload, trigger.outputTransform);
		logger.debug(context$1, "Payload transformation successful");
		return {
			success: true,
			payload: transformedPayload
		};
	} catch (error) {
		const errorMessage = error instanceof Error ? error.message : String(error);
		logger.error({
			...context$1,
			error: errorMessage
		}, "Payload transformation failed");
		return {
			success: false,
			error: `Payload transformation failed: ${errorMessage}`,
			status: 422
		};
	}
}
function buildMessage(trigger, transformedPayload, triggerId) {
	const messageParts = [];
	if (trigger.messageTemplate) {
		const payloadForTemplate = typeof transformedPayload === "object" && transformedPayload !== null && !Array.isArray(transformedPayload) ? transformedPayload : {};
		const interpolatedMessage = interpolateTemplate(trigger.messageTemplate, payloadForTemplate);
		messageParts.push({
			kind: "text",
			text: interpolatedMessage
		});
	}
	if (transformedPayload != null) messageParts.push({
		kind: "data",
		data: transformedPayload,
		metadata: {
			source: "trigger",
			triggerId
		}
	});
	return {
		messageParts,
		userMessageText: trigger.messageTemplate ? (() => {
			const payloadForTemplate = typeof transformedPayload === "object" && transformedPayload !== null && !Array.isArray(transformedPayload) ? transformedPayload : {};
			return interpolateTemplate(trigger.messageTemplate, payloadForTemplate);
		})() : JSON.stringify(transformedPayload)
	};
}
async function dispatchExecution(params) {
	const { tenantId, projectId, agentId, triggerId, resolvedRef, payload, transformedPayload, messageParts, userMessageText } = params;
	const conversationId = getConversationId();
	const invocationId = generateId();
	await createTriggerInvocation(runDbClient_default)({
		id: invocationId,
		triggerId,
		tenantId,
		projectId,
		agentId,
		conversationId,
		status: "pending",
		requestPayload: payload,
		transformedPayload
	});
	logger.info({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId,
		conversationId
	}, "Trigger invocation created");
	const safeExecutionPromise = executeAgentAsync({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId,
		conversationId,
		userMessage: userMessageText,
		messageParts,
		resolvedRef
	}).catch(async (error) => {
		const errorMessage = error instanceof Error ? error.message : String(error);
		const errorStack = error instanceof Error ? error.stack : void 0;
		logger.error({
			err: errorMessage,
			errorStack,
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId
		}, "Background trigger execution failed");
		try {
			await updateTriggerInvocationStatus(runDbClient_default)({
				scopes: {
					tenantId,
					projectId,
					agentId
				},
				triggerId,
				invocationId,
				data: {
					status: "failed",
					errorMessage
				}
			});
		} catch (updateError) {
			const updateErrorMessage = updateError instanceof Error ? updateError.message : String(updateError);
			logger.error({
				err: updateErrorMessage,
				invocationId
			}, "Failed to update invocation status to failed");
		}
	});
	const waitUntil = await getWaitUntil();
	if (waitUntil) {
		logger.info({
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId
		}, "Calling waitUntil with execution promise");
		waitUntil(safeExecutionPromise);
	} else logger.warn({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId
	}, "waitUntil is NOT available — background execution will be abandoned on serverless");
	logger.info({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId,
		conversationId
	}, "Async execution dispatched");
	return {
		invocationId,
		conversationId
	};
}
/**
* Execute the agent asynchronously.
* This runs after the webhook response is sent.
*/
async function executeAgentAsync(params) {
	const { tenantId, projectId, agentId, triggerId, invocationId, conversationId, userMessage, messageParts, resolvedRef } = params;
	logger.info({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId
	}, "executeAgentAsync: started, loading project");
	const project = await withRef(manageDbPool_default, resolvedRef, async (db) => {
		return await getFullProjectWithRelationIds(db)({ scopes: {
			tenantId,
			projectId
		} });
	});
	logger.info({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId,
		hasProject: !!project
	}, "executeAgentAsync: project loaded");
	if (!project) {
		logger.error({
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId
		}, "Project not found for trigger execution");
		throw new Error(`Project ${projectId} not found`);
	}
	const agent = project.agents?.[agentId];
	if (!agent) {
		logger.error({
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId
		}, "Agent not found in project for trigger execution");
		throw new Error(`Agent ${agentId} not found in project`);
	}
	const defaultSubAgentId = agent.defaultSubAgentId;
	if (!defaultSubAgentId) {
		logger.error({
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId
		}, "Agent has no default sub-agent configured");
		throw new Error(`Agent ${agentId} has no default sub-agent configured`);
	}
	const agentName = agent.name;
	const baggage = propagation.createBaggage().setEntry("conversation.id", { value: conversationId }).setEntry("tenant.id", { value: tenantId }).setEntry("project.id", { value: projectId }).setEntry("agent.id", { value: agentId }).setEntry("agent.name", { value: agentName });
	const ctxWithBaggage = propagation.setBaggage(context.active(), baggage);
	logger.info({
		tenantId,
		projectId,
		agentId,
		triggerId,
		invocationId
	}, "executeAgentAsync: starting tracer span");
	return tracer.startActiveSpan("trigger.execute_async", {
		root: true,
		attributes: {
			"tenant.id": tenantId,
			"project.id": projectId,
			"agent.id": agentId,
			"agent.name": agentName,
			"trigger.id": triggerId,
			"trigger.invocation.id": invocationId,
			"conversation.id": conversationId,
			"invocation.type": "trigger"
		}
	}, ctxWithBaggage, async (span) => {
		tracer.startSpan("trigger.message_received", { attributes: {
			"tenant.id": tenantId,
			"project.id": projectId,
			"agent.id": agentId,
			"agent.name": agentName,
			"trigger.id": triggerId,
			"trigger.invocation.id": invocationId,
			"conversation.id": conversationId,
			"invocation.type": "trigger",
			"message.content": userMessage,
			"message.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
			"message.parts": JSON.stringify(messageParts)
		} }, context.active()).end();
		await flushBatchProcessor();
		logger.info({
			tenantId,
			projectId,
			agentId,
			triggerId,
			invocationId,
			conversationId
		}, "Starting async trigger execution");
		try {
			await createOrGetConversation(runDbClient_default)({
				id: conversationId,
				tenantId,
				projectId,
				agentId,
				activeSubAgentId: defaultSubAgentId,
				ref: resolvedRef
			});
			await setActiveAgentForConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId,
				subAgentId: defaultSubAgentId,
				agentId,
				ref: resolvedRef
			});
			await createMessage(runDbClient_default)({
				id: generateId(),
				tenantId,
				projectId,
				conversationId,
				role: "user",
				content: {
					text: userMessage,
					parts: messageParts
				},
				metadata: { a2a_metadata: {
					triggerId,
					invocationId
				} }
			});
			const executionContext = {
				tenantId,
				projectId,
				agentId,
				baseUrl: env.INKEEP_AGENTS_API_URL || "http://localhost:3002",
				apiKey: env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET || "",
				apiKeyId: "trigger-invocation",
				resolvedRef,
				project,
				metadata: { initiatedBy: {
					type: "api_key",
					id: triggerId
				} }
			};
			const requestId = `trigger-${invocationId}`;
			const noOpStreamHelper = createSSEStreamHelper({
				writeSSE: async () => {},
				sleep: async () => {}
			}, requestId, Math.floor(Date.now() / 1e3));
			await new ExecutionHandler().execute({
				executionContext,
				conversationId,
				userMessage,
				messageParts,
				initialAgentId: agentId,
				requestId,
				sseHelper: noOpStreamHelper,
				emitOperations: false
			});
			await updateTriggerInvocationStatus(runDbClient_default)({
				scopes: {
					tenantId,
					projectId,
					agentId
				},
				triggerId,
				invocationId,
				data: { status: "success" }
			});
			span.setStatus({ code: SpanStatusCode.OK });
			logger.info({
				tenantId,
				projectId,
				agentId,
				triggerId,
				invocationId,
				conversationId
			}, "Async trigger execution completed successfully");
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			const errorStack = error instanceof Error ? error.stack : void 0;
			span.setStatus({
				code: SpanStatusCode.ERROR,
				message: errorMessage
			});
			span.recordException(error instanceof Error ? error : new Error(errorMessage));
			logger.error({
				err: errorMessage,
				errorStack,
				tenantId,
				projectId,
				agentId,
				triggerId,
				invocationId
			}, "Async trigger execution failed");
			try {
				await updateTriggerInvocationStatus(runDbClient_default)({
					scopes: {
						tenantId,
						projectId,
						agentId
					},
					triggerId,
					invocationId,
					data: {
						status: "failed",
						errorMessage
					}
				});
			} catch (updateError) {
				const updateErrorMessage = updateError instanceof Error ? updateError.message : String(updateError);
				logger.error({
					err: updateErrorMessage,
					invocationId
				}, "Failed to update invocation status to failed");
			}
			throw error;
		} finally {
			span.end();
			await flushBatchProcessor();
		}
	});
}

//#endregion
export { dispatchExecution, processWebhook };