/**
 * Field-level encryption utilities using AES-256-GCM
 * Provides authenticated encryption for sensitive data
 */

const crypto = require('crypto');

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;  // 128 bits for GCM
const SALT_LENGTH = 32;
const TAG_LENGTH = 16;  // 128 bits authentication tag
const KEY_LENGTH = 32;  // 256 bits
const PBKDF2_ITERATIONS = 100000;

let masterKey = null;

/**
 * Initialize encryption with master key
 * @param {string} key - Master encryption key (hex string)
 */
function initEncryption(key) {
  if (!key || typeof key !== 'string') {
    throw new Error('Master encryption key is required');
  }
  
  // Key should be at least 64 hex characters (32 bytes)
  if (key.length < 64) {
    throw new Error('Master encryption key must be at least 64 hex characters (32 bytes)');
  }
  
  masterKey = Buffer.from(key.slice(0, 64), 'hex');
  console.log('✅ Encryption initialized with master key');
}

/**
 * Derive encryption key from master key and salt using PBKDF2
 * @param {Buffer} salt - Salt for key derivation
 * @returns {Buffer} Derived key
 */
function deriveKey(salt) {
  if (!masterKey) {
    throw new Error('Encryption not initialized. Call initEncryption() first.');
  }
  
  return crypto.pbkdf2Sync(masterKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256');
}

/**
 * Encrypt a string value
 * @param {string} plaintext - Value to encrypt
 * @returns {string} Encrypted value with format: salt:iv:tag:ciphertext (all hex encoded)
 */
function encrypt(plaintext) {
  if (!plaintext) {
    return '';
  }
  
  if (!masterKey) {
    throw new Error('Encryption not initialized. Call initEncryption() first.');
  }
  
  try {
    // Generate random salt and IV
    const salt = crypto.randomBytes(SALT_LENGTH);
    const iv = crypto.randomBytes(IV_LENGTH);
    
    // Derive key from master key and salt
    const key = deriveKey(salt);
    
    // Create cipher
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
    
    // Encrypt
    const encrypted = Buffer.concat([
      cipher.update(plaintext, 'utf8'),
      cipher.final()
    ]);
    
    // Get authentication tag
    const tag = cipher.getAuthTag();
    
    // Combine: salt:iv:tag:ciphertext
    return [
      salt.toString('hex'),
      iv.toString('hex'),
      tag.toString('hex'),
      encrypted.toString('hex')
    ].join(':');
  } catch (error) {
    console.error('Encryption error:', error);
    throw new Error('Failed to encrypt data');
  }
}

/**
 * Decrypt an encrypted string value
 * @param {string} ciphertext - Encrypted value with format: salt:iv:tag:ciphertext
 * @returns {string} Decrypted plaintext
 */
function decrypt(ciphertext) {
  if (!ciphertext) {
    return '';
  }
  
  if (!masterKey) {
    throw new Error('Encryption not initialized. Call initEncryption() first.');
  }
  
  try {
    // Split components
    const parts = ciphertext.split(':');
    if (parts.length !== 4) {
      throw new Error('Invalid encrypted data format');
    }
    
    const [saltHex, ivHex, tagHex, encryptedHex] = parts;
    
    // Convert from hex
    const salt = Buffer.from(saltHex, 'hex');
    const iv = Buffer.from(ivHex, 'hex');
    const tag = Buffer.from(tagHex, 'hex');
    const encrypted = Buffer.from(encryptedHex, 'hex');
    
    // Derive key from master key and salt
    const key = deriveKey(salt);
    
    // Create decipher
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    decipher.setAuthTag(tag);
    
    // Decrypt
    const decrypted = Buffer.concat([
      decipher.update(encrypted),
      decipher.final()
    ]);
    
    return decrypted.toString('utf8');
  } catch (error) {
    console.error('Decryption error:', error);
    throw new Error('Failed to decrypt data');
  }
}

/**
 * Hash a value using PBKDF2 (for tokens, not for encryption)
 * @param {string} value - Value to hash
 * @param {string} salt - Optional salt (hex string), will generate if not provided
 * @returns {Object} Object with hash and salt (both hex strings)
 */
function hashValue(value, salt = null) {
  if (!value) {
    throw new Error('Value is required for hashing');
  }
  
  const saltBuffer = salt ? Buffer.from(salt, 'hex') : crypto.randomBytes(SALT_LENGTH);
  const hash = crypto.pbkdf2Sync(value, saltBuffer, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256');
  
  return {
    hash: hash.toString('hex'),
    salt: saltBuffer.toString('hex')
  };
}

/**
 * Verify a hashed value
 * @param {string} value - Value to verify
 * @param {string} hash - Expected hash (hex string)
 * @param {string} salt - Salt used for hashing (hex string)
 * @returns {boolean} True if match
 */
function verifyHash(value, hash, salt) {
  if (!value || !hash || !salt) {
    return false;
  }
  
  try {
    const result = hashValue(value, salt);
    return crypto.timingSafeEqual(Buffer.from(result.hash, 'hex'), Buffer.from(hash, 'hex'));
  } catch (error) {
    return false;
  }
}

/**
 * Generate a secure random token
 * @param {number} bytes - Number of random bytes (default 32)
 * @returns {string} Random token (hex string)
 */
function generateToken(bytes = 32) {
  return crypto.randomBytes(bytes).toString('hex');
}

/**
 * Check if encryption is initialized
 * @returns {boolean}
 */
function isEncryptionInitialized() {
  return masterKey !== null;
}

module.exports = {
  initEncryption,
  encrypt,
  decrypt,
  hashValue,
  verifyHash,
  generateToken,
  isEncryptionInitialized
};
