<?php
// ============================================================
//  Email credential encryption
// ============================================================
//
//  IMAP/SMTP passwords need to be stored REVERSIBLY because we
//  decrypt them at runtime to authenticate against mail servers.
//
//  Algorithm: AES-256-GCM (authenticated encryption).
//  Falls back to libsodium if the openssl extension is missing.
//
//  Format of stored value:
//      "v1." + base64(iv || ciphertext || tag)
//
//  The v1 prefix is forward-compatibility: if we ever need to
//  rotate the algorithm we can detect old vs new values.
// ============================================================

require_once __DIR__ . '/config.php';

/**
 * Get the binary key from the hex-encoded constant.
 */
function email_crypto_key(): string {
    if (!defined('EMAIL_ENCRYPTION_KEY') || EMAIL_ENCRYPTION_KEY === '') {
        throw new RuntimeException('EMAIL_ENCRYPTION_KEY is not defined in config.php');
    }
    $key = @hex2bin(EMAIL_ENCRYPTION_KEY);
    if ($key === false || strlen($key) !== 32) {
        throw new RuntimeException('EMAIL_ENCRYPTION_KEY must be a 64-char hex string (32 bytes)');
    }
    return $key;
}

/**
 * Encrypt a plaintext string. Returns the storable string form.
 */
function email_encrypt(string $plaintext): string {
    $key = email_crypto_key();

    if (extension_loaded('openssl')) {
        $iv  = random_bytes(12);                           // 96-bit IV for GCM
        $tag = '';
        $ct  = openssl_encrypt(
            $plaintext, 'aes-256-gcm', $key,
            OPENSSL_RAW_DATA, $iv, $tag, '', 16
        );
        if ($ct === false) throw new RuntimeException('openssl_encrypt failed');
        return 'v1.' . base64_encode($iv . $ct . $tag);
    }

    // Fallback: libsodium
    if (function_exists('sodium_crypto_aead_aes256gcm_is_available')
        && sodium_crypto_aead_aes256gcm_is_available()) {
        $nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
        $ct    = sodium_crypto_aead_aes256gcm_encrypt($plaintext, '', $nonce, $key);
        return 'v1s.' . base64_encode($nonce . $ct);
    }

    throw new RuntimeException('Neither openssl nor sodium AES-GCM is available');
}

/**
 * Decrypt a stored ciphertext. Returns the original plaintext.
 * Throws on tamper / wrong key / corrupt input.
 */
function email_decrypt(string $stored): string {
    $key = email_crypto_key();

    if (str_starts_with($stored, 'v1.')) {
        $blob = base64_decode(substr($stored, 3), true);
        if ($blob === false || strlen($blob) < 12 + 16 + 1) {
            throw new RuntimeException('Encrypted value is corrupt');
        }
        $iv  = substr($blob, 0, 12);
        $tag = substr($blob, -16);
        $ct  = substr($blob, 12, -16);
        $pt  = openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
        if ($pt === false) throw new RuntimeException('Decryption failed (wrong key or tampered)');
        return $pt;
    }

    if (str_starts_with($stored, 'v1s.')) {
        $blob = base64_decode(substr($stored, 4), true);
        if ($blob === false || strlen($blob) < SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES + 16) {
            throw new RuntimeException('Encrypted value is corrupt');
        }
        $nlen  = SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES;
        $nonce = substr($blob, 0, $nlen);
        $ct    = substr($blob, $nlen);
        $pt    = sodium_crypto_aead_aes256gcm_decrypt($ct, '', $nonce, $key);
        if ($pt === false) throw new RuntimeException('Decryption failed (wrong key or tampered)');
        return $pt;
    }

    throw new RuntimeException('Unknown encryption envelope: ' . substr($stored, 0, 6));
}

/**
 * Convenience: re-encrypt only if the input looks like plaintext (no envelope prefix).
 * Used on save when the user might have left the password field blank to keep existing.
 */
function email_encrypt_if_needed(?string $value): ?string {
    if ($value === null || $value === '') return $value;
    if (str_starts_with($value, 'v1.') || str_starts_with($value, 'v1s.')) {
        return $value; // already encrypted
    }
    return email_encrypt($value);
}