import { getLogger } from "../utils/logger.js";
import { createApiError } from "../utils/error.js";
import { isRefWritable, resolveRef } from "./ref-helpers.js";
import { ensureBranchExists } from "./branch.js";

//#region src/dolt/ref-middleware.ts
const logger = getLogger("ref-middleware");
const MERGE_ROUTE_PATTERN = /\/branches\/merge(\/preview)?$/;
function isMergeRoute(path) {
	return MERGE_ROUTE_PATTERN.test(path);
}
/**
* Default tenant ID extractor - extracts from /tenants/{tenantId} path pattern
*/
const defaultExtractTenantId = (c) => {
	return c.req.path.match(/^\/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(/^\/tenants\/[^/]+\/(?:projects|project-full)(?:\/([^/]+))?/)?.[1];
};
/**
* 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) => {
		let ref = c.req.query("ref");
		const path = c.req.path;
		const pathSplit = path.split("/");
		if (!ref && isMergeRoute(path) && c.req.method === "POST") try {
			const body = await c.req.json();
			if (body && typeof body.targetBranch === "string") {
				ref = body.targetBranch;
				logger.debug({ ref }, "Extracted targetBranch from merge route body as ref");
			}
		} catch {
			logger.debug("Could not extract targetBranch from merge route body");
		}
		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 ? `${tenantId}_${projectId}_main` : `${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);
		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 = `${tenantId}_${projectId}_main`;
	const projectScopedRef = `${tenantId}_${projectId}_${ref}`;
	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) {
		if (c.req.method === "PUT") {
			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;
}
/**
* Creates a write protection middleware that prevents write operations on immutable refs.
*
* This middleware checks if the resolved ref is writable (branches are writable,
* tags and commits are not) and rejects write operations on immutable refs.
*
* @returns Hono middleware function
*
* @example
* ```typescript
* import { createRefMiddleware, createWriteProtectionMiddleware } from '@inkeep/agents-core';
*
* const refMiddleware = createRefMiddleware(db);
* const writeProtection = createWriteProtectionMiddleware();
*
* app.use('/tenants/*', refMiddleware, writeProtection);
* ```
*/
const createWriteProtectionMiddleware = () => {
	return 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();
	};
};
/**
* @deprecated Use createRefMiddleware instead. This is kept for backwards compatibility.
*/
const refMiddlewareFactory = createRefMiddleware;
/**
* @deprecated Use createWriteProtectionMiddleware instead. This is kept for backwards compatibility.
*/
const writeProtectionMiddlewareFactory = createWriteProtectionMiddleware;

//#endregion
export { createRefMiddleware, createWriteProtectionMiddleware, isMergeRoute, refMiddlewareFactory, writeProtectionMiddlewareFactory };