<?php
/**
 * Application-layer encryption for sensitive mobile API payloads.
 * AES-256-GCM (IV prepended) + HKDF-SHA256 session key from the Bearer token (info: qdb-aes).
 * Legacy CBC envelopes are accepted for compatibility with older app builds.
 */

function qdb_sensitive_envelope(string $plainJson, string $tokenHex): array {
    $key = hkdf_session_key_from_token($tokenHex);
    return [
        'encrypted' => true,
        'alg'       => 'A256GCM',
        'payload'   => base64_encode(qdb_aes256_gcm_encrypt_bytes($plainJson, $key)),
    ];
}

function qdb_decrypt_sensitive_envelope(array $envelope, string $tokenHex): string {
    if (empty($envelope['encrypted']) || !isset($envelope['payload']) || !is_string($envelope['payload'])) {
        throw new InvalidArgumentException('Invalid encrypted envelope');
    }
    $raw = base64_decode($envelope['payload'], true);
    if ($raw === false) {
        throw new InvalidArgumentException('Invalid base64 payload');
    }
    $key = hkdf_session_key_from_token($tokenHex);
    $alg = isset($envelope['alg']) && is_string($envelope['alg']) ? $envelope['alg'] : 'A256CBC';
    return match ($alg) {
        'A256GCM' => qdb_aes256_gcm_decrypt_bytes($raw, $key),
        'A256CBC' => aes256_cbc_decrypt_bytes($raw, $key),
        default => throw new InvalidArgumentException('Unsupported encrypted envelope algorithm'),
    };
}

function qdb_aes256_gcm_encrypt_bytes(string $plain, string $key): string {
    $key = str_pad(substr($key, 0, 32), 32, "\0");
    $iv = random_bytes(12);
    $tag = '';
    $cipher = openssl_encrypt($plain, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
    if ($cipher === false || strlen($tag) !== 16) {
        throw new RuntimeException('openssl_encrypt failed');
    }
    return $iv . $cipher . $tag;
}

function qdb_aes256_gcm_decrypt_bytes(string $data, string $key): string {
    $ivLen = 12;
    $tagLen = 16;
    if (strlen($data) <= $ivLen + $tagLen) {
        throw new InvalidArgumentException('cipher too short');
    }
    $key = str_pad(substr($key, 0, 32), 32, "\0");
    $iv = substr($data, 0, $ivLen);
    $tag = substr($data, -$tagLen);
    $ct = substr($data, $ivLen, -$tagLen);
    $plain = openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
    if ($plain === false) {
        throw new InvalidArgumentException('openssl_decrypt failed');
    }
    return $plain;
}
