/**
 * Encryption Utility
 *
 * Encrypts/decrypts sensitive API keys using AES-256-GCM.
 * Used for secure storage of third-party API credentials.
 */

import crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16; // 16 bytes for AES-GCM IV
const AUTH_TAG_LENGTH = 16; // 16 bytes for GCM auth tag

// Fixed default key for development (64 hex chars = 32 bytes)
// IMPORTANT: Set ENCRYPTION_KEY env var for production!
const DEV_ENCRYPTION_KEY = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2';

/**
 * Get encryption key from environment
 * Key should be a 32-byte (64 hex character) string
 * Falls back to a development key (NOT for production!)
 */
function getEncryptionKey(): Buffer {
  const key = process.env.ENCRYPTION_KEY;
  if (!key) {
    if (process.env.NODE_ENV === 'production') {
      throw new Error('ENCRYPTION_KEY environment variable must be set in production');
    }
    console.warn('[Encryption] WARNING: ENCRYPTION_KEY is not set — using the built-in development key.');
    console.warn('[Encryption] Any credential encrypted under a DIFFERENT ENCRYPTION_KEY (e.g. on another');
    console.warn('[Encryption] environment sharing this database) CANNOT be decrypted here. Set the SAME');
    console.warn('[Encryption] 64-hex-char ENCRYPTION_KEY in every environment that shares the database.');
    return Buffer.from(DEV_ENCRYPTION_KEY, 'hex');
  }
  // AES-256 needs a 32-byte key = 64 hex characters. Validate up-front so a
  // mis-formatted key (e.g. base64 or 32 chars) gives a clear error instead of a
  // cryptic "Invalid key length" / "unable to authenticate data" later.
  if (!/^[0-9a-fA-F]{64}$/.test(key)) {
    throw new Error(
      'ENCRYPTION_KEY must be exactly 64 hexadecimal characters (32 bytes). ' +
      'Generate one with:  openssl rand -hex 32'
    );
  }
  return Buffer.from(key, 'hex');
}

/**
 * Encrypt plaintext using AES-256-GCM
 * Returns ciphertext, IV, and auth tag
 */
export function encrypt(plaintext: string): { ciphertext: string; iv: string; authTag: string } {
  const key = getEncryptionKey();
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);

  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  return {
    ciphertext: encrypted,
    iv: iv.toString('hex'),
    authTag: authTag.toString('hex'),
  };
}

/**
 * Decrypt ciphertext using AES-256-GCM
 * Requires ciphertext, IV, and auth tag
 */
export function decrypt(ciphertext: string, iv: string, authTag: string): string {
  const key = getEncryptionKey();
  try {
    const decipher = crypto.createDecipheriv(
      ALGORITHM,
      key,
      Buffer.from(iv, 'hex')
    );

    decipher.setAuthTag(Buffer.from(authTag, 'hex'));

    let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  } catch (error: any) {
    // An AES-GCM auth-tag failure ("Unsupported state or unable to authenticate
    // data") almost always means this value was encrypted under a DIFFERENT
    // ENCRYPTION_KEY than the one configured here.
    throw new Error(
      'Decryption failed — the stored value was encrypted with a different ENCRYPTION_KEY. ' +
      'Ensure ENCRYPTION_KEY is identical across every environment that shares this database, ' +
      'then re-save the affected credentials in Super Admin → Settings. ' +
      `(${error?.message || error})`
    );
  }
}

/**
 * Encrypt API key for storage
 * Returns combined encrypted string and IV
 * Format: encrypted = "ciphertext:authTag"
 */
export function encryptApiKey(apiKey: string): { encrypted: string; iv: string } {
  const { ciphertext, iv, authTag } = encrypt(apiKey);
  // Combine ciphertext and authTag for storage
  return {
    encrypted: `${ciphertext}:${authTag}`,
    iv,
  };
}

/**
 * Decrypt API key from storage
 * Expects combined format: "ciphertext:authTag"
 */
export function decryptApiKey(encrypted: string, iv: string): string {
  const [ciphertext, authTag] = encrypted.split(':');
  if (!ciphertext || !authTag) {
    throw new Error('Invalid encrypted API key format');
  }
  return decrypt(ciphertext, iv, authTag);
}

/**
 * Generate a new encryption key for .env setup
 * Run this once to generate a key for the ENCRYPTION_KEY env variable
 */
export function generateEncryptionKey(): string {
  return crypto.randomBytes(32).toString('hex');
}

/**
 * Verify encryption is working correctly
 */
export function verifyEncryption(): boolean {
  try {
    const testValue = 'test-api-key-12345';
    const { encrypted, iv } = encryptApiKey(testValue);
    const decrypted = decryptApiKey(encrypted, iv);
    return decrypted === testValue;
  } catch (error) {
    console.error('Encryption verification failed:', error);
    return false;
  }
}