import { MCPTransportType } from "../../types/utility.js";
import { getLogger } from "../logger.js";
import { Composio } from "@composio/core";

//#region src/utils/third-party-mcp-servers/composio-client.ts
/**
* Composio MCP Server Client
* Handles all Composio-specific operations for MCP server integration
*/
const logger$1 = getLogger("composio-client");
const TOOLKIT_TO_CATEGORY = {
	github: "development",
	gitlab: "development",
	bitbucket: "development",
	jira: "project_management",
	asana: "project_management",
	linear: "project_management",
	trello: "project_management",
	monday: "project_management",
	slack: "communication",
	discord: "communication",
	teams: "communication",
	zoom: "communication",
	notion: "knowledge",
	confluence: "knowledge",
	docs: "knowledge",
	drive: "storage",
	dropbox: "storage",
	box: "storage",
	stripe: "payments",
	paypal: "payments",
	square: "payments",
	gmail: "communication",
	outlook: "communication",
	calendar: "productivity",
	sheets: "productivity",
	airtable: "database",
	salesforce: "crm",
	hubspot: "crm",
	zendesk: "communication",
	intercom: "communication"
};
let composio = null;
/**
* Get or create a Composio instance
* Returns null if COMPOSIO_API_KEY is not configured
*/
function getComposioInstance() {
	if (!process.env.COMPOSIO_API_KEY) return null;
	if (!composio) composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
	return composio;
}
/**
* Derive a Composio user ID from tenant and project IDs
* This creates a unique identifier for Composio connected accounts per tenant/project
*/
function deriveComposioUserId(tenantId, projectId) {
	return `${tenantId}||${projectId}`;
}
/**
* Get the Composio user ID based on credential scope
* - For project-scoped: uses derived tenant||project ID (shared team credentials)
* - For user-scoped: uses the actual user ID (per-user credentials)
*/
function getComposioUserId(tenantId, projectId, credentialScope, userId) {
	if (credentialScope === "user" && userId) return userId;
	return deriveComposioUserId(tenantId, projectId);
}
/**
* Configure a Composio MCP server config with the appropriate user_id and x-api-key.
* Mutates serverConfig in place:
*  - Injects user_id query param into the URL (scoped by tenant/project/user)
*  - Injects connected_account_id query param if provided (pins to a specific account)
*  - Injects x-api-key header from COMPOSIO_API_KEY env var
*
* No-op if the URL is not a composio.dev URL or already has a user_id.
*/
function configureComposioMCPServer(serverConfig, tenantId, projectId, credentialScope, userId, connectedAccountId) {
	const baseUrl = serverConfig.url?.toString();
	if (!baseUrl?.includes("composio.dev")) return;
	const urlObj = new URL(baseUrl);
	if (!urlObj.searchParams.has("user_id")) {
		const composioUserId = getComposioUserId(tenantId, projectId, credentialScope, userId);
		urlObj.searchParams.set("user_id", composioUserId);
	}
	if (connectedAccountId && !urlObj.searchParams.has("connected_account_id")) urlObj.searchParams.set("connected_account_id", connectedAccountId);
	serverConfig.url = urlObj.toString();
	const composioApiKey = process.env.COMPOSIO_API_KEY;
	if (composioApiKey) serverConfig.headers = {
		...serverConfig.headers,
		"x-api-key": composioApiKey
	};
}
/**
* Extract server ID from a Composio MCP URL
* Example: https://backend.composio.dev/v3/mcp/1234-1234-1234?user_id=... -> 1234-1234-1234
*/
function extractComposioServerId(mcpUrl) {
	if (!mcpUrl.includes("composio.dev")) return null;
	try {
		const pathParts = new URL(mcpUrl).pathname.split("/");
		const mcpIndex = pathParts.indexOf("mcp");
		if (mcpIndex !== -1 && pathParts[mcpIndex + 1]) return pathParts[mcpIndex + 1];
		return null;
	} catch {
		return null;
	}
}
/**
* Delete a Composio connected account
* Returns true if successful, false if failed (non-blocking)
*/
async function deleteComposioConnectedAccount(accountId) {
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured, skipping account deletion");
		return false;
	}
	try {
		await composioInstance.connectedAccounts.delete(accountId);
		return true;
	} catch (error) {
		logger$1.warn({ error }, "Error deleting Composio connected account");
		return false;
	}
}
/**
* Fetch connected accounts from Composio for a given user
* Returns null if the API call fails or Composio is not configured
*/
async function fetchComposioConnectedAccounts(derivedUserId) {
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured, skipping connected accounts fetch");
		return null;
	}
	try {
		return await composioInstance.connectedAccounts.list({
			userIds: [derivedUserId],
			statuses: [
				"ACTIVE",
				"INITIATED",
				"EXPIRED"
			]
		});
	} catch (error) {
		logger$1.error({ error }, "Error fetching Composio connected accounts");
		return null;
	}
}
/**
* Check if a Composio MCP server is authenticated for the given tenant/project/user
* @param credentialScope - 'project' for shared team credentials, 'user' for per-user credentials
* @param userId - The actual user ID (required if credentialScope is 'user')
*/
async function isComposioMCPServerAuthenticated(tenantId, projectId, mcpServerUrl, credentialScope = "project", userId) {
	if (!process.env.COMPOSIO_API_KEY) {
		logger$1.info("Composio API key not configured, skipping auth check");
		return { authenticated: false };
	}
	const serverId = extractComposioServerId(mcpServerUrl);
	if (!serverId) {
		logger$1.info({ mcpServerUrl }, "Could not extract Composio server ID from URL");
		return { authenticated: false };
	}
	const composioUserId = getComposioUserId(tenantId, projectId, credentialScope, userId);
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured, skipping auth check");
		return { authenticated: false };
	}
	try {
		const [composioMcpServer, connectedAccounts] = await Promise.all([composioInstance.mcp.get(serverId), fetchComposioConnectedAccounts(composioUserId)]);
		if (!(composioMcpServer.authConfigIds.length > 0 ? composioMcpServer.authConfigIds[0] : null)) return { authenticated: false };
		if (!connectedAccounts) return { authenticated: false };
		const activeAccounts = connectedAccounts.items.filter((account) => account.status === "ACTIVE");
		const activeAuthConfigIds = new Set(activeAccounts.map((account) => account.authConfig.id));
		if (!composioMcpServer.authConfigIds.some((authConfigId) => activeAuthConfigIds.has(authConfigId))) return { authenticated: false };
		const connectedAccountId = activeAccounts.find((account) => composioMcpServer.authConfigIds.includes(account.authConfig.id))?.id;
		return {
			authenticated: !!connectedAccountId,
			connectedAccountId
		};
	} catch (error) {
		logger$1.error({
			error,
			mcpServerUrl
		}, "Error checking Composio authentication status");
		return {
			authenticated: false,
			error: true
		};
	}
}
/**
* Convert Composio server data to PrebuiltMCPServer format
*/
function transformComposioServerData(composioMcpServer, isAuthenticated, url, thirdPartyConnectAccountUrl, connectedAccountId, authScheme) {
	const firstToolkit = composioMcpServer.toolkits[0];
	const category = firstToolkit ? TOOLKIT_TO_CATEGORY[firstToolkit] || "integration" : "integration";
	const imageUrl = composioMcpServer.toolkitIcons?.[firstToolkit];
	const description = composioMcpServer.toolkits.length > 0 ? `${composioMcpServer.toolkits.slice(0, 3).join(", ")} integration${composioMcpServer.toolkits.length > 1 ? "s" : ""} via Composio` : "Integration via Composio";
	return {
		id: composioMcpServer.id,
		name: composioMcpServer.name,
		url,
		transport: MCPTransportType.streamableHttp,
		imageUrl,
		category,
		description,
		isOpen: isAuthenticated,
		thirdPartyConnectAccountUrl,
		connectedAccountId,
		authScheme
	};
}
/**
* Side effects: Ensure Composio account exists for the given server
* Handles account creation/deletion and returns the redirect URL
* Returns null if account creation fails
*/
async function ensureComposioAccount(composioMcpServer, derivedUserId, staleAccounts) {
	const firstAuthConfigId = composioMcpServer.authConfigIds[0];
	if (!firstAuthConfigId) {
		logger$1.error({ serverId: composioMcpServer.id }, "No auth config ID found for MCP server");
		return null;
	}
	const existingStaleAccounts = staleAccounts.filter((account) => account.authConfig.id === firstAuthConfigId);
	await Promise.all(existingStaleAccounts.map((account) => deleteComposioConnectedAccount(account.id)));
	try {
		const composioInstance = getComposioInstance();
		if (!composioInstance) {
			logger$1.error({ serverId: composioMcpServer.id }, "Composio not configured");
			return null;
		}
		return (await composioInstance.connectedAccounts.link(derivedUserId, firstAuthConfigId)).redirectUrl ?? null;
	} catch (error) {
		logger$1.error({
			serverId: composioMcpServer.id,
			error
		}, "Error creating connected account for MCP server");
		return null;
	}
}
/**
* Get the OAuth redirect URL for a Composio MCP server based on credential scope
* This should be called AFTER scope selection to get the correct URL
* @param credentialScope - 'project' for shared team credentials, 'user' for per-user credentials
* @param userId - The actual user ID (required if credentialScope is 'user')
*/
async function getComposioOAuthRedirectUrl(tenantId, projectId, mcpServerUrl, credentialScope, userId) {
	if (!process.env.COMPOSIO_API_KEY) {
		logger$1.info("Composio API key not configured");
		return null;
	}
	const serverId = extractComposioServerId(mcpServerUrl);
	if (!serverId) {
		logger$1.info({ mcpServerUrl }, "Could not extract Composio server ID from URL");
		return null;
	}
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured");
		return null;
	}
	const composioUserId = getComposioUserId(tenantId, projectId, credentialScope, userId);
	try {
		return await ensureComposioAccount(await composioInstance.mcp.get(serverId), composioUserId, (await fetchComposioConnectedAccounts(composioUserId))?.items.filter((account) => account.status === "INITIATED" || account.status === "EXPIRED") ?? []);
	} catch (error) {
		logger$1.error({
			error,
			mcpServerUrl
		}, "Failed to get Composio OAuth redirect URL");
		return null;
	}
}
/**
* Orchestration: Transform Composio server to PrebuiltMCPServer format
* Coordinates authentication checks and account management
* Returns null if the server cannot be properly configured
*/
async function transformComposioServer(composioMcpServer, authenticatedAuthConfigIds, activeAccounts, staleAccounts, derivedUserId) {
	const isAuthenticated = composioMcpServer.authConfigIds.some((authConfigId) => authenticatedAuthConfigIds.has(authConfigId));
	let thirdPartyConnectAccountUrl;
	let connectedAccountId;
	let authScheme;
	if (!isAuthenticated) {
		const redirectUrl = await ensureComposioAccount(composioMcpServer, derivedUserId, staleAccounts);
		if (!redirectUrl) return null;
		thirdPartyConnectAccountUrl = redirectUrl;
	} else {
		const matchingAccount = activeAccounts.find((account) => composioMcpServer.authConfigIds.includes(account.authConfig.id));
		connectedAccountId = matchingAccount?.id;
		authScheme = matchingAccount?.state?.authScheme;
	}
	return transformComposioServerData(composioMcpServer, isAuthenticated, composioMcpServer.MCPUrl, thirdPartyConnectAccountUrl, connectedAccountId, authScheme);
}
/**
* Fetch Composio MCP servers for the catalog
*/
async function fetchComposioServers() {
	if (!process.env.COMPOSIO_API_KEY) {
		logger$1.info("COMPOSIO_API_KEY not configured, skipping Composio servers");
		return [];
	}
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured, returning empty list");
		return [];
	}
	try {
		return (await composioInstance.mcp.list({
			limit: 100,
			toolkits: [],
			page: 1,
			authConfigs: []
		}))?.items.map((server) => transformComposioServerData(server, false, server.MCPUrl, void 0)) ?? [];
	} catch (error) {
		logger$1.error({ error }, "Failed to fetch Composio servers");
		return [];
	}
}
/**
* Fetch a single Composio MCP server by URL and return its details with auth status
* @param credentialScope - 'project' uses tenant||project, 'user' uses actual userId
* @param userId - The actual user ID (required if credentialScope is 'user')
*/
async function fetchSingleComposioServer(tenantId, projectId, mcpServerUrl, credentialScope = "project", userId) {
	if (!process.env.COMPOSIO_API_KEY) {
		logger$1.debug("COMPOSIO_API_KEY not configured");
		return null;
	}
	const composioUserId = getComposioUserId(tenantId, projectId, credentialScope, userId);
	const composioInstance = getComposioInstance();
	if (!composioInstance) {
		logger$1.info("Composio not configured, returning null");
		return null;
	}
	try {
		const serverId = extractComposioServerId(mcpServerUrl);
		if (!serverId) {
			logger$1.error({ mcpServerUrl }, "Could not extract Composio server ID from URL");
			return null;
		}
		const composioMcpServer = await composioInstance.mcp.get(serverId);
		const userConnectedAccounts = await composioInstance.connectedAccounts.list({
			userIds: [composioUserId],
			statuses: [
				"ACTIVE",
				"INITIATED",
				"EXPIRED"
			]
		});
		const activeAccounts = userConnectedAccounts?.items.filter((account) => account.status === "ACTIVE");
		const staleAccounts = userConnectedAccounts?.items.filter((account) => account.status === "INITIATED" || account.status === "EXPIRED");
		return await transformComposioServer(composioMcpServer, new Set(activeAccounts?.map((account) => account.authConfig.id) ?? []), activeAccounts ?? [], staleAccounts ?? [], composioUserId);
	} catch (error) {
		logger$1.error({
			error,
			mcpServerUrl
		}, "Failed to fetch single Composio server");
		return null;
	}
}

//#endregion
export { configureComposioMCPServer, deleteComposioConnectedAccount, extractComposioServerId, fetchComposioServers, fetchSingleComposioServer, getComposioInstance, getComposioOAuthRedirectUrl, getComposioUserId, isComposioMCPServerAuthenticated };