import { manage_schema_exports } from "../db/manage/manage-schema.js";
import { getLogger } from "../utils/logger.js";
import { getDatabaseErrorLogContext } from "../utils/error.js";
import { generateId } from "../utils/conversations.js";
import { doltAddAndCommit, doltReset, doltStatus } from "./commit.js";
import { checkoutBranch } from "./branches-api.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { drizzle } from "drizzle-orm/node-postgres";

//#region src/dolt/ref-scope.ts
const logger = getLogger("ref-scope");
/**
* AsyncLocalStorage to track active ref scope and detect nesting
*/
const refScopeStorage = new AsyncLocalStorage();
/**
* Error thrown when nested withRef calls are detected with different refs
*/
var NestedRefScopeError = class extends Error {
	constructor(existingRef, attemptedRef) {
		super(`Nested withRef detected. Already in ref scope '${existingRef}', attempted to enter '${attemptedRef}'. Either reuse the existing db from the outer scope, or batch your operations in a single withRef call.`);
		this.name = "NestedRefScopeError";
	}
};
/**
* Execute a function with a database connection scoped to a specific ref (branch/tag/commit).
*
* This function:
* 1. Gets a dedicated connection from the pool
* 2. For branches: checks out the branch directly
* 3. For tags/commits: creates a temporary branch from the hash, then checks out
* 4. Executes the callback with a Drizzle client scoped to that connection
* 5. Optionally auto-commits changes on success (if commit: true)
* 6. Cleans up: checks out main, deletes temp branch if created, releases connection
*
* Important:
* - The callback should only perform database operations
* - Do NOT hold the connection while making external API calls
*
* @param pool - The PostgreSQL connection pool
* @param resolvedRef - The resolved ref (branch, tag, or commit)
* @param dataAccessFn - The function to execute with the scoped database client
* @param options - Optional configuration for commit behavior
* @returns The result of the callback function
*
* @example
* ```typescript
* // Simple read (no commit)
* const agent = await withRef(pool, resolvedRef, (db) =>
*   getAgent(db, agentId)
* );
*
* // Write with auto-commit
* await withRef(pool, resolvedRef, async (db) => {
*   await updateAgent(db, agentId, data);
* }, { commit: true, commitMessage: 'Update agent config' });
*
* // Batch multiple operations with commit
* const result = await withRef(pool, resolvedRef, async (db) => {
*   await createCredential(db, credData);
*   await updateTool(db, toolId, { credentialId });
*   return { success: true };
* }, { commit: true, author: { name: 'oauth', email: 'oauth@inkeep.com' } });
* ```
*/
async function withRef(pool, resolvedRef, dataAccessFn, options) {
	const { commit = false, commitMessage = "withRef auto-commit", author } = options ?? {};
	const startTime = Date.now();
	const connectionId = generateId();
	const existingScope = refScopeStorage.getStore();
	if (existingScope) {
		if (existingScope.ref === resolvedRef.name) {
			logger.debug({
				ref: resolvedRef.name,
				existingConnectionId: existingScope.connectionId
			}, "Reusing existing ref scope");
			return dataAccessFn(existingScope.db);
		}
		throw new NestedRefScopeError(existingScope.ref, resolvedRef.name);
	}
	if (process.env.ENVIRONMENT === "test") {
		const connection$1 = await pool.connect();
		try {
			const db = drizzle(connection$1, { schema: manage_schema_exports });
			return await refScopeStorage.run({
				db,
				ref: resolvedRef.name,
				connectionId
			}, () => dataAccessFn(db));
		} finally {
			connection$1.release();
		}
	}
	logger.debug({
		ref: resolvedRef.name,
		refType: resolvedRef.type,
		connectionId
	}, "Acquiring connection for ref scope");
	const connectStart = Date.now();
	const connection = await pool.connect();
	const connectMs = Date.now() - connectStart;
	if (connectMs > 5e3) logger.info({
		ref: resolvedRef.name,
		connectMs,
		connectionId
	}, "Slow pool.connect in withRef");
	let tempBranch = null;
	try {
		const db = drizzle(connection, { schema: manage_schema_exports });
		if (resolvedRef.type === "branch") {
			logger.debug({
				branch: resolvedRef.name,
				connectionId
			}, "Checking out branch");
			const checkoutStart = Date.now();
			await checkoutBranch(db)({
				branchName: resolvedRef.name,
				syncSchema: false
			});
			const checkoutMs = Date.now() - checkoutStart;
			if (checkoutMs > 5e3) logger.info({
				ref: resolvedRef.name,
				checkoutMs,
				connectionId
			}, "Slow checkoutBranch in withRef");
		} else {
			tempBranch = `temp_${resolvedRef.type}_${Date.now()}_${generateId()}`;
			logger.debug({
				tempBranch,
				hash: resolvedRef.hash,
				refType: resolvedRef.type,
				connectionId
			}, "Creating temporary branch from ref");
			await connection.query(`SELECT DOLT_CHECKOUT('-b', $1, $2)`, [tempBranch, resolvedRef.hash]);
		}
		const result = await refScopeStorage.run({
			db,
			ref: resolvedRef.name,
			connectionId
		}, () => dataAccessFn(db));
		if (commit && resolvedRef.type === "branch") try {
			if ((await doltStatus(db)()).length > 0) {
				logger.info({
					branch: resolvedRef.name,
					message: commitMessage,
					connectionId
				}, "Auto-committing changes");
				await doltAddAndCommit(db)({
					message: commitMessage,
					author: author ?? {
						name: "agents-api",
						email: "api@inkeep.com"
					}
				});
				logger.info({
					branch: resolvedRef.name,
					connectionId
				}, "Successfully committed changes");
			}
		} catch (commitError) {
			logger.error({
				error: commitError,
				...getDatabaseErrorLogContext(commitError),
				branch: resolvedRef.name,
				connectionId
			}, "Failed to auto-commit changes — uncommitted writes will be lost on connection release");
			throw commitError;
		}
		logger.debug({
			ref: resolvedRef.name,
			duration: Date.now() - startTime,
			connectionId
		}, "Ref scope completed successfully");
		return result;
	} catch (error) {
		if (commit && resolvedRef.type === "branch") try {
			const db = drizzle(connection, { schema: manage_schema_exports });
			if ((await doltStatus(db)()).length > 0) {
				await doltReset(db)();
				logger.info({
					branch: resolvedRef.name,
					connectionId
				}, "Reset uncommitted changes due to failed operation");
			}
		} catch (resetError) {
			logger.error({
				error: resetError,
				...getDatabaseErrorLogContext(resetError),
				branch: resolvedRef.name,
				connectionId
			}, "Failed to reset changes after error");
		}
		logger.error({
			ref: resolvedRef.name,
			duration: Date.now() - startTime,
			connectionId,
			error,
			...getDatabaseErrorLogContext(error)
		}, "Ref scope failed");
		throw error;
	} finally {
		const cleanupStart = Date.now();
		try {
			await connection.query(`SELECT DOLT_CHECKOUT('main')`);
			if (tempBranch) {
				logger.debug({
					tempBranch,
					connectionId
				}, "Deleting temporary branch");
				await connection.query(`SELECT DOLT_BRANCH('-D', $1)`, [tempBranch]);
			}
		} catch (cleanupError) {
			logger.info({
				ref: resolvedRef.name,
				connectionId,
				cleanupMs: Date.now() - cleanupStart,
				error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
			}, "withRef cleanup failed");
			logger.error({
				error: cleanupError,
				...getDatabaseErrorLogContext(cleanupError),
				tempBranch,
				connectionId
			}, "Error during ref scope cleanup");
		} finally {
			const totalMs = Date.now() - startTime;
			if (totalMs > 5e3) logger.info({
				ref: resolvedRef.name,
				totalMs,
				connectMs,
				connectionId
			}, "Slow withRef total duration");
			connection.release();
			logger.debug({
				ref: resolvedRef.name,
				duration: totalMs,
				connectionId
			}, "Connection released");
		}
	}
}
/**
* Check if currently inside a withRef scope
*/
function isInRefScope() {
	return refScopeStorage.getStore() !== void 0;
}
/**
* Get the current ref scope context if inside one
*/
function getCurrentRefScope() {
	return refScopeStorage.getStore();
}
/**
* Get the database client from the current ref scope.
* Throws if not inside a withRef scope.
*/
function getRefScopedDb() {
	const scope = refScopeStorage.getStore();
	if (!scope) throw new Error("Not inside a withRef scope. Wrap your code in withRef() to get a ref-scoped database client.");
	return scope.db;
}

//#endregion
export { NestedRefScopeError, getCurrentRefScope, getRefScopedDb, isInRefScope, withRef };