import { env } from "../../../env.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { createApiError, createBranch, createCoPilotRun, createConversation, generateId, getAppById, getCoPilotRunByBranchName, getFeedbackByIds, getInProcessFetch, getProjectMainResolvedRef, getProjectScopedRef, resolveRef, updateCoPilotRunStatusByConversationId } from "@inkeep/agents-core";
import { createSign } from "node:crypto";

//#region src/domains/run/services/ImprovementService.ts
const logger = getLogger$1("ImprovementService");
const COPILOT_JWT_AUDIENCE = "inkeep-copilot";
async function markRunFailedOnLaunchError(conversationId) {
	try {
		await updateCoPilotRunStatusByConversationId(runDbClient_default)({
			conversationId,
			status: "failed"
		});
	} catch (err) {
		logger.warn({
			err,
			conversationId
		}, "Failed to mark copilot run as failed on launch error");
	}
}
let copilotConfigPromise = null;
async function getCopilotConfig() {
	if (copilotConfigPromise) return copilotConfigPromise;
	copilotConfigPromise = (async () => {
		const appId = env.PUBLIC_INKEEP_COPILOT_APP_ID;
		if (!appId) throw createApiError({
			code: "internal_server_error",
			message: "PUBLIC_INKEEP_COPILOT_APP_ID is required"
		});
		const app = await getAppById(runDbClient_default)(appId);
		if (!app) throw createApiError({
			code: "internal_server_error",
			message: `Copilot app not found for id: ${appId}`
		});
		if (!app.tenantId || !app.defaultProjectId || !app.defaultAgentId) throw createApiError({
			code: "internal_server_error",
			message: `Copilot app "${appId}" is missing tenantId, defaultProjectId, or defaultAgentId`
		});
		return {
			tenantId: app.tenantId,
			projectId: app.defaultProjectId,
			agentId: app.defaultAgentId,
			appId
		};
	})().catch((err) => {
		copilotConfigPromise = null;
		throw err;
	});
	return copilotConfigPromise;
}
async function isCopilotScope(params) {
	try {
		const copilot = await getCopilotConfig();
		return copilot.tenantId === params.tenantId && copilot.projectId === params.projectId;
	} catch (err) {
		logger.warn({
			err,
			tenantId: params.tenantId,
			projectId: params.projectId
		}, "Failed to check copilot scope, defaulting to false");
		return false;
	}
}
function base64url(input) {
	return (typeof input === "string" ? Buffer.from(input) : input).toString("base64url");
}
function signCopilotJwt(userId) {
	const privateKeyB64 = env.INKEEP_COPILOT_JWT_PRIVATE_KEY;
	const kid = env.INKEEP_COPILOT_JWT_KID;
	if (!privateKeyB64 || !kid) throw createApiError({
		code: "internal_server_error",
		message: "INKEEP_COPILOT_JWT_PRIVATE_KEY and INKEEP_COPILOT_JWT_KID are required"
	});
	const privateKeyPem = Buffer.from(privateKeyB64, "base64").toString("utf-8");
	const now = Math.floor(Date.now() / 1e3);
	const exp = now + 3600;
	const signingInput = `${base64url(JSON.stringify({
		alg: "RS256",
		kid
	}))}.${base64url(JSON.stringify({
		sub: userId,
		aud: COPILOT_JWT_AUDIENCE,
		iat: now,
		exp,
		internal: true
	}))}`;
	const sign = createSign("RSA-SHA256");
	sign.update(signingInput);
	return `${signingInput}.${sign.sign(privateKeyPem, "base64url")}`;
}
async function triggerImprovement(params) {
	const { tenantId, projectId, agentId, feedbackIds, additionalContext, userId, forwardedCookie, db } = params;
	const branchName = `improvement_${agentId}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
	const conversationId = generateId();
	logger.info({
		tenantId,
		projectId,
		agentId,
		branchName
	}, "Triggering improvement run");
	await createBranch(db)({
		tenantId,
		projectId,
		name: branchName,
		fromBranch: "main"
	});
	logger.info({ branchName }, "Improvement branch created");
	const scopedRef = await resolveRef(db)(getProjectScopedRef(tenantId, projectId, branchName));
	if (!scopedRef) throw createApiError({
		code: "internal_server_error",
		message: `Failed to resolve newly created improvement branch: ${branchName}`
	});
	const resolvedBranchRef = {
		...scopedRef,
		name: branchName
	};
	const copilot = await getCopilotConfig();
	const copilotMainRef = await getProjectMainResolvedRef(db)(copilot.tenantId, copilot.projectId);
	await createConversation(runDbClient_default)({
		id: conversationId,
		tenantId: copilot.tenantId,
		projectId: copilot.projectId,
		activeSubAgentId: "orchestrator",
		ref: copilotMainRef
	});
	await createCoPilotRun(runDbClient_default)({
		tenantId,
		projectId,
		id: generateId(),
		ref: resolvedBranchRef,
		conversationId,
		feedbackIds,
		status: "running"
	});
	const userMessage = [
		`Improvement branch: "${branchName}" (already created from main).`,
		`Feedback IDs: ${feedbackIds.join(", ")}`,
		additionalContext ? `\nAdditional context from the builder:\n${additionalContext}` : ""
	].join("\n");
	const jwt = signCopilotJwt(userId);
	getInProcessFetch()("http://localhost/run/v1/chat/completions", {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${jwt}`,
			"x-inkeep-app-id": copilot.appId,
			...forwardedCookie && { "x-forwarded-cookie": forwardedCookie },
			"x-target-tenant-id": tenantId,
			"x-target-project-id": projectId,
			"x-target-agent-id": agentId,
			"x-target-branch-name": branchName,
			"x-emit-operations": "true"
		},
		body: JSON.stringify({
			model: `${copilot.projectId}/${copilot.agentId}`,
			messages: [{
				role: "user",
				content: userMessage
			}],
			stream: false,
			conversationId
		})
	}).then(async (res) => {
		if (!res.ok) {
			const text = await res.text().catch(() => "<unreadable>");
			logger.error({
				status: res.status,
				body: text.slice(0, 500),
				branchName,
				conversationId
			}, "Improvement chat API returned non-OK");
			await markRunFailedOnLaunchError(conversationId);
		}
	}).catch(async (err) => {
		logger.error({
			err,
			branchName,
			conversationId
		}, "Failed to fire improvement chat API call");
		await markRunFailedOnLaunchError(conversationId);
	});
	logger.info({
		branchName,
		conversationId
	}, "Improvement run triggered");
	return {
		branchName,
		conversationId
	};
}
async function continueImprovement(params) {
	const { tenantId, projectId, branchName, message, userId, forwardedCookie, db } = params;
	const existingRun = await getCoPilotRunByBranchName(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		branchName
	});
	if (!existingRun) throw createApiError({
		code: "not_found",
		message: `No copilot run found for branch: ${branchName}`
	});
	const feedbackIds = existingRun.feedbackIds ?? [];
	if (feedbackIds.length === 0) throw createApiError({
		code: "bad_request",
		message: `Copilot run for branch "${branchName}" has no feedback to derive agent from`
	});
	const agentId = (await getFeedbackByIds(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		feedbackIds
	})).find((f) => f.agentId)?.agentId;
	if (!agentId) throw createApiError({
		code: "bad_request",
		message: `Could not derive target agentId from feedback for branch: ${branchName}`
	});
	const conversationId = generateId();
	const copilot = await getCopilotConfig();
	const copilotMainRef = await getProjectMainResolvedRef(db)(copilot.tenantId, copilot.projectId);
	await createConversation(runDbClient_default)({
		id: conversationId,
		tenantId: copilot.tenantId,
		projectId: copilot.projectId,
		activeSubAgentId: "orchestrator",
		ref: copilotMainRef
	});
	await createCoPilotRun(runDbClient_default)({
		tenantId,
		projectId,
		id: generateId(),
		ref: existingRun.ref,
		conversationId,
		feedbackIds,
		status: "running"
	});
	const jwt = signCopilotJwt(userId);
	const userMessage = [`Continuing improvement on branch: "${branchName}".`, message].join("\n");
	getInProcessFetch()("http://localhost/run/v1/chat/completions", {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${jwt}`,
			"x-inkeep-app-id": copilot.appId,
			...forwardedCookie && { "x-forwarded-cookie": forwardedCookie },
			"x-target-tenant-id": tenantId,
			"x-target-project-id": projectId,
			"x-target-agent-id": agentId,
			"x-target-branch-name": branchName,
			"x-emit-operations": "true"
		},
		body: JSON.stringify({
			model: `${copilot.projectId}/${copilot.agentId}`,
			messages: [{
				role: "user",
				content: userMessage
			}],
			stream: false,
			conversationId
		})
	}).then(async (res) => {
		if (!res.ok) {
			const text = await res.text().catch(() => "<unreadable>");
			logger.error({
				status: res.status,
				body: text.slice(0, 500),
				branchName,
				conversationId
			}, "Continue improvement chat API returned non-OK");
			await markRunFailedOnLaunchError(conversationId);
		}
	}).catch(async (err) => {
		logger.error({
			err,
			branchName,
			conversationId
		}, "Failed to fire continue improvement call");
		await markRunFailedOnLaunchError(conversationId);
	});
	logger.info({
		branchName,
		conversationId
	}, "Improvement continuation triggered");
	return { conversationId };
}

//#endregion
export { continueImprovement, isCopilotScope, triggerImprovement };