import { getLogger } from "../../utils/logger.js";
import { doltBranch, doltBranchExists, doltCheckout, doltDeleteBranch, doltListBranches } from "../../dolt/branch.js";
import { createProjectMetadata, deleteProjectMetadata, getProjectMetadata, listProjectsMetadataPaginated } from "../runtime/projects.js";
import { sql } from "drizzle-orm";

//#region src/data-access/manage/projectLifecycle.ts
const logger = getLogger("project-lifecycle");
/**
* Generate the main branch name for a project
*/
function getProjectMainBranchName(tenantId, projectId) {
	return `${tenantId}_${projectId}_main`;
}
/**
* Create a project with its main branch
*
* This utility:
* 1. Creates the project record in the runtime DB (source of truth for existence)
* 2. Creates the project main branch in the config DB (Doltgres)
*
* @param runDb - Runtime database client (Postgres)
* @param configDb - Config database client (Doltgres)
*/
const createProjectMetadataAndBranch = (runDb, configDb) => async (params) => {
	const { tenantId, projectId, createdBy } = params;
	const mainBranchName = getProjectMainBranchName(tenantId, projectId);
	logger.info({
		tenantId,
		projectId,
		mainBranchName
	}, "Creating project with branch");
	const runtimeProject = await createProjectMetadata(runDb)({
		id: projectId,
		tenantId,
		createdBy: createdBy ?? null,
		mainBranchName
	});
	logger.debug({ projectId }, "Created project in runtime DB");
	try {
		if (!await doltBranchExists(configDb)({ name: mainBranchName })) {
			await doltBranch(configDb)({ name: mainBranchName });
			logger.debug({ mainBranchName }, "Created project main branch");
		}
	} catch (error) {
		logger.error({
			error,
			mainBranchName
		}, "Failed to create project branch, rolling back");
		await deleteProjectMetadata(runDb)({
			tenantId,
			projectId
		});
		throw error;
	}
	logger.info({
		tenantId,
		projectId,
		mainBranchName
	}, "Successfully created project with branch");
	return {
		id: runtimeProject.id,
		tenantId: runtimeProject.tenantId,
		mainBranchName: runtimeProject.mainBranchName,
		createdAt: runtimeProject.createdAt,
		createdBy: runtimeProject.createdBy
	};
};
/**
* Delete a project and ALL its branches
*
* This utility:
* 1. Gets the project from runtime DB
* 2. Lists and deletes ALL branches matching the project prefix from config DB (Doltgres)
* 3. Deletes the project record from runtime DB
*
* Note: Callers should handle cascade deletion of runtime entities (conversations, etc.)
* before calling this function.
*
* @param runDb - Runtime database client (Postgres)
* @param configDb - Config database client (Doltgres)
*/
const deleteProjectAndBranches = (runDb, configDb) => async (params) => {
	const { tenantId, projectId } = params;
	logger.info({
		tenantId,
		projectId
	}, "Deleting project with all branches");
	if (!await getProjectMetadata(runDb)({
		tenantId,
		projectId
	})) {
		logger.warn({
			tenantId,
			projectId
		}, "Project not found in runtime DB");
		return false;
	}
	try {
		await doltCheckout(configDb)({ branch: "main" });
		const allBranches = await doltListBranches(configDb)();
		const prefix = `${tenantId}_${projectId}_`;
		const projectBranches = allBranches.filter((b) => b.name.startsWith(prefix));
		const failedBranches = [];
		for (const branch of projectBranches) try {
			await doltDeleteBranch(configDb)({
				name: branch.name,
				force: true
			});
			logger.debug({ branchName: branch.name }, "Deleted project branch");
		} catch (error) {
			failedBranches.push(branch.name);
			logger.error({
				error,
				branchName: branch.name
			}, "Failed to delete project branch, continuing with remaining branches");
		}
		if (failedBranches.length > 0) logger.warn({
			tenantId,
			projectId,
			failedBranches,
			successCount: projectBranches.length - failedBranches.length
		}, "Some project branches could not be deleted");
		else logger.info({
			tenantId,
			projectId,
			branchCount: projectBranches.length
		}, "Deleted all project branches");
	} catch (error) {
		logger.error({
			error,
			tenantId,
			projectId
		}, "Failed to list/delete project branches, continuing with runtime cleanup");
	}
	const deleted = await deleteProjectMetadata(runDb)({
		tenantId,
		projectId
	});
	if (deleted) logger.info({
		tenantId,
		projectId
	}, "Successfully deleted project with all branches");
	else logger.warn({
		tenantId,
		projectId
	}, "Failed to delete project from runtime DB");
	return deleted;
};
/**
* Get project with branch info
*
* Returns the project from runtime DB including the main branch name
*/
const getProjectWithBranchInfo = (runDb) => async (params) => {
	return await getProjectMetadata(runDb)(params);
};
/**
* Get project metadata from config DB at a specific branch using AS OF syntax
*
* This queries the projects table without checking out the branch
*/
async function getProjectMetadataFromBranch(configDb, branchName, tenantId, projectId) {
	try {
		const result = await configDb.execute(sql.raw(`
        SELECT name, description, models, stop_when, created_at, updated_at
        FROM projects AS OF '${branchName}'
        WHERE tenant_id = '${tenantId}' AND id = '${projectId}'
        LIMIT 1
      `));
		if (result.rows.length === 0) return null;
		const row = result.rows[0];
		return {
			name: row.name,
			description: row.description,
			models: row.models,
			stopWhen: row.stop_when,
			createdAt: row.created_at,
			updatedAt: row.updated_at
		};
	} catch (error) {
		logger.warn({
			error,
			branchName,
			tenantId,
			projectId
		}, "Failed to get project metadata from branch");
		return null;
	}
}
/**
* List projects with metadata from both runtime and config DBs
*
* This function:
* 1. Gets the list of projects from runtime DB (source of truth for existence)
* 2. For each project, queries the config DB at the project's main branch to get metadata
*
* @param runDb - Runtime database client (Postgres)
* @param configDb - Config database client (Doltgres)
*/
const listProjectsWithMetadataPaginated = (runDb, configDb) => async (params) => {
	const { tenantId, pagination, projectIds } = params;
	const projectMetadataResult = await listProjectsMetadataPaginated(runDb)({
		tenantId,
		pagination,
		projectIds
	});
	return {
		data: await Promise.all(projectMetadataResult.data.map(async (projectMetadata) => {
			const metadata = await getProjectMetadataFromBranch(configDb, projectMetadata.mainBranchName, tenantId, projectMetadata.id);
			return {
				id: projectMetadata.id,
				tenantId: projectMetadata.tenantId,
				createdAt: projectMetadata.createdAt,
				createdBy: projectMetadata.createdBy,
				mainBranchName: projectMetadata.mainBranchName,
				name: metadata?.name ?? null,
				description: metadata?.description ?? null,
				models: metadata?.models ?? null,
				stopWhen: metadata?.stopWhen ?? null,
				configUpdatedAt: metadata?.updatedAt ?? null
			};
		})),
		pagination: projectMetadataResult.pagination
	};
};
/**
* Get a single project with metadata from both runtime and config DBs
*
* @param runDb - Runtime database client (Postgres)
* @param configDb - Config database client (Doltgres)
*/
const getProjectWithMetadata = (runDb, configDb) => async (params) => {
	const { tenantId, projectId } = params;
	const runtimeProject = await getProjectMetadata(runDb)({
		tenantId,
		projectId
	});
	if (!runtimeProject) return null;
	const metadata = await getProjectMetadataFromBranch(configDb, runtimeProject.mainBranchName, tenantId, projectId);
	return {
		id: runtimeProject.id,
		tenantId: runtimeProject.tenantId,
		createdAt: runtimeProject.createdAt,
		createdBy: runtimeProject.createdBy,
		mainBranchName: runtimeProject.mainBranchName,
		name: metadata?.name ?? null,
		description: metadata?.description ?? null,
		models: metadata?.models ?? null,
		stopWhen: metadata?.stopWhen ?? null,
		configUpdatedAt: metadata?.updatedAt ?? null
	};
};

//#endregion
export { createProjectMetadataAndBranch, deleteProjectAndBranches, getProjectMainBranchName, getProjectWithBranchInfo, getProjectWithMetadata, listProjectsWithMetadataPaginated };