/**
 * Centralized Encryption Utility for CWE-311 Compliance
 * Provides AES-256-GCM encryption for sensitive data at rest
 * 
 * Security Features:
 * - AES-256-GCM authenticated encryption
 * - Unique IV per encryption operation
 * - HMAC authentication tags
 * - Key rotation support
 * - Secure key derivation from master secret
 */

const crypto = require('crypto');
const logger = require('./logger');

// Encryption configuration
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16;  // 128 bits for GCM
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;

/**
 * Get master encryption key from environment or generate default
 * SECURITY WARNING: Always set ENCRYPTION_MASTER_KEY in production!
 */
function getMasterKey() {
  const envKey = process.env.ENCRYPTION_MASTER_KEY;
  
  if (!envKey) {
    logger.warn('⚠️  ENCRYPTION_MASTER_KEY not set - using default (insecure for production)');
    // Use JWT_SECRET as fallback, but warn about it
    const fallbackKey = process.env.JWT_SECRET || 'default-insecure-key-change-in-production';
    return crypto.createHash('sha256').update(fallbackKey + '-encryption-v1').digest();
  }
  
  // Derive proper key from master secret
  return crypto.createHash('sha256').update(envKey).digest();
}

/**
 * Derive encryption key for specific purpose using HKDF-like approach
 * @param {String} purpose - Purpose identifier (e.g., 'settings', 'vpn', 'api-tokens')
 * @returns {Buffer} Derived encryption key
 */
function deriveKey(purpose) {
  const masterKey = getMasterKey();
  const info = Buffer.from(purpose + '-v1', 'utf8');
  
  return crypto.createHmac('sha256', masterKey)
    .update(info)
    .digest();
}

/**
 * Encrypt sensitive data with AES-256-GCM
 * @param {String} plaintext - Data to encrypt
 * @param {String} purpose - Purpose identifier for key derivation
 * @returns {String} Encrypted data in format: salt:iv:authTag:ciphertext (all hex encoded)
 */
function encrypt(plaintext, purpose = 'default') {
  try {
    if (!plaintext) {
      return null;
    }

    // Generate random salt and IV for this encryption operation
    const salt = crypto.randomBytes(SALT_LENGTH);
    const iv = crypto.randomBytes(IV_LENGTH);
    
    // Derive encryption key with salt for additional security
    const masterKey = getMasterKey();
    const derivedKey = crypto.pbkdf2Sync(
      masterKey,
      salt,
      100000, // iterations
      KEY_LENGTH,
      'sha256'
    );

    // Create cipher
    const cipher = crypto.createCipheriv(ALGORITHM, derivedKey, iv);
    
    // Encrypt
    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    // Get authentication tag (GCM provides authenticated encryption)
    const authTag = cipher.getAuthTag();

    // Return format: salt:iv:authTag:ciphertext
    return [
      salt.toString('hex'),
      iv.toString('hex'),
      authTag.toString('hex'),
      encrypted
    ].join(':');
    
  } catch (error) {
    logger.error('Encryption error:', { purpose, error: error.message });
    throw new Error('Failed to encrypt data');
  }
}

/**
 * Decrypt data encrypted with encrypt()
 * @param {String} encryptedData - Encrypted data in format: salt:iv:authTag:ciphertext
 * @param {String} purpose - Purpose identifier (must match encryption purpose)
 * @returns {String} Decrypted plaintext
 */
function decrypt(encryptedData, purpose = 'default') {
  try {
    if (!encryptedData) {
      return null;
    }

    // Parse encrypted data
    const parts = encryptedData.split(':');
    if (parts.length !== 4) {
      throw new Error('Invalid encrypted data format');
    }

    const salt = Buffer.from(parts[0], 'hex');
    const iv = Buffer.from(parts[1], 'hex');
    const authTag = Buffer.from(parts[2], 'hex');
    const encrypted = parts[3];

    // Validate lengths
    if (salt.length !== SALT_LENGTH || iv.length !== IV_LENGTH || authTag.length !== AUTH_TAG_LENGTH) {
      throw new Error('Invalid encrypted data structure');
    }

    // Derive the same key used for encryption
    const masterKey = getMasterKey();
    const derivedKey = crypto.pbkdf2Sync(
      masterKey,
      salt,
      100000,
      KEY_LENGTH,
      'sha256'
    );

    // Create decipher
    const decipher = crypto.createDecipheriv(ALGORITHM, derivedKey, iv);
    decipher.setAuthTag(authTag);

    // Decrypt
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
    
  } catch (error) {
    logger.error('Decryption error:', { purpose, error: error.message });
    throw new Error('Failed to decrypt data');
  }
}

/**
 * Encrypt sensitive settings value
 * Automatically detects if already encrypted
 */
function encryptSetting(value, key) {
  if (!value) return null;
  
  // Don't encrypt if already encrypted (starts with salt:iv:authTag:ciphertext format)
  if (typeof value === 'string' && value.split(':').length === 4) {
    const parts = value.split(':');
    if (parts[0].length === SALT_LENGTH * 2 && parts[1].length === IV_LENGTH * 2) {
      return value; // Already encrypted
    }
  }
  
  const plaintext = typeof value === 'string' ? value : JSON.stringify(value);
  return encrypt(plaintext, `setting:${key}`);
}

/**
 * Decrypt sensitive setting value
 */
function decryptSetting(encryptedValue, key) {
  if (!encryptedValue) return null;
  
  try {
    const decrypted = decrypt(encryptedValue, `setting:${key}`);
    
    // Try to parse as JSON if it looks like JSON
    if (decrypted && (decrypted.startsWith('{') || decrypted.startsWith('['))) {
      try {
        return JSON.parse(decrypted);
      } catch {
        return decrypted;
      }
    }
    
    return decrypted;
  } catch (error) {
    // If decryption fails, value might not be encrypted (migration scenario)
    logger.warn(`Failed to decrypt setting ${key}, returning as-is`);
    return encryptedValue;
  }
}

/**
 * Check if encryption key is properly configured
 */
function isEncryptionConfigured() {
  return !!process.env.ENCRYPTION_MASTER_KEY;
}

/**
 * Get encryption health status
 */
function getEncryptionStatus() {
  const configured = isEncryptionConfigured();
  
  return {
    configured,
    algorithm: ALGORITHM,
    keySize: KEY_LENGTH * 8, // bits
    status: configured ? 'secure' : 'default-key',
    warning: configured ? null : 'Using default encryption key - set ENCRYPTION_MASTER_KEY in production',
    recommendations: configured ? [] : [
      'Set ENCRYPTION_MASTER_KEY environment variable',
      'Use a strong random key (at least 32 characters)',
      'Store the key securely (e.g., Docker secrets, AWS Secrets Manager)'
    ]
  };
}

/**
 * Re-encrypt data with new master key (for key rotation)
 * @param {String} oldEncryptedData - Data encrypted with old key
 * @param {String} purpose - Purpose identifier
 * @param {String} oldMasterKey - Old master key (optional, uses current env if not provided)
 * @returns {String} Data re-encrypted with current master key
 */
function reEncrypt(oldEncryptedData, purpose = 'default', oldMasterKey = null) {
  try {
    // Temporarily swap master key if provided
    const originalKey = process.env.ENCRYPTION_MASTER_KEY;
    if (oldMasterKey) {
      process.env.ENCRYPTION_MASTER_KEY = oldMasterKey;
    }
    
    // Decrypt with old key
    const plaintext = decrypt(oldEncryptedData, purpose);
    
    // Restore original key
    if (oldMasterKey) {
      process.env.ENCRYPTION_MASTER_KEY = originalKey;
    }
    
    // Encrypt with current key
    return encrypt(plaintext, purpose);
    
  } catch (error) {
    logger.error('Re-encryption error:', { purpose, error: error.message });
    throw new Error('Failed to re-encrypt data');
  }
}

/**
 * Hash sensitive data for comparison (one-way, cannot be decrypted)
 * Use for data that needs to be compared but not retrieved (e.g., backup codes)
 */
function hashSensitiveData(data) {
  return crypto.createHash('sha256').update(data).digest('hex');
}

/**
 * Generate cryptographically secure random token
 */
function generateSecureToken(length = 32) {
  return crypto.randomBytes(length).toString('hex');
}

module.exports = {
  encrypt,
  decrypt,
  encryptSetting,
  decryptSetting,
  deriveKey,
  isEncryptionConfigured,
  getEncryptionStatus,
  reEncrypt,
  hashSensitiveData,
  generateSecureToken,
  
  // Export for legacy VPN compatibility
  encryptVPN: (data) => encrypt(JSON.stringify(data), 'vpn'),
  decryptVPN: (data) => JSON.parse(decrypt(data, 'vpn'))
};
