/**
 * @bytelyst/field-encrypt — AES-256-GCM primitives
 *
 * Low-level encrypt/decrypt using Node.js native crypto.
 * All higher-level APIs delegate to these functions.
 */

import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
import type { EncryptedField } from './types.js';

const ALGORITHM = 'aes-256-gcm';
const IV_BYTES = 12;
const KEY_BYTES = 32;

/**
 * Encrypt a plaintext string with AES-256-GCM.
 *
 * @param plaintext - UTF-8 string to encrypt
 * @param key       - 32-byte AES key
 * @param dekId     - DEK identifier stored in the output
 * @param aad       - Optional additional authenticated data (e.g., userId + context)
 * @returns EncryptedField object ready for Cosmos/SQLite storage
 */
export function encryptField(
  plaintext: string,
  key: Buffer,
  dekId: string,
  aad?: string
): EncryptedField {
  if (key.length !== KEY_BYTES) {
    throw new Error(`AES-256-GCM requires a ${KEY_BYTES}-byte key, got ${key.length}`);
  }

  const iv = randomBytes(IV_BYTES);
  const cipher = createCipheriv(ALGORITHM, key, iv);

  if (aad) {
    cipher.setAAD(Buffer.from(aad, 'utf8'));
  }

  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag();

  return {
    __encrypted: true,
    v: 1,
    alg: 'aes-256-gcm',
    ct: encrypted,
    iv: iv.toString('hex'),
    tag: authTag.toString('hex'),
    dekId,
  };
}

/**
 * Decrypt an EncryptedField back to plaintext.
 *
 * @param field - EncryptedField object
 * @param key   - 32-byte AES key (must match the key used to encrypt)
 * @param aad   - Optional AAD (must match the AAD used during encryption)
 * @returns Decrypted UTF-8 string
 * @throws Error if authentication tag verification fails (tampered data)
 */
export function decryptField(field: EncryptedField, key: Buffer, aad?: string): string {
  if (key.length !== KEY_BYTES) {
    throw new Error(`AES-256-GCM requires a ${KEY_BYTES}-byte key, got ${key.length}`);
  }

  const iv = Buffer.from(field.iv, 'hex');
  const authTag = Buffer.from(field.tag, 'hex');
  const decipher = createDecipheriv(ALGORITHM, key, iv);

  decipher.setAuthTag(authTag);

  if (aad) {
    decipher.setAAD(Buffer.from(aad, 'utf8'));
  }

  let decrypted = decipher.update(field.ct, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

/** Generate a random 32-byte AES-256 key. */
export function generateAesKey(): Buffer {
  return randomBytes(KEY_BYTES);
}
