import manageDbClient_default from "../data/db/manageDbClient.js";
import "../data/db/index.js";
import { createApiError, ensureBranchExists, getLogger, getProjectScopedRef, getTenantScopedRef, isRefWritable, resolveRef } from "@inkeep/agents-core";

//#region src/middleware/ref.ts
const logger = getLogger("ref-middleware");
/**
* Default tenant ID extractor - extracts from /tenants/{tenantId} path pattern
*/
const defaultExtractTenantId = (c) => {
	return c.req.path.match(/^\/(?:manage|run)\/tenants\/([^/]+)/)?.[1];
};
/**
* Default project ID extractor - extracts from /tenants/{tenantId}/projects/{projectId} or
* /tenants/{tenantId}/project-full/{projectId} path patterns
*/
const defaultExtractProjectId = (c) => {
	return c.req.path.match(/^\/(?:manage|run)\/tenants\/[^/]+\/(?:projects|project-full)(?:\/([^/]+))?/)?.[1];
};
/**
* Extract tenantId from the executionContext set by apiKeyAuth middleware.
*/
const extractTenantIdFromExecutionContext = (c) => {
	const executionContext = c.get("executionContext");
	let tenantId;
	if (executionContext) tenantId = executionContext.tenantId;
	else tenantId = defaultExtractTenantId(c);
	return tenantId;
};
/**
* Extract projectId from the executionContext set by apiKeyAuth middleware.
*/
const extractProjectIdFromExecutionContext = (c) => {
	const executionContext = c.get("executionContext");
	let projectId;
	if (executionContext) projectId = executionContext.projectId;
	else projectId = defaultExtractProjectId(c);
	return projectId;
};
/**
* Creates a ref resolution middleware factory.
*
* This middleware:
* 1. Extracts tenantId and projectId from the request
* 2. Resolves the `ref` query parameter to a ResolvedRef
* 3. Creates branches if needed (tenant_main, project_main)
* 4. Sets `resolvedRef` in the Hono context for downstream handlers
*
* @param db - The Doltgres database client to use for ref resolution
* @param options - Optional configuration for extraction and validation
* @returns Hono middleware function
*
* @example
* ```typescript
* import { createRefMiddleware } from '@inkeep/agents-core';
* import { manageDbClient } from './db';
*
* const refMiddleware = createRefMiddleware(manageDbClient);
* app.use('/tenants/*', refMiddleware);
* ```
*/
const createRefMiddleware = (db, options = {}) => {
	const { extractTenantId = defaultExtractTenantId, extractProjectId = defaultExtractProjectId, allowProjectIdFromBody = true } = options;
	return async (c, next) => {
		const ref = c.req.query("ref");
		const pathSplit = c.req.path.split("/");
		const tenantId = extractTenantId(c);
		let projectId = extractProjectId(c);
		if (!projectId && allowProjectIdFromBody && [
			"POST",
			"PUT",
			"PATCH"
		].includes(c.req.method)) try {
			const body = await c.req.json();
			if (body && typeof body.projectId === "string") {
				projectId = body.projectId;
				logger.debug({ projectId }, "Extracted projectId from request body");
			}
		} catch {
			logger.debug({}, "Could not extract projectId from body");
		}
		if (!tenantId) throw createApiError({
			code: "bad_request",
			message: "Missing tenantId"
		});
		if (process.env.ENVIRONMENT === "test") {
			const defaultRef = {
				type: "branch",
				name: projectId ? getProjectScopedRef(tenantId, projectId, "main") : getTenantScopedRef(tenantId, "main"),
				hash: "test-hash"
			};
			c.set("resolvedRef", defaultRef);
			await next();
			return;
		}
		if (pathSplit.length < 4 && ref !== "main" && ref !== void 0) throw createApiError({
			code: "bad_request",
			message: "Ref is not supported for this path"
		});
		let resolvedRef;
		if (projectId) resolvedRef = await resolveProjectRef(db, c, tenantId, projectId, ref);
		else resolvedRef = await resolveTenantRef(db, tenantId, ref);
		if (c.req.path.includes("/signoz/")) logger.debug({
			resolvedRef,
			projectId,
			tenantId
		}, "Resolved ref");
		else logger.info({
			resolvedRef,
			projectId,
			tenantId
		}, "Resolved ref");
		c.set("resolvedRef", resolvedRef);
		await next();
	};
};
/**
* Resolve ref for project-scoped requests
*/
async function resolveProjectRef(db, c, tenantId, projectId, ref) {
	const projectMain = getProjectScopedRef(tenantId, projectId, "main");
	const projectScopedRef = ref ? getProjectScopedRef(tenantId, projectId, ref) : projectMain;
	if (ref && ref !== "main") {
		let refResult$1 = await resolveRef(db)(projectScopedRef);
		if (!refResult$1) refResult$1 = await resolveRef(db)(ref);
		if (!refResult$1) throw createApiError({
			code: "not_found",
			message: `Unknown ref: ${ref}`
		});
		return refResult$1;
	}
	let refResult = null;
	try {
		refResult = await resolveRef(db)(projectMain);
	} catch (error) {
		logger.warn({
			error,
			projectMain
		}, "Failed to resolve project main branch");
		refResult = null;
	}
	if (!refResult) {
		const method = c.req.method;
		if (method === "PUT" || method === "PATCH") {
			const tenantMain = `${tenantId}_main`;
			let tenantRefResult = await resolveRef(db)(tenantMain);
			if (!tenantRefResult) {
				await ensureBranchExists(db, tenantMain);
				tenantRefResult = await resolveRef(db)(tenantMain);
			}
			if (tenantRefResult) return tenantRefResult;
			throw createApiError({
				code: "internal_server_error",
				message: `Failed to create tenant main branch for upsert`
			});
		}
		throw createApiError({
			code: "not_found",
			message: `Project not found: ${projectId}`
		});
	}
	return refResult;
}
/**
* Resolve ref for tenant-level requests
*/
async function resolveTenantRef(db, tenantId, ref) {
	const tenantMain = `${tenantId}_main`;
	if (ref && ref !== "main") {
		const tenantScopedRef = `${tenantId}_${ref}`;
		let refResult$1 = await resolveRef(db)(tenantScopedRef);
		if (!refResult$1) refResult$1 = await resolveRef(db)(ref);
		if (!refResult$1) throw createApiError({
			code: "not_found",
			message: `Unknown ref: ${ref}`
		});
		return refResult$1;
	}
	let refResult = await resolveRef(db)(tenantMain);
	if (!refResult) {
		await ensureBranchExists(db, tenantMain);
		refResult = await resolveRef(db)(tenantMain);
		if (!refResult) throw createApiError({
			code: "internal_server_error",
			message: `Failed to create tenant main branch: ${tenantMain}`
		});
	}
	return refResult;
}
const writeProtectionMiddleware = async (c, next) => {
	if (process.env.ENVIRONMENT === "test") {
		await next();
		return;
	}
	const resolvedRef = c.get("resolvedRef");
	if (!resolvedRef) {
		await next();
		return;
	}
	const method = c.req.method;
	if ([
		"POST",
		"PUT",
		"PATCH",
		"DELETE"
	].includes(method) && !isRefWritable(resolvedRef)) throw createApiError({
		code: "bad_request",
		message: `Cannot perform write operation on ${resolvedRef.type}. Tags and commits are immutable. Write to a branch instead.`
	});
	await next();
};
const manageRefMiddleware = createRefMiddleware(manageDbClient_default);
const runRefMiddleware = createRefMiddleware(manageDbClient_default, {
	extractTenantId: extractTenantIdFromExecutionContext,
	extractProjectId: extractProjectIdFromExecutionContext,
	allowProjectIdFromBody: false
});

//#endregion
export { createRefMiddleware, manageRefMiddleware, runRefMiddleware, writeProtectionMiddleware };