/**
 * Application-layer AES-256-GCM encryption for sensitive DB fields.
 *
 * Encrypted format stored in DB:
 *   enc:<base64(iv)>:<base64(authTag)>:<base64(ciphertext)>
 *
 * Set DB_ENCRYPTION_KEY to a 64-char hex string (32 bytes):
 *   export DB_ENCRYPTION_KEY=$(openssl rand -hex 32)
 *
 * If DB_ENCRYPTION_KEY is not set, values are stored as plaintext (with warning).
 */

import { createCipheriv, createDecipheriv, randomBytes } from "crypto";

const ALGORITHM   = "aes-256-gcm";
const IV_LENGTH   = 12;   // 96-bit IV recommended for GCM
const TAG_LENGTH  = 16;   // 128-bit auth tag
const ENC_PREFIX  = "enc:";

let _key = null;
let _warned = false;

function getKey() {
	if (_key) return _key;
	const hex = process.env.DB_ENCRYPTION_KEY;
	if (!hex) {
		if (!_warned) {
			console.warn("[crypto] WARNING: DB_ENCRYPTION_KEY is not set. WireGuard private keys are stored as PLAINTEXT in the database.");
			_warned = true;
		}
		return null;
	}
	if (hex.length !== 64) {
		throw new Error("DB_ENCRYPTION_KEY must be a 64-character hex string (32 bytes). Generate with: openssl rand -hex 32");
	}
	_key = Buffer.from(hex, "hex");
	return _key;
}

/**
 * Encrypt a plaintext string.
 * Returns the encrypted string with "enc:" prefix, or the original value if no key is set.
 * @param {string} plaintext
 * @returns {string}
 */
export function encrypt(plaintext) {
	if (!plaintext) return plaintext;
	// Already encrypted — idempotent
	if (plaintext.startsWith(ENC_PREFIX)) return plaintext;

	const key = getKey();
	if (!key) return plaintext; // passthrough if no key configured

	const iv = randomBytes(IV_LENGTH);
	const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });

	const encrypted = Buffer.concat([
		cipher.update(plaintext, "utf8"),
		cipher.final(),
	]);
	const tag = cipher.getAuthTag();

	return `${ENC_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`;
}

/**
 * Decrypt an encrypted string.
 * Returns plaintext, or the original value if it is not encrypted.
 * @param {string} value
 * @returns {string}
 */
export function decrypt(value) {
	if (!value) return value;
	// Not encrypted — return as-is (backward compat with plaintext rows)
	if (!value.startsWith(ENC_PREFIX)) return value;

	const key = getKey();
	if (!key) {
		throw new Error("DB_ENCRYPTION_KEY is required to decrypt WireGuard keys but is not set.");
	}

	const parts = value.slice(ENC_PREFIX.length).split(":");
	if (parts.length !== 3) {
		throw new Error("Invalid encrypted value format in database.");
	}

	const [ivB64, tagB64, dataB64] = parts;
	const iv       = Buffer.from(ivB64,  "base64");
	const tag      = Buffer.from(tagB64, "base64");
	const data     = Buffer.from(dataB64,"base64");

	const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
	decipher.setAuthTag(tag);

	return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
}
