/**
 * Vault Credential Resolver — Provider Key Resolution with Vault Support
 *
 * Purpose: Resolve LLM provider credentials from multiple sources (env, vault, auth.json)
 * with unified fallback semantics. Enables secure storage of API keys in HashiCorp Vault.
 *
 * Consumer: doctor-providers.js, preferences-models.js when checking provider availability.
 * Also used by SF agent when actually making API calls with provider credentials.
 *
 * Resolution Chain:
 * 1. Environment variable (may contain vault:// URI)
 * 2. HashiCorp Vault (if URI present and vault available)
 * 3. auth.json (AuthStorage from coding-agent)
 * 4. Fallback: undefined (fail-open)
 *
 * Fail-Open Semantics:
 * - If vault unavailable: returns undefined (caller can try next source)
 * - If vault:// URI malformed: returns undefined
 * - If field not found in vault: returns undefined
 */

import { isVaultUri, resolveSecret } from "./vault-resolver.js";

/**
 * Check if the environment variable value might be a vault URI without resolving it.
 * Used for synchronous availability checks (e.g., doctor-providers.js).
 *
 * Returns true if the value looks like a vault:// URI, allowing callers to assume
 * the credential is available (though resolution may happen later asynchronously).
 */
export function couldBeVaultUri(value) {
	return typeof value === "string" && isVaultUri(value);
}

/**
 * Resolve a provider credential that may be a vault URI or plaintext env var.
 * Async because vault resolution requires HTTP calls.
 *
 * Fail-open behavior: if vault unavailable, returns the URI string itself.
 * Callers using the result must check the resolved value and handle errors.
 *
 * Purpose: Late-binding resolution of credentials for actual API calls,
 * avoiding blocking on vault during startup/doctor checks.
 *
 * Returns:
 * - { resolved: true, value: actualKey, source: 'vault'|'plaintext' } on success
 * - { resolved: false, value: uriOrKey, source: 'vault'|'plaintext', warning: string } if vault unavailable
 * - undefined if the input is null/undefined
 */
export async function resolveProviderCredential(envValue) {
	if (!envValue) {
		return undefined;
	}

	// Try vault URI
	if (isVaultUri(envValue)) {
		const result = await resolveSecret(envValue, { failOpen: true });
		return {
			resolved: result.resolved,
			value: result.value,
			source: result.source,
			warning: result.warning,
		};
	}

	// Plaintext or env var reference
	return {
		resolved: true,
		value: envValue,
		source: "plaintext",
	};
}

/**
 * Resolve multiple provider credentials (e.g., from a record of provider IDs to env var names).
 *
 * Returns a map of provider ID → resolved credential result.
 * Missing providers are omitted from the result.
 */
export async function resolveProviderCredentials(credentialMap) {
	if (!credentialMap || typeof credentialMap !== "object") {
		return {};
	}

	const resolved = {};
	for (const [providerId, envValue] of Object.entries(credentialMap)) {
		const result = await resolveProviderCredential(envValue);
		if (result) {
			resolved[providerId] = result;
		}
	}
	return resolved;
}

/**
 * Synchronous check: does an environment variable exist and is it non-empty?
 * Used for fast doctor checks.
 *
 * Returns true if the env var is set to any non-empty value (including vault:// URIs).
 * Actual resolution is deferred to async calls.
 */
export function hasProviderCredentialEnvVar(envVarName) {
	const value = process.env[envVarName];
	return !!value && value.trim().length > 0;
}

/**
 * Get the actual credential value or throw if unavailable in strict mode.
 *
 * Used when a provider credential is required to proceed (e.g., making an API call).
 * Ensures the value is safe to use.
 */
export function getCredentialValue(resolveResult, strictMode = false) {
	if (!resolveResult) {
		if (strictMode) {
			throw new Error("Provider credential not available");
		}
		return undefined;
	}

	if (resolveResult.resolved) {
		return resolveResult.value;
	}

	if (strictMode) {
		throw new Error(
			`Provider credential resolution failed: ${resolveResult.warning || "unknown error"}`,
		);
	}
	return undefined;
}

/**
 * Format diagnostic info for a resolved credential (e.g., in doctor output).
 * Masks the actual value for security.
 */
export function formatCredentialInfo(resolveResult, providerId) {
	if (!resolveResult) {
		return `${providerId}: not available`;
	}

	let sourceLabel = resolveResult.source;
	if (resolveResult.source === "vault") {
		sourceLabel = "Vault";
	} else if (resolveResult.source === "plaintext") {
		sourceLabel = "env var";
	}

	const valuePreview =
		resolveResult.value && resolveResult.value.length > 0
			? `[${resolveResult.value.length} chars]`
			: "[empty]";

	const warning = resolveResult.warning
		? ` (warning: ${resolveResult.warning})`
		: "";

	return `${providerId}: ${valuePreview} from ${sourceLabel}${warning}`;
}
