/**
 * cli-key.ts — `sf key` subcommand handler.
 *
 * Provides surgical read/write access to ~/.sf/agent/auth.json without ever
 * touching the file directly. All mutations go through AuthStorage so the
 * file-locking and chmod-600 invariants are always respected.
 *
 * SOURCE OF TRUTH: ~/.sf/agent/auth.json is the authoritative credential store
 * for SF at runtime. SF does NOT read API keys from environment variables at
 * runtime — env vars are only used during initial one-time provider setup.
 * After that, auth.json is the sole source of truth.
 *
 * Key rotation is always explicit: use `sf key set <provider> <new-key>`.
 * There is no auto-sync from env vars by design — implicit rotation undermines
 * the explicit-rotation model and makes key provenance hard to audit.
 *
 * Subcommands:
 *   sf key set    <provider> <api-key>  — add/replace the stored key (authoritative write)
 *   sf key get    <provider>            — show masked key (last 4 chars only)
 *   sf key remove <provider> [--yes]    — remove credential (confirms unless --yes)
 *   sf key list                         — list all providers with cred status
 */

import * as readline from "node:readline";
import { AuthStorage } from "@singularity-forge/coding-agent";

// ---------------------------------------------------------------------------
// Inline copy of the provider registry, sourced from key-manager.js.
// We reproduce only the fields we need so this module has no runtime
// dependency on the SF extension bundle (which may not be loaded here).
// ---------------------------------------------------------------------------

interface ProviderEntry {
	id: string;
	label: string;
	envVar?: string;
}

// Keep in sync with src/resources/extensions/sf/key-manager.js PROVIDER_REGISTRY.
const PROVIDER_REGISTRY: ProviderEntry[] = [
	{ id: "anthropic", label: "Anthropic (Claude)", envVar: "ANTHROPIC_API_KEY" },
	{ id: "openai", label: "OpenAI", envVar: "OPENAI_API_KEY" },
	{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)" },
	{ id: "google-gemini-cli", label: "Google Code Assist" },
	{ id: "groq", label: "Groq", envVar: "GROQ_API_KEY" },
	{ id: "openrouter", label: "OpenRouter", envVar: "OPENROUTER_API_KEY" },
	{ id: "mistral", label: "Mistral", envVar: "MISTRAL_API_KEY" },
	{ id: "minimax", label: "MiniMax", envVar: "MINIMAX_API_KEY" },
	{ id: "kimi-coding", label: "Kimi Coding", envVar: "KIMI_API_KEY" },
	{ id: "zai", label: "ZAI", envVar: "ZAI_API_KEY" },
	{ id: "xiaomi", label: "Xiaomi MiMo", envVar: "XIAOMI_API_KEY" },
	{ id: "ollama-cloud", label: "Ollama Cloud", envVar: "OLLAMA_CLOUD_API_KEY" },
	{ id: "opencode", label: "OpenCode Zen", envVar: "OPENCODE_API_KEY" },
	{ id: "opencode-go", label: "OpenCode Go", envVar: "OPENCODE_GO_API_KEY" },
	{
		id: "custom-openai",
		label: "Custom (OpenAI-compat)",
		envVar: "CUSTOM_OPENAI_API_KEY",
	},
	{ id: "cerebras", label: "Cerebras", envVar: "CEREBRAS_API_KEY" },
	{
		id: "azure-openai-responses",
		label: "Azure OpenAI",
		envVar: "AZURE_OPENAI_API_KEY",
	},
	{
		id: "alibaba-coding-plan",
		label: "Alibaba Coding Plan",
		envVar: "ALIBABA_API_KEY",
	},
	{
		id: "alibaba-dashscope",
		label: "Alibaba DashScope",
		envVar: "DASHSCOPE_API_KEY",
	},
	{ id: "context7", label: "Context7 Docs", envVar: "CONTEXT7_API_KEY" },
	{ id: "jina", label: "Jina Page Extract", envVar: "JINA_API_KEY" },
	{ id: "tavily", label: "Tavily Search", envVar: "TAVILY_API_KEY" },
	{ id: "brave", label: "Brave Search", envVar: "BRAVE_API_KEY" },
	{ id: "serper", label: "Serper", envVar: "SERPER_API_KEY" },
	{ id: "exa", label: "Exa Search", envVar: "EXA_API_KEY" },
	{ id: "discord_bot", label: "Discord Bot", envVar: "DISCORD_BOT_TOKEN" },
	{ id: "slack_bot", label: "Slack Bot", envVar: "SLACK_BOT_TOKEN" },
	{ id: "telegram_bot", label: "Telegram Bot", envVar: "TELEGRAM_BOT_TOKEN" },
];

const PROVIDER_IDS = new Set(PROVIDER_REGISTRY.map((p) => p.id));

// ---------------------------------------------------------------------------
// Masking
// ---------------------------------------------------------------------------

/**
 * Mask a key for display: show only the last 4 characters.
 * Never reveals the full key or any prefix beyond "***".
 */
export function maskKeyLast4(key: string): string {
	if (!key) return "(empty)";
	if (key.length <= 4) return "***" + key;
	return "***" + key.slice(-4);
}

// ---------------------------------------------------------------------------
// Provider validation
// ---------------------------------------------------------------------------

function validProvidersList(): string {
	return PROVIDER_REGISTRY.map((p) => p.id).join(", ");
}

// ---------------------------------------------------------------------------
// Prompt helper
// ---------------------------------------------------------------------------

async function promptConfirm(question: string): Promise<boolean> {
	const rl = readline.createInterface({
		input: process.stdin,
		output: process.stderr,
	});
	return new Promise((resolve) => {
		rl.question(question + " (y/N): ", (answer) => {
			rl.close();
			resolve(answer.trim().toLowerCase() === "y");
		});
	});
}

// ---------------------------------------------------------------------------
// Subcommand handlers
// ---------------------------------------------------------------------------

/** sf key set <provider> <api-key> */
function handleKeySet(
	auth: AuthStorage,
	provider: string,
	newKey: string,
): number {
	if (!PROVIDER_IDS.has(provider)) {
		process.stderr.write(
			`[sf key] Unknown provider: "${provider}".\n` +
				`[sf key] Valid providers: ${validProvidersList()}\n`,
		);
		return 1;
	}

	if (!newKey || !/^\S+$/.test(newKey)) {
		process.stderr.write(
			`[sf key] Key must be a non-empty string with no whitespace.\n`,
		);
		return 1;
	}

	// Read old key for reporting (mask only)
	const existing = auth
		.getCredentialsForProvider(provider)
		.find((c) => c.type === "api_key");
	const oldDisplay =
		existing?.type === "api_key" ? maskKeyLast4(existing.key) : "(none)";

	// Replace: remove old api_key entries for this provider, then add new one.
	// We preserve OAuth credentials by reconstructing without api_key entries.
	const allCreds = auth.getCredentialsForProvider(provider);
	const oauthCreds = allCreds.filter((c) => c.type === "oauth");
	auth.remove(provider);
	for (const c of oauthCreds) {
		auth.set(provider, c);
	}
	auth.set(provider, { type: "api_key", key: newKey });

	const newDisplay = maskKeyLast4(newKey);
	process.stdout.write(
		`Updated ${provider} credential (was: ${oldDisplay}, now: ${newDisplay})\n`,
	);
	return 0;
}

/** sf key get <provider> */
function handleKeyGet(auth: AuthStorage, provider: string): number {
	if (!PROVIDER_IDS.has(provider)) {
		process.stderr.write(
			`[sf key] Unknown provider: "${provider}".\n` +
				`[sf key] Valid providers: ${validProvidersList()}\n`,
		);
		return 1;
	}

	const creds = auth.getCredentialsForProvider(provider);
	const apiKeyCred = creds.find((c) => c.type === "api_key");
	if (!apiKeyCred || apiKeyCred.type !== "api_key" || !apiKeyCred.key) {
		process.stdout.write(`${provider}: no credential set\n`);
	} else {
		process.stdout.write(
			`${provider}: api_key set (${maskKeyLast4(apiKeyCred.key)})\n`,
		);
	}
	return 0;
}

/** sf key remove <provider> [--yes] */
async function handleKeyRemove(
	auth: AuthStorage,
	provider: string,
	yes: boolean,
): Promise<number> {
	if (!PROVIDER_IDS.has(provider)) {
		process.stderr.write(
			`[sf key] Unknown provider: "${provider}".\n` +
				`[sf key] Valid providers: ${validProvidersList()}\n`,
		);
		return 1;
	}

	const creds = auth.getCredentialsForProvider(provider);
	if (creds.length === 0) {
		process.stderr.write(`[sf key] No credential stored for "${provider}".\n`);
		return 1;
	}

	if (!yes) {
		const confirmed = await promptConfirm(
			`Are you sure? This will remove the credential for "${provider}" and require re-adding it to use this provider.`,
		);
		if (!confirmed) {
			process.stdout.write("Aborted.\n");
			return 0;
		}
	}

	auth.remove(provider);
	process.stdout.write(`Removed credential for ${provider}.\n`);
	return 0;
}

/** sf key list */
function handleKeyList(auth: AuthStorage): number {
	process.stdout.write("Provider credentials:\n");
	for (const entry of PROVIDER_REGISTRY) {
		const creds = auth.getCredentialsForProvider(entry.id);
		const apiKeyCred = creds.find((c) => c.type === "api_key");
		if (apiKeyCred?.type === "api_key" && apiKeyCred.key) {
			process.stdout.write(
				`  ${entry.id.padEnd(30)} set    (${maskKeyLast4(apiKeyCred.key)})\n`,
			);
		} else if (creds.some((c) => c.type === "oauth")) {
			process.stdout.write(`  ${entry.id.padEnd(30)} oauth\n`);
		} else {
			process.stdout.write(`  ${entry.id.padEnd(30)} not set\n`);
		}
	}
	return 0;
}

/** sf key sync — rotate stored keys to match differing env vars */
function handleKeySync(auth: AuthStorage): number {
	let rotated = 0;
	for (const entry of PROVIDER_REGISTRY) {
		if (!entry.envVar) continue;
		const envValue = process.env[entry.envVar];
		if (!envValue) continue;

		const creds = auth.getCredentialsForProvider(entry.id);
		const stored = creds.find((c) => c.type === "api_key");
		const storedKey = stored?.type === "api_key" ? stored.key : undefined;

		if (storedKey === envValue) continue;

		// Env value present and differs from stored (or not stored yet) — rotate.
		const oauthCreds = creds.filter((c) => c.type === "oauth");
		auth.remove(entry.id);
		for (const c of oauthCreds) {
			auth.set(entry.id, c);
		}
		auth.set(entry.id, { type: "api_key", key: envValue });
		process.stdout.write(
			`Rotated ${entry.id} from env ${entry.envVar} (${maskKeyLast4(envValue)})\n`,
		);
		rotated++;
	}

	if (rotated === 0) {
		process.stdout.write(
			"No changes — all env vars match stored credentials (or no env vars set).\n",
		);
	} else {
		process.stdout.write(
			`Synced ${rotated} provider${rotated === 1 ? "" : "s"}.\n`,
		);
	}
	return 0;
}

// ---------------------------------------------------------------------------
// Main dispatcher
// ---------------------------------------------------------------------------

export async function runKeyCommand(
	authFilePath: string,
	argv: string[],
): Promise<number> {
	// argv = the raw process.argv starting from the token after "key"
	// e.g. ["set", "xiaomi", "tp-abc123"] or ["list"]
	const sub = argv[0];
	const auth = AuthStorage.create(authFilePath);

	if (!sub || sub === "--help" || sub === "-h") {
		printKeyHelp();
		return 0;
	}

	switch (sub) {
		case "set": {
			const provider = argv[1];
			const key = argv[2];
			if (!provider || key === undefined) {
				process.stderr.write("Usage: sf key set <provider> <api-key>\n");
				return 1;
			}
			return handleKeySet(auth, provider, key);
		}

		case "get": {
			const provider = argv[1];
			if (!provider) {
				process.stderr.write("Usage: sf key get <provider>\n");
				return 1;
			}
			return handleKeyGet(auth, provider);
		}

		case "remove":
		case "rm": {
			const provider = argv[1];
			if (!provider) {
				process.stderr.write("Usage: sf key remove <provider> [--yes]\n");
				return 1;
			}
			const yes = argv.includes("--yes") || argv.includes("-y");
			return await handleKeyRemove(auth, provider, yes);
		}

		case "list":
		case "ls": {
			return handleKeyList(auth);
		}

		case "sync": {
			return handleKeySync(auth);
		}

		default: {
			process.stderr.write(`[sf key] Unknown subcommand: "${sub}"\n`);
			process.stderr.write(
				"Commands: set <provider> <key>, get <provider>, remove <provider>, list, sync\n",
			);
			return 1;
		}
	}
}

function printKeyHelp(): void {
	process.stdout.write(
		[
			"Usage: sf key <subcommand> [args]",
			"",
			"Manage provider API keys stored in ~/.sf/agent/auth.json.",
			"",
			"Credentials for SF runtime live in ~/.sf/agent/auth.json.",
			"SF does NOT read API keys from environment variables at runtime —",
			"env is only used during initial provider setup. Use `sf key set`",
			"to rotate keys. auth.json is always the authoritative source.",
			"",
			"Subcommands:",
			"  set <provider> <api-key>   Set or rotate the API key for a provider",
			"  get <provider>             Show key status (last 4 chars only, never full key)",
			"  remove <provider> [--yes]  Remove the credential (prompts unless --yes)",
			"  list                       List all providers with credential status",
			"  sync                       Rotate stored keys to match differing env vars",
			"",
			"Examples:",
			"  sf key set xiaomi tp-abc123          Set the xiaomi API key",
			"  sf key get xiaomi                    Show masked key for xiaomi",
			"  sf key remove xiaomi                 Remove xiaomi credential (confirms)",
			"  sf key remove xiaomi --yes           Remove without prompting",
			"  sf key list                          List all providers",
			"  sf key sync                          Sync keys from environment variables",
			"",
			"Security:",
			"  - Keys are never echoed in full. Only the last 4 characters are shown.",
			"  - All writes go through AuthStorage (file-locked, chmod 600).",
			"  - sf key remove always asks for confirmation unless --yes is passed.",
		].join("\n") + "\n",
	);
}
