import { CredentialStoreType } from "../types/utility.js";
import { getLogger } from "../utils/logger.js";
import { z } from "@hono/zod-openapi";
import { Nango } from "@nangohq/node";

//#region src/credential-stores/nango-store.ts
const logger = getLogger("nango-credential-store");
const CredentialKeySchema = z.object({
	connectionId: z.string().min(1, "connectionId must be a non-empty string"),
	providerConfigKey: z.string().min(1, "providerConfigKey must be a non-empty string"),
	integrationDisplayName: z.string().nullish()
});
/**
* Parse and validate a Nango credential key
* @param key - JSON string containing connectionId and providerConfigKey
* @returns Parsed credential key or null if invalid
*/
function parseCredentialKey(key) {
	try {
		const parsed = JSON.parse(key);
		return CredentialKeySchema.parse(parsed);
	} catch (error) {
		logger.warn({
			key: key.substring(0, 100),
			error: error instanceof Error ? error.message : "Unknown error"
		}, "Failed to parse credential key");
		return null;
	}
}
const SUPPORTED_AUTH_MODES = [
	"APP",
	"API_KEY",
	"BASIC",
	"CUSTOM",
	"JWT",
	"NONE",
	"OAUTH1",
	"OAUTH2",
	"OAUTH2_CC",
	"TBA"
];
function isSupportedAuthMode(mode) {
	return SUPPORTED_AUTH_MODES.includes(mode);
}
/**
* Nango-based CredentialStore that fetches OAuth credentials from Nango API
* Uses connectionId and providerConfigKey from metadata to fetch live credentials
*/
var NangoCredentialStore = class {
	id;
	type = CredentialStoreType.nango;
	nangoConfig;
	nangoClient;
	constructor(id, config) {
		this.id = id;
		this.nangoConfig = config;
		this.nangoClient = new Nango({
			secretKey: this.nangoConfig.secretKey,
			host: this.nangoConfig.apiUrl
		});
	}
	getAccessToken(credentials) {
		const { type } = credentials;
		if (!isSupportedAuthMode(type)) return null;
		const extractAccessTokenForBearerType = (tokenString) => {
			if (tokenString && typeof tokenString === "string") {
				try {
					const parsedToken = JSON.parse(tokenString);
					if (parsedToken.access_token && typeof parsedToken.access_token === "string") return parsedToken.access_token;
				} catch {}
				return tokenString;
			}
		};
		switch (type) {
			case "API_KEY": return { token: extractAccessTokenForBearerType(credentials.apiKey || credentials.api_key) };
			case "APP": return { token: extractAccessTokenForBearerType(credentials.accessToken || credentials.access_token) };
			case "BASIC": return {
				username: credentials.username,
				token: credentials.password
			};
			case "CUSTOM": return credentials.raw;
			case "JWT": return { token: extractAccessTokenForBearerType(credentials.token) };
			case "OAUTH1": return {
				token: credentials.oauth_token,
				token_secret: credentials.oauth_token_secret
			};
			case "OAUTH2": return {
				token: extractAccessTokenForBearerType(credentials.access_token),
				refresh_token: credentials.refresh_token,
				expiresAt: credentials.expires_at
			};
			case "OAUTH2_CC": return {
				token: extractAccessTokenForBearerType(credentials.token),
				client_certificate: credentials.client_certificate,
				client_id: credentials.client_id,
				client_private_key: credentials.client_private_key,
				client_secret: credentials.client_secret
			};
			case "TBA": return {
				token: credentials.token_id,
				token_secret: credentials.token_secret
			};
			default: return null;
		}
	}
	sanitizeMetadata(metadata) {
		if (!metadata || typeof metadata !== "object") return {};
		const result = {};
		for (const [key, value] of Object.entries(metadata)) {
			if (typeof key !== "string") continue;
			if (typeof value === "string") result[key] = value;
		}
		return result;
	}
	/**
	* Fetch a specific Nango integration
	*/
	async fetchNangoIntegration(uniqueKey) {
		try {
			const integration = (await this.nangoClient.getIntegration({ uniqueKey }, { include: ["credentials"] })).data;
			let areCredentialsSet = false;
			if (integration.credentials?.type === "OAUTH2" || integration.credentials?.type === "OAUTH1" || integration.credentials?.type === "TBA") areCredentialsSet = !!(integration.credentials?.client_id && integration.credentials?.client_secret);
			else if (integration.credentials?.type === "APP") areCredentialsSet = !!(integration.credentials?.app_id && integration.credentials?.app_link);
			else areCredentialsSet = true;
			const { credentials: _credentials, ...integrationWithoutCredentials } = integration;
			return {
				...integrationWithoutCredentials,
				areCredentialsSet
			};
		} catch (error) {
			if (error && typeof error === "object" && "status" in error && error.status === 404) return null;
			logger.error({
				error: error instanceof Error ? error.message : "Unknown error",
				uniqueKey
			}, `Failed to fetch integration ${uniqueKey}`);
			return null;
		}
	}
	/**
	* Create an API key credential by setting up Nango integration and importing the connection
	*/
	async createNangoApiKeyConnection({ uniqueKey, displayName, apiKeyToSet, metadata }) {
		const provider = "private-api-bearer";
		try {
			let integration;
			try {
				integration = (await this.nangoClient.createIntegration({
					provider,
					unique_key: uniqueKey,
					display_name: displayName
				})).data;
			} catch (error) {
				const existingIntegration = await this.fetchNangoIntegration(uniqueKey);
				if (existingIntegration) integration = existingIntegration;
				else console.log(`Integration creation failed for unexpected reasons`, error);
			}
			if (!integration) throw new Error(`Integration '${uniqueKey}' not found`);
			const importConnectionUrl = `${process.env.NANGO_SERVER_URL || "https://api.nango.dev"}/connections`;
			const credentials = {
				type: "API_KEY",
				apiKey: apiKeyToSet
			};
			const body = {
				provider_config_key: integration.unique_key,
				connection_id: uniqueKey,
				metadata,
				credentials
			};
			const response = await fetch(importConnectionUrl, {
				method: "POST",
				headers: {
					Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`,
					"Content-Type": "application/json"
				},
				body: JSON.stringify(body)
			});
			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(`Failed to import connection: HTTP ${response.status} - ${response.statusText}. Response: ${errorText}`);
			}
		} catch (error) {
			logger.error({
				error: error instanceof Error ? error.message : "Unknown error",
				displayName
			}, `Unexpected error creating API key credential '${displayName}'`);
			throw new Error(`Failed to create API key credential '${displayName}': ${error instanceof Error ? error.message : "Unknown error"}`);
		}
	}
	/**
	* Fetch credentials from Nango API using connection information
	* @param connectionId - The connection ID for the Nango connection
	* @param providerConfigKey - The provider config key for the Nango connection
	* @returns The credential data or null if the credentials are not found
	*/
	async fetchCredentialsFromNango({ connectionId, providerConfigKey }) {
		try {
			const nangoConnection = await this.nangoClient.getConnection(providerConfigKey, connectionId);
			return {
				...this.getAccessToken(nangoConnection.credentials) ?? {},
				connectionId,
				providerConfigKey,
				provider: nangoConnection.provider || "unknown",
				secretKey: this.nangoConfig.secretKey,
				metadata: this.sanitizeMetadata(nangoConnection.metadata ?? {})
			};
		} catch (error) {
			logger.error({
				error: error instanceof Error ? error.message : "Unknown error",
				connectionId,
				providerConfigKey
			}, "Error fetching credentials from Nango");
			return null;
		}
	}
	/**
	* Get credentials by key - implements CredentialStore interface
	* Key format: JSON string with connectionId and providerConfigKey
	*/
	async get(key) {
		try {
			const parsedKey = parseCredentialKey(key);
			if (!parsedKey) return null;
			const { connectionId, providerConfigKey } = parsedKey;
			const credentials = await this.fetchCredentialsFromNango({
				connectionId,
				providerConfigKey
			});
			if (!credentials) return null;
			return JSON.stringify(credentials);
		} catch (error) {
			logger.error({
				storeId: this.id,
				key: key.substring(0, 100),
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Error getting credentials from Nango");
			return null;
		}
	}
	/**
	* Set credentials - this is used to save bearer auth
	* Key format: JSON string with connectionId and providerConfigKey
	*/
	async set(key, value, metadata = {}) {
		const parsedKey = parseCredentialKey(key);
		if (!parsedKey) throw new Error(`Invalid credential key: ${key}`);
		const { connectionId, providerConfigKey, integrationDisplayName } = parsedKey;
		await this.createNangoApiKeyConnection({
			uniqueKey: connectionId,
			displayName: integrationDisplayName ?? providerConfigKey,
			apiKeyToSet: value,
			metadata
		});
	}
	/**
	* Check if credentials exist by attempting to fetch them
	*/
	async has(key) {
		try {
			return await this.get(key) !== null;
		} catch (error) {
			logger.error({
				error: error instanceof Error ? error.message : "Unknown error",
				key
			}, "Error checking credentials existence");
			return false;
		}
	}
	/**
	* Delete credentials - not supported for Nango (revoke through Nango dashboard)
	*/
	async delete(key) {
		try {
			const parsedKey = parseCredentialKey(key);
			if (!parsedKey) return false;
			const { connectionId, providerConfigKey } = parsedKey;
			await this.nangoClient.deleteConnection(providerConfigKey, connectionId);
			return true;
		} catch (error) {
			logger.error({
				storeId: this.id,
				key: key.substring(0, 100),
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Error deleting credentials from Nango");
			return false;
		}
	}
	/**
	* Check if the credential store is available and functional
	*/
	async checkAvailability() {
		if (!this.nangoConfig.secretKey) return {
			available: false,
			reason: "Nango secret key not configured"
		};
		if (this.nangoConfig.secretKey.includes("mock") || this.nangoConfig.secretKey === "your_nango_secret_key") return {
			available: false,
			reason: "Nango secret key appears to be a placeholder or mock value"
		};
		return { available: true };
	}
};
/**
* Factory function to create NangoCredentialStore
* Automatically reads NANGO_SECRET_KEY from environment and validates it
*/
function createNangoCredentialStore(id, config) {
	const nangoSecretKey = config?.secretKey || process.env.NANGO_SECRET_KEY;
	if (!nangoSecretKey || nangoSecretKey === "your_nango_secret_key" || nangoSecretKey.includes("mock")) throw new Error("NANGO_SECRET_KEY environment variable is required and must be a real Nango secret key (not mock/placeholder)");
	return new NangoCredentialStore(id, {
		apiUrl: "https://api.nango.dev",
		...config,
		secretKey: nangoSecretKey
	});
}

//#endregion
export { NangoCredentialStore, createNangoCredentialStore };