/**
 * Shared client-side AES-256-GCM helper for our OWN internal AJAX calls
 * (dashboard/demo polling endpoints). Matches the token format produced by
 * encryptPayload()/decryptPayload() in includes/functions.php:
 *
 *   base64url( iv(12 bytes) . authTag(16 bytes) . ciphertext )
 *
 * This is NOT used against the public documented API (api/qr.php,
 * api/verify_order.php) — those keep a plain-JSON contract for outside
 * integrations and are untouched.
 *
 * Honest note on what this does and doesn't protect against: the key
 * below lives in this file, so it's readable by anyone who opens devtools
 * on this site. That's unavoidable for any purely client-side scheme —
 * JS that runs in the browser can't hide a secret from that same browser.
 * What this DOES protect against: anything sitting between the browser
 * and server (proxies, logs, a compromised CDN, a browser extension
 * sniffing generic network traffic) sees only opaque ciphertext instead
 * of readable order IDs/amounts/plan IDs.
 */
const FAMAPI_PAYLOAD_KEY_B64 = 'LLaSBLd6+Lao+7OS02Qe/jTfb0th84L33JEhqEUSGrg=';

async function _famapiGetKey() {
    const raw = Uint8Array.from(atob(FAMAPI_PAYLOAD_KEY_B64), c => c.charCodeAt(0));
    return crypto.subtle.importKey('raw', raw, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
}

function _b64urlEncode(bytes) {
    let bin = '';
    for (const b of bytes) bin += String.fromCharCode(b);
    return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function _b64urlDecode(str) {
    str = str.replace(/-/g, '+').replace(/_/g, '/');
    while (str.length % 4) str += '=';
    const bin = atob(str);
    const bytes = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
    return bytes;
}

/** Encrypts a JS value into the same token format the PHP side expects. */
async function encryptPayload(value) {
    const key = await _famapiGetKey();
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const enc = new TextEncoder().encode(JSON.stringify(value));
    const cipherBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, enc);
    const cipherBytes = new Uint8Array(cipherBuf);
    // Web Crypto appends the 16-byte tag to the END of the ciphertext.
    // PHP's openssl_encrypt keeps tag separate, so we re-order to
    // iv . tag . ciphertext to match encryptPayload() in functions.php.
    const tag = cipherBytes.slice(cipherBytes.length - 16);
    const cipherText = cipherBytes.slice(0, cipherBytes.length - 16);
    const combined = new Uint8Array(iv.length + tag.length + cipherText.length);
    combined.set(iv, 0);
    combined.set(tag, iv.length);
    combined.set(cipherText, iv.length + tag.length);
    return _b64urlEncode(combined);
}

/** Decrypts a token produced by the PHP side's encryptPayload(). */
async function decryptPayload(token) {
    const key = await _famapiGetKey();
    const combined = _b64urlDecode(token);
    const iv = combined.slice(0, 12);
    const tag = combined.slice(12, 28);
    const cipherText = combined.slice(28);
    // Re-append tag to the end for Web Crypto's expected layout.
    const forWebCrypto = new Uint8Array(cipherText.length + tag.length);
    forWebCrypto.set(cipherText, 0);
    forWebCrypto.set(tag, cipherText.length);
    const plainBuf = await crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, forWebCrypto);
    return JSON.parse(new TextDecoder().decode(plainBuf));
}

/**
 * Convenience wrapper: POSTs an encrypted body to `url` and decrypts the
 * encrypted `{ "p": "<token>" }` response, returning the plain JS value.
 */
async function encryptedFetch(url, data) {
    const body = await encryptPayload(data || {});
    const res = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'text/plain' },
        body
    });
    const wrapped = await res.json();
    if (!wrapped || !wrapped.p) throw new Error('Malformed encrypted response');
    return decryptPayload(wrapped.p);
}
