/**
 * Shared server-side encryption utilities for rSpace at-rest encryption.
 *
 * Uses AES-256-GCM with keys derived from ENCRYPTION_SECRET via HMAC-SHA256.
 * File format: [4-byte magic "rSEN"][4-byte keyId length][keyId bytes][12-byte IV][ciphertext+tag]
 */

// Magic bytes to identify encrypted files: "rSEN" (rSpace ENcrypted)
export const ENCRYPTED_MAGIC = new Uint8Array([0x72, 0x53, 0x45, 0x4e]);

/**
 * Derive an AES-256-GCM key from a key identifier using HMAC-SHA256.
 * Uses ENCRYPTION_SECRET env var as the HMAC key.
 */
export async function deriveSpaceKey(keyId: string): Promise<CryptoKey> {
	const serverSecret = process.env.ENCRYPTION_SECRET;
	if (!serverSecret) {
		throw new Error("ENCRYPTION_SECRET environment variable is required");
	}
	const encoder = new TextEncoder();
	const keyMaterial = await crypto.subtle.importKey(
		"raw",
		encoder.encode(serverSecret),
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"],
	);
	const derived = await crypto.subtle.sign(
		"HMAC",
		keyMaterial,
		encoder.encode(keyId),
	);
	return crypto.subtle.importKey(
		"raw",
		derived,
		{ name: "AES-GCM", length: 256 },
		false,
		["encrypt", "decrypt"],
	);
}

/**
 * Encrypt binary data using AES-256-GCM.
 * Returns: 12-byte IV + ciphertext + 16-byte auth tag (concatenated).
 */
export async function encryptBinary(
	data: Uint8Array,
	key: CryptoKey,
): Promise<Uint8Array> {
	const iv = crypto.getRandomValues(new Uint8Array(12));
	const plainBuf = new ArrayBuffer(data.byteLength);
	new Uint8Array(plainBuf).set(data);
	const ciphertext = await crypto.subtle.encrypt(
		{ name: "AES-GCM", iv },
		key,
		plainBuf,
	);
	const result = new Uint8Array(12 + ciphertext.byteLength);
	result.set(iv, 0);
	result.set(new Uint8Array(ciphertext), 12);
	return result;
}

/**
 * Decrypt binary data encrypted with AES-256-GCM.
 * Expects: 12-byte IV + ciphertext + 16-byte auth tag.
 */
export async function decryptBinary(
	data: Uint8Array,
	key: CryptoKey,
): Promise<Uint8Array> {
	const iv = data.slice(0, 12);
	const ciphertext = data.slice(12);
	const plaintext = await crypto.subtle.decrypt(
		{ name: "AES-GCM", iv },
		key,
		ciphertext,
	);
	return new Uint8Array(plaintext);
}

/**
 * Check if a byte array starts with the rSEN magic bytes.
 */
export function isEncryptedFile(bytes: Uint8Array): boolean {
	return (
		bytes.length >= ENCRYPTED_MAGIC.length &&
		bytes[0] === ENCRYPTED_MAGIC[0] &&
		bytes[1] === ENCRYPTED_MAGIC[1] &&
		bytes[2] === ENCRYPTED_MAGIC[2] &&
		bytes[3] === ENCRYPTED_MAGIC[3]
	);
}

/**
 * Pack an encrypted payload with the rSEN header.
 * Format: [4-byte magic][4-byte keyId length][keyId UTF-8 bytes][ciphertext]
 */
export function packEncrypted(keyId: string, ciphertext: Uint8Array): Uint8Array {
	const keyIdBytes = new TextEncoder().encode(keyId);
	const packed = new Uint8Array(8 + keyIdBytes.length + ciphertext.length);
	packed.set(ENCRYPTED_MAGIC, 0);
	new DataView(packed.buffer).setUint32(4, keyIdBytes.length);
	packed.set(keyIdBytes, 8);
	packed.set(ciphertext, 8 + keyIdBytes.length);
	return packed;
}

/**
 * Unpack an rSEN-encrypted file into keyId and ciphertext components.
 * Assumes caller already checked isEncryptedFile().
 */
export function unpackEncrypted(data: Uint8Array): {
	keyId: string;
	ciphertext: Uint8Array;
} {
	const keyIdLen = new DataView(
		data.buffer,
		data.byteOffset + 4,
		4,
	).getUint32(0);
	const keyId = new TextDecoder().decode(data.slice(8, 8 + keyIdLen));
	const ciphertext = data.slice(8 + keyIdLen);
	return { keyId, ciphertext };
}
