/**
 * Sync the active Deja Vu API key into other ecosystem touchpoints.
 *
 * Why: the CLI canonical state is ~/.dejavu/config.json. MCP servers
 * (Claude Code plugin, Codex plugin) read VENICE_API_KEY from env or
 * their own config files. Without a sync, agent-mode bootstrap mints a
 * new key into config.json but the plugin's MCP keeps using the old
 * key from env — silent surprise.
 *
 * Design:
 *  - Update ONLY entries that already exist; never create new ones
 *  - Preserve surrounding content, formatting, other keys
 *  - Atomic writes (tmp + rename) so a crash mid-write doesn't corrupt
 *  - Idempotent — re-running with the same key is a no-op
 *
 * Targets:
 *  - ~/.claude/settings.json::env::VENICE_API_KEY (Claude Code env injection)
 *  - ~/.zshrc / ~/.bashrc `export VENICE_API_KEY="..."` lines
 *
 * Out of scope: Codex / Cursor MCP configs and the plugin's own
 * <plugin-dir>/.api_key file (plugin-managed, different schema).
 */

import fs from "node:fs";
import os from "node:os";
import path from "node:path";

const CLAUDE_SETTINGS = path.join(os.homedir(), ".claude", "settings.json");
const SHELL_RCS = [
	path.join(os.homedir(), ".zshrc"),
	path.join(os.homedir(), ".bashrc"),
	path.join(os.homedir(), ".bash_profile"),
];

// Use [ \t]* (not \s*) so a trailing newline at end-of-file is preserved
// when the VENICE_API_KEY export is the last line of the rc file.
const RC_LINE_RE =
	/^([ \t]*export[ \t]+VENICE_API_KEY[ \t]*=[ \t]*)(["']?)([^"'\n]*)(["']?)[ \t]*$/m;

export function syncApiKey(apiKey: string): string[] {
	if (!apiKey) return [];
	const updated: string[] = [];
	if (updateClaudeSettings(CLAUDE_SETTINGS, apiKey)) {
		updated.push(CLAUDE_SETTINGS);
	}
	for (const rc of SHELL_RCS) {
		if (updateShellRc(rc, apiKey)) updated.push(rc);
	}
	return updated;
}

/** @internal — exported for unit tests; consumers should use {@link syncApiKey}. */
export function updateClaudeSettings(
	filePath: string,
	apiKey: string,
): boolean {
	if (!fs.existsSync(filePath)) return false;
	let raw: string;
	let data: Record<string, unknown>;
	try {
		raw = fs.readFileSync(filePath, "utf-8");
		data = JSON.parse(raw);
	} catch {
		return false;
	}
	const env = data.env;
	if (!env || typeof env !== "object" || !("VENICE_API_KEY" in env)) {
		return false; // no existing entry — don't create one
	}
	const envObj = env as Record<string, string>;
	if (envObj.VENICE_API_KEY === apiKey) return false; // already in sync
	envObj.VENICE_API_KEY = apiKey;
	atomicWriteText(filePath, `${JSON.stringify(data, null, 2)}\n`);
	return true;
}

/** @internal — exported for unit tests; consumers should use {@link syncApiKey}. */
export function updateShellRc(filePath: string, apiKey: string): boolean {
	if (!fs.existsSync(filePath)) return false;
	let text: string;
	try {
		text = fs.readFileSync(filePath, "utf-8");
	} catch {
		return false;
	}
	const match = text.match(RC_LINE_RE);
	if (!match) return false; // no existing line
	if (match[3] === apiKey) return false;
	const newText = text.replace(
		RC_LINE_RE,
		(_full, prefix) => `${prefix}"${apiKey}"`,
	);
	atomicWriteText(filePath, newText);
	return true;
}

function atomicWriteText(filePath: string, content: string): void {
	const dir = path.dirname(filePath);
	const tmp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.tmp`);
	try {
		fs.writeFileSync(tmp, content, "utf-8");
		// Preserve permissions if original existed.
		if (fs.existsSync(filePath)) {
			try {
				const mode = fs.statSync(filePath).mode & 0o777;
				fs.chmodSync(tmp, mode);
			} catch {
				/* best-effort */
			}
		}
		fs.renameSync(tmp, filePath);
	} catch (err) {
		try {
			fs.unlinkSync(tmp);
		} catch {
			/* ignore */
		}
		throw err;
	}
}
