import { getLogger as getLogger$1 } from "../logger.js";
import { env } from "../env.js";
import runDbClient_default from "../data/db/runDbClient.js";
import { createBaseExecutionContext } from "../types/runExecutionContext.js";
import { canUseProjectStrict, validateAndGetApiKey, validateTargetAgent, verifyServiceToken, verifyTempToken } from "@inkeep/agents-core";
import { createMiddleware } from "hono/factory";
import { HTTPException } from "hono/http-exception";

//#region src/middleware/runAuth.ts
const logger = getLogger$1("env-key-auth");
/**
* Extract common request data from the Hono context
*/
function extractRequestData(c) {
	const authHeader = c.req.header("Authorization");
	const tenantId = c.req.header("x-inkeep-tenant-id");
	const projectId = c.req.header("x-inkeep-project-id");
	const agentId = c.req.header("x-inkeep-agent-id");
	const subAgentId = c.req.header("x-inkeep-sub-agent-id");
	const proto = c.req.header("x-forwarded-proto")?.split(",")[0].trim();
	const host = c.req.header("x-forwarded-host")?.split(",")[0].trim() ?? c.req.header("host");
	const reqUrl = new URL(c.req.url);
	const ref = c.req.query("ref");
	const baseUrl = proto && host ? `${proto}://${host}` : host ? `${reqUrl.protocol}//${host}` : `${reqUrl.origin}`;
	return {
		authHeader,
		apiKey: authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : void 0,
		tenantId,
		projectId,
		agentId,
		subAgentId,
		ref,
		baseUrl
	};
}
/**
* Build the final execution context from auth result and request data
*/
function buildExecutionContext(authResult, reqData) {
	return createBaseExecutionContext({
		apiKey: authResult.apiKey,
		tenantId: authResult.tenantId,
		projectId: authResult.projectId,
		agentId: authResult.agentId,
		apiKeyId: authResult.apiKeyId,
		baseUrl: reqData.baseUrl,
		subAgentId: reqData.subAgentId,
		ref: reqData.ref,
		metadata: authResult.metadata
	});
}
/**
* Attempts to authenticate using a JWT temporary token
*
* Throws HTTPException(403) if the JWT is valid but the user lacks permission.
* Returns null if the token is not a temp JWT (allowing fallback to other auth methods).
*/
async function tryTempJwtAuth(apiKey) {
	if (!apiKey.startsWith("eyJ") || !env.INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY) return null;
	try {
		const payload = await verifyTempToken(Buffer.from(env.INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY, "base64").toString("utf-8"), apiKey);
		const userId = payload.sub;
		const projectId = payload.projectId;
		const agentId = payload.agentId;
		if (!projectId || !agentId) {
			logger.warn({ userId }, "Missing projectId or agentId in JWT");
			throw new HTTPException(400, { message: "Invalid token: missing projectId or agentId" });
		}
		let canUse;
		try {
			canUse = await canUseProjectStrict({
				userId,
				projectId
			});
		} catch (error) {
			logger.error({
				error,
				userId,
				projectId
			}, "SpiceDB permission check failed");
			throw new HTTPException(503, { message: "Authorization service temporarily unavailable" });
		}
		if (!canUse) {
			logger.warn({
				userId,
				projectId
			}, "User does not have use permission on project");
			throw new HTTPException(403, { message: "Access denied: insufficient permissions" });
		}
		logger.info({
			projectId,
			agentId
		}, "JWT temp token authenticated successfully");
		return {
			apiKey,
			tenantId: payload.tenantId,
			projectId,
			agentId,
			apiKeyId: "temp-jwt",
			metadata: { initiatedBy: payload.initiatedBy }
		};
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		logger.debug({ error }, "JWT verification failed");
		return null;
	}
}
/**
* Authenticate using a regular API key
*/
async function tryApiKeyAuth(apiKey) {
	const apiKeyRecord = await validateAndGetApiKey(apiKey, runDbClient_default);
	if (!apiKeyRecord) return null;
	logger.debug({
		tenantId: apiKeyRecord.tenantId,
		projectId: apiKeyRecord.projectId,
		agentId: apiKeyRecord.agentId
	}, "API key authenticated successfully");
	return {
		apiKey,
		tenantId: apiKeyRecord.tenantId,
		projectId: apiKeyRecord.projectId,
		agentId: apiKeyRecord.agentId,
		apiKeyId: apiKeyRecord.id
	};
}
/**
* Authenticate using a team agent JWT token (for intra-tenant delegation)
*/
async function tryTeamAgentAuth(token, expectedSubAgentId) {
	const result = await verifyServiceToken(token);
	if (!result.valid || !result.payload) {
		logger.warn({ error: result.error }, "Invalid team agent JWT token");
		return {
			authResult: null,
			failureMessage: `Invalid team agent token: ${result.error || "Invalid token"}`
		};
	}
	const payload = result.payload;
	if (expectedSubAgentId && !validateTargetAgent(payload, expectedSubAgentId)) {
		logger.error({
			tokenTargetAgentId: payload.aud,
			expectedSubAgentId,
			originAgentId: payload.sub
		}, "Team agent token target mismatch");
		throw new HTTPException(403, { message: "Token not valid for the requested agent" });
	}
	logger.info({
		originAgentId: payload.sub,
		targetAgentId: payload.aud,
		tenantId: payload.tenantId,
		projectId: payload.projectId
	}, "Team agent JWT token authenticated successfully");
	return { authResult: {
		apiKey: "team-agent-jwt",
		tenantId: payload.tenantId,
		projectId: payload.projectId,
		agentId: payload.aud,
		apiKeyId: "team-agent-token",
		metadata: {
			teamDelegation: true,
			originAgentId: payload.sub
		}
	} };
}
/**
* Authenticate using bypass secret (production mode bypass)
*/
function tryBypassAuth(apiKey, reqData) {
	if (!env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET) return null;
	if (apiKey !== env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET) return null;
	if (!reqData.tenantId || !reqData.projectId || !reqData.agentId) throw new HTTPException(401, { message: "Missing or invalid tenant, project, or agent ID" });
	logger.info({}, "Bypass secret authenticated successfully");
	return {
		apiKey,
		tenantId: reqData.tenantId,
		projectId: reqData.projectId,
		agentId: reqData.agentId,
		apiKeyId: "bypass"
	};
}
/**
* Create default development context
*/
function createDevContext(reqData) {
	const result = {
		apiKey: "development",
		tenantId: reqData.tenantId || "test-tenant",
		projectId: reqData.projectId || "test-project",
		agentId: reqData.agentId || "test-agent",
		apiKeyId: "test-key"
	};
	if (!reqData.tenantId || !reqData.projectId) logger.warn({
		hasTenantId: !!reqData.tenantId,
		hasProjectId: !!reqData.projectId,
		hasApiKey: !!reqData.apiKey,
		apiKeyPrefix: reqData.apiKey?.substring(0, 10),
		resultTenantId: result.tenantId,
		resultProjectId: result.projectId
	}, "createDevContext: Using fallback test values due to missing tenant/project in request");
	return result;
}
/**
* Try all auth strategies in order, returning the first successful result
*/
async function authenticateRequest(reqData) {
	const { apiKey, subAgentId } = reqData;
	if (!apiKey) return { authResult: null };
	const jwtResult = await tryTempJwtAuth(apiKey);
	if (jwtResult) return { authResult: jwtResult };
	const bypassResult = tryBypassAuth(apiKey, reqData);
	if (bypassResult) return { authResult: bypassResult };
	const apiKeyResult = await tryApiKeyAuth(apiKey);
	if (apiKeyResult) return { authResult: apiKeyResult };
	const teamAttempt = await tryTeamAgentAuth(apiKey, subAgentId);
	if (teamAttempt.authResult) return { authResult: teamAttempt.authResult };
	return {
		authResult: null,
		failureMessage: teamAttempt.failureMessage
	};
}
/**
* Core authentication handler that can be reused across middleware
*/
async function runApiKeyAuthHandler(c, next) {
	if (c.req.method === "OPTIONS") {
		await next();
		return;
	}
	const reqData = extractRequestData(c);
	if (process.env.ENVIRONMENT === "development" || process.env.ENVIRONMENT === "test") {
		logger.info({}, "development environment");
		const attempt$1 = await authenticateRequest(reqData);
		if (attempt$1.authResult) c.set("executionContext", buildExecutionContext(attempt$1.authResult, reqData));
		else {
			logger.info({}, reqData.apiKey ? "Development/test environment - fallback to default context due to invalid API key" : "Development/test environment - no API key provided, using default context");
			c.set("executionContext", buildExecutionContext(createDevContext(reqData), reqData));
		}
		await next();
		return;
	}
	if (!reqData.authHeader || !reqData.authHeader.startsWith("Bearer ")) throw new HTTPException(401, { message: "Missing or invalid authorization header. Expected: Bearer <api_key>" });
	if (!reqData.apiKey || reqData.apiKey.length < 16) throw new HTTPException(401, { message: "Invalid API key format" });
	let attempt = { authResult: null };
	try {
		attempt = await authenticateRequest(reqData);
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		logger.error({ error }, "Authentication failed");
		throw new HTTPException(500, { message: "Authentication failed" });
	}
	if (!attempt.authResult) {
		logger.error({}, "API key authentication error - no valid auth method found");
		throw new HTTPException(401, { message: attempt.failureMessage || "Invalid Token" });
	}
	logger.debug({
		tenantId: attempt.authResult.tenantId,
		projectId: attempt.authResult.projectId,
		agentId: attempt.authResult.agentId,
		subAgentId: reqData.subAgentId
	}, "API key authenticated successfully");
	c.set("executionContext", buildExecutionContext(attempt.authResult, reqData));
	await next();
}
const runApiKeyAuth = () => createMiddleware(runApiKeyAuthHandler);
/**
* Creates a middleware that applies API key authentication except for specified route patterns
* @param skipRouteCheck - Function that returns true if the route should skip authentication
*/
const runApiKeyAuthExcept = (skipRouteCheck) => createMiddleware(async (c, next) => {
	if (skipRouteCheck(c.req.path)) return next();
	return runApiKeyAuthHandler(c, next);
});
/**
* Helper middleware for endpoints that optionally support API key authentication
* If no auth header is present, it continues without setting the executionContext
*/
const runOptionalAuth = () => createMiddleware(async (c, next) => {
	const authHeader = c.req.header("Authorization");
	if (!authHeader || !authHeader.startsWith("Bearer ")) {
		await next();
		return;
	}
	return runApiKeyAuthHandler(c, next);
});

//#endregion
export { runApiKeyAuth, runApiKeyAuthExcept, runOptionalAuth };