/**
 * AES-256-GCM field-level encryption for sensitive database fields.
 *
 * Encrypted values are stored as: `enc:v{version}:{iv}:{authTag}:{ciphertext}`
 * All segments are hex-encoded. The `enc:` prefix lets us distinguish encrypted
 * from plaintext values (important for migration).
 */

import crypto from 'node:crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12; // 96-bit IV recommended for GCM
const AUTH_TAG_LENGTH = 16;
const PREFIX = 'enc:';

export interface FieldEncryptionConfig {
  /** 32-byte hex-encoded encryption key (64 hex chars). */
  key: string;
  /** Key version for rotation support. Defaults to 1. */
  keyVersion?: number;
}

function deriveKeyBuffer(hexKey: string): Buffer {
  const buf = Buffer.from(hexKey, 'hex');
  if (buf.length !== 32) {
    throw new Error(
      `KYC_ENCRYPTION_KEY must be exactly 32 bytes (64 hex chars), got ${buf.length} bytes`,
    );
  }
  return buf;
}

/**
 * Encrypts a JSON-serializable value using AES-256-GCM.
 * Returns a prefixed string suitable for database storage.
 */
export function encryptField(value: unknown, config: FieldEncryptionConfig): string {
  const keyBuf = deriveKeyBuffer(config.key);
  const version = config.keyVersion ?? 1;
  const plaintext = JSON.stringify(value);

  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, keyBuf, iv, { authTagLength: AUTH_TAG_LENGTH });

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

  return `${PREFIX}v${version}:${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
}

/**
 * Decrypts a previously encrypted field value.
 * Returns the original JSON-parsed value, or the raw input if it is not encrypted
 * (supports transparent migration from plaintext).
 */
export function decryptField(stored: unknown, config: FieldEncryptionConfig): unknown {
  if (typeof stored !== 'string' || !stored.startsWith(PREFIX)) {
    // Not encrypted — return as-is (plaintext migration path)
    return stored;
  }

  const keyBuf = deriveKeyBuffer(config.key);
  // Format: enc:v{version}:{iv}:{authTag}:{ciphertext}
  const parts = stored.slice(PREFIX.length).split(':');
  if (parts.length !== 4) {
    throw new Error('Malformed encrypted field: expected 4 segments after prefix');
  }

  const [_versionTag, ivHex, authTagHex, ciphertextHex] = parts;
  const iv = Buffer.from(ivHex!, 'hex');
  const authTag = Buffer.from(authTagHex!, 'hex');
  const ciphertext = Buffer.from(ciphertextHex!, 'hex');

  const decipher = crypto.createDecipheriv(ALGORITHM, keyBuf, iv, {
    authTagLength: AUTH_TAG_LENGTH,
  });
  decipher.setAuthTag(authTag);

  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  return JSON.parse(decrypted.toString('utf8'));
}

/** Check if a stored value is already encrypted. */
export function isEncrypted(value: unknown): boolean {
  return typeof value === 'string' && value.startsWith(PREFIX);
}
