import crypto from 'crypto';

/**
 * Generate a secure salt from a user ID and a private key.
 * @param userId The unique, immutable identifier of the user.
 * @returns The generated salt.
 *
 * IMPORTANT: We use the userId instead of editable fields like username for salt generation.
 * This is crucial for maintaining consistent encryption/decryption capabilities:
 *
 * 1. Immutability: The userId doesn't change, unlike usernames which can be edited.
 *    If we used the username, changing it would result in a different salt,
 *    making it impossible to decrypt previously encrypted data.
 *
 * 2. Consistency: Using the userId ensures that the salt (and thus the derived encryption key)
 *    remains the same throughout the user's lifetime in the system.
 *
 * 3. Security: The userId is typically not known to the user, adding an extra layer of security.
 *
 * Example scenario:
 * - User signs up with username "ABC69" and is assigned userId "12345"
 * - We generate a salt and encrypt their private key using this salt
 * - Later, the user changes their username to "XYZ107"
 * - If we had used the username for salt generation, we'd no longer be able to decrypt
 *   their private key because the new salt would be different
 * - By using the unchanging userId, we maintain the ability to decrypt regardless of username changes
 *
 * This approach ensures that we can always decrypt the user's encrypted data,
 * even if they change their editable profile information.
 */
export function createSecureSalt(inputString: string, privateKey: string): string {
  if (!privateKey) {
    throw new Error('privateKey is not set in the environment variables');
  }

  // Combine the input string with the private key
  const combinedString = inputString + privateKey;

  // Create a SHA-256 hash of the combined string
  const hash = crypto.createHash('sha256').update(combinedString).digest('hex');

  return hash;
}

/**
 * Encrypt data using AES with the derived key from the salt.
 * @param data The data to encrypt.
 * @param salt The salt to use for key derivation.
 * @returns The encrypted data in base64 format with IV prepended.
 */
export function aesEncrypt(data: string, pwd: string, salt: string): string {
  // Derive a key from the salt using PBKDF2
  const key = crypto.pbkdf2Sync(pwd, salt, 100000, 32, 'sha256');

  // Generate a random initialization vector (IV)
  const iv = crypto.randomBytes(16);

  // Create AES cipher using the key and IV
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

  // Encrypt the data
  const encryptedBuffer = Buffer.concat([cipher.update(Buffer.from(data, 'utf8')), cipher.final()]);

  // Combine IV and encrypted data, then convert to base64
  return Buffer.concat([iv, encryptedBuffer]).toString('base64');
}

// Helper function to check if a string is base64 encoded
export function isBase64(str: string): boolean {
  try {
    return Buffer.from(str, 'base64').toString('base64') === str;
  } catch {
    return false;
  }
}

export function aesDecrypt(encryptedData: string, pwdHash: string, salt: string): string {
  // Derive the key from the salt using PBKDF2
  const key = crypto.pbkdf2Sync(pwdHash, salt, 100000, 32, 'sha256');

  let iv: Buffer;
  let encrypted: string;

  // Check if the data is in the old base64 format or the new hex format
  if (isBase64(encryptedData)) {
    // Old format: base64 encoded
    const buffer = Buffer.from(encryptedData, 'base64');
    iv = buffer.slice(0, 16);
    encrypted = buffer.slice(16).toString('hex');
  } else {
    // New format: hex encoded
    iv = Buffer.from(encryptedData.slice(0, 32), 'hex');
    encrypted = encryptedData.slice(32);
  }

  // Create AES decipher using the key and IV
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);

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

  return decrypted;
}

export function getSecret(
  id: string,
  encryptKey: string,
  publicOrganizationId: string,
  dataEncryptionKey: string,
): string | null {
  try {
    const pwdHash = createSecureSalt(id, publicOrganizationId);
    const salt = createSecureSalt(id, dataEncryptionKey);

    return aesDecrypt(encryptKey, pwdHash, salt);
  } catch (error) {
    console.error('Error in getSecret:', error);
    return null;
  }
}

export const createEncryptedValue = (
  privateKey: string,
  id: string,
  publicOrganizationId: string,
  dataEncryptionKey: string,
) => {
  const pwdHash = createSecureSalt(id, publicOrganizationId);
  const salt = createSecureSalt(id, dataEncryptionKey);
  return aesEncrypt(privateKey, pwdHash, salt);
};
