import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
import { ENCRYPTION_KEY_RAW } from '@server/auth/encryption-key';
import { JWT_SECRET_RAW } from '@server/auth/jwt-secret';
import { childLogger } from '@server/logger';

const log = childLogger('utils.crypto');
const ALGORITHM = 'aes-256-gcm';

function deriveKey(secret: string, salt: string): Buffer {
  return scryptSync(secret, salt, 32);
}

function getEncryptionKey(): Buffer {
  if (ENCRYPTION_KEY_RAW) {
    const buf = Buffer.from(ENCRYPTION_KEY_RAW, 'base64');
    if (buf.length === 32) {
      return buf;
    }
    return deriveKey(ENCRYPTION_KEY_RAW, 'restroagent-enc-salt');
  }
  return deriveKey(JWT_SECRET_RAW, 'restroagent-enc-salt');
}

function getLegacyKey(): Buffer {
  return deriveKey(JWT_SECRET_RAW, 'restroagent-salt');
}

export function encrypt(text: string): string {
  const key = getEncryptionKey();
  const iv = randomBytes(16);
  const cipher = createCipheriv(ALGORITHM, key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag().toString('hex');
  return `${iv.toString('hex')}:${authTag}:${encrypted}`;
}

export function decrypt(encryptedText: string): string {
  const [ivHex, authTagHex, encrypted] = encryptedText.split(':');
  if (!ivHex || !authTagHex || !encrypted) {
    throw new Error('Invalid encrypted format');
  }
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  if (authTag.length !== 16) {
    throw new Error('Invalid auth tag length — expected 16 bytes');
  }

  const tryDecrypt = (key: Buffer): string => {
    const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: 16 });
    decipher.setAuthTag(authTag);
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  };

  try {
    return tryDecrypt(getEncryptionKey());
  } catch {
    try {
      const result = tryDecrypt(getLegacyKey());
      log.warn(
        'Decrypted a value using the legacy JWT-derived key. ' +
        'Re-save this credential to migrate it to the new ENCRYPTION_KEY.'
      );
      return result;
    } catch {
      throw new Error('Decryption failed with both the current ENCRYPTION_KEY and the legacy JWT-derived key');
    }
  }
}
