/*
 * vault1984 — crypto test suite
 * Runs in both QuickJS (CLI) and browser.
 *
 * CLI:   vault1984-cli test-roundtrip
 * Web:   open browser console, paste: fetch('/app/test_crypto.js').then(r=>r.text()).then(eval)
 *        or load as <script> in a test page
 *
 * All tests must produce identical results on both platforms.
 * If any test fails on one but passes on the other, the shared crypto is broken.
 */

(function() {
    var R = [];      /* results */
    var FAIL = false;

    function pass(name) { R.push('PASS  ' + name); }
    function fail(name, detail) { R.push('FAIL  ' + name + (detail ? ' — ' + detail : '')); FAIL = true; }

    /* Wrap a test that might be sync (QuickJS) or async (browser) */
    function resolve(val, cb) {
        if (val && typeof val.then === 'function') {
            val.then(function(r) { cb(r); }).catch(function(e) { cb(null, e); });
        } else {
            cb(val);
        }
    }

    /* Safe call: catches sync throws (QuickJS) and async rejections (browser) */
    function safe(fn, cb) {
        try {
            var result = fn();
            if (result && typeof result.then === 'function') {
                result.then(function(r) { cb(r); }).catch(function(e) { cb(null, e); });
            } else {
                cb(result);
            }
        } catch(e) {
            cb(null, e);
        }
    }

    /* --- Key fixtures --- */
    var K8  = new Uint8Array([11,22,33,44,55,66,77,88]);
    var K16 = new Uint8Array([11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]);
    var K32 = new Uint8Array([11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116,
                              201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216]);
    /* K16 is the first 16 bytes of K32 — by design (truncation model) */
    /* K8 is the first 8 bytes of K16 — by design */

    var WRONG_K16 = new Uint8Array([255,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]);

    var tests = [];

    /* --- Test 1: L1 (8-byte) roundtrip --- */
    tests.push(function(done) {
        var name = 'L1 (8B) encrypt/decrypt roundtrip';
        resolve(vault1984.crypto.encrypt_field(K8, 'username', 'johanj'), function(ct, err) {
            if (err) { fail(name, err.message); done(); return; }
            resolve(vault1984.crypto.decrypt_field(K8, 'username', ct), function(pt, err2) {
                if (err2) fail(name, err2.message);
                else if (pt === 'johanj') pass(name);
                else fail(name, 'got "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 2: L2 (16-byte) roundtrip --- */
    tests.push(function(done) {
        var name = 'L2 (16B) encrypt/decrypt roundtrip';
        resolve(vault1984.crypto.encrypt_field(K16, 'password', 's3cret!P@ss'), function(ct, err) {
            if (err) { fail(name, err.message); done(); return; }
            resolve(vault1984.crypto.decrypt_field(K16, 'password', ct), function(pt, err2) {
                if (err2) fail(name, err2.message);
                else if (pt === 's3cret!P@ss') pass(name);
                else fail(name, 'got "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 3: L3 (32-byte) roundtrip --- */
    tests.push(function(done) {
        var name = 'L3 (32B) encrypt/decrypt roundtrip';
        resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
            if (err) { fail(name, err.message); done(); return; }
            resolve(vault1984.crypto.decrypt_field(K32, 'passport', ct), function(pt, err2) {
                if (err2) fail(name, err2.message);
                else if (pt === 'NL12345678') pass(name);
                else fail(name, 'got "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 4: L2 key cannot decrypt L3 ciphertext --- */
    tests.push(function(done) {
        var name = 'L2 key rejects L3 ciphertext';
        resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
            if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
            safe(function() { return vault1984.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
                if (err2) pass(name);
                else fail(name, 'L2 key decrypted L3 data to "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 5: L3 key cannot decrypt L2 ciphertext --- */
    tests.push(function(done) {
        var name = 'L3 key rejects L2 ciphertext';
        resolve(vault1984.crypto.encrypt_field(K16, 'password', 'secret'), function(ct, err) {
            if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
            safe(function() { return vault1984.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
                if (err2) pass(name);
                else fail(name, 'L3 key decrypted L2 data to "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 6: Wrong key rejection --- */
    tests.push(function(done) {
        var name = 'wrong key rejected';
        resolve(vault1984.crypto.encrypt_field(K16, 'secret', 'value'), function(ct, err) {
            if (err) { fail(name, 'encrypt failed'); done(); return; }
            safe(function() { return vault1984.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
                if (err2) pass(name);
                else fail(name, 'wrong key decrypted to "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 7: Wrong label rejection --- */
    tests.push(function(done) {
        var name = 'wrong label rejected';
        resolve(vault1984.crypto.encrypt_field(K16, 'labelA', 'value'), function(ct, err) {
            if (err) { fail(name, 'encrypt failed'); done(); return; }
            safe(function() { return vault1984.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
                if (err2) pass(name);
                else fail(name, 'wrong label decrypted to "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 8: Empty string roundtrip --- */
    tests.push(function(done) {
        var name = 'empty string roundtrip';
        resolve(vault1984.crypto.encrypt_field(K16, 'empty', ''), function(ct, err) {
            if (err) { fail(name, err.message); done(); return; }
            resolve(vault1984.crypto.decrypt_field(K16, 'empty', ct), function(pt, err2) {
                if (err2) fail(name, err2.message);
                else if (pt === '') pass(name);
                else fail(name, 'got "' + pt + '"');
                done();
            });
        });
    });

    /* --- Test 9: Unicode roundtrip --- */
    tests.push(function(done) {
        var name = 'unicode roundtrip';
        var unicode = 'pässwörd 密码 🔑';
        resolve(vault1984.crypto.encrypt_field(K16, 'intl', unicode), function(ct, err) {
            if (err) { fail(name, err.message); done(); return; }
            resolve(vault1984.crypto.decrypt_field(K16, 'intl', ct), function(pt, err2) {
                if (err2) fail(name, err2.message);
                else if (pt === unicode) pass(name);
                else fail(name, 'mismatch');
                done();
            });
        });
    });

    /* --- Test 10: L1 key (8B) produces different ciphertext than L2 key (16B) --- */
    tests.push(function(done) {
        var name = 'L1 and L2 keys produce different ciphertexts';
        /* K8 doubled = [11,22,33,44,55,66,77,88,11,22,33,44,55,66,77,88]
         * K16       = [11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]
         * These are different keys after normalization, so HKDF produces different field keys.
         * L1 ciphertext must NOT be decryptable with L2 key. */
        resolve(vault1984.crypto.encrypt_field(K8, 'field', 'test'), function(ct1, err) {
            if (err) { fail(name, 'L1 encrypt: ' + err.message); done(); return; }
            safe(function() { return vault1984.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
                if (err2) pass(name);
                else fail(name, 'L2 key decrypted L1 data');
                done();
            });
        });
    });

    /* --- Test 11: TOTP generation (RFC 6238 test vector) --- */
    tests.push(function(done) {
        var name = 'TOTP RFC 6238 test vector';
        var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);
        resolve(result, function(code, err) {
            if (err) fail(name, err.message);
            else if (code === '287082') pass(name);
            else fail(name, 'got ' + code + ', expected 287082');
            done();
        });
    });

    /* --- Test 12: TOTP second test vector (time=1111111109) --- */
    tests.push(function(done) {
        var name = 'TOTP RFC 6238 test vector #2';
        var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 1111111109, 30, 8);
        resolve(result, function(code, err) {
            if (err) fail(name, err.message);
            else if (code === '07081804') pass(name);
            else fail(name, 'got ' + code + ', expected 07081804');
            done();
        });
    });

    /* --- Runner --- */
    function run(idx) {
        if (idx >= tests.length) {
            /* Summary */
            var summary = '\n' + R.join('\n') + '\n\n' +
                (FAIL ? 'FAILED' : 'ALL ' + tests.length + ' TESTS PASSED');

            if (typeof globalThis.document !== 'undefined') {
                /* Browser: log to console */
                console.log(summary);
                R.forEach(function(line) {
                    if (line.indexOf('FAIL') === 0) console.error(line);
                    else console.log(line);
                });
            }

            /* Return result string (for QuickJS eval or browser display) */
            globalThis._v1984_test_result = summary;
            return;
        }
        tests[idx](function() { run(idx + 1); });
    }

    run(0);

    /* For sync environments (QuickJS), result is available immediately */
    if (typeof globalThis._v1984_test_result !== 'undefined') {
        /* Used by CLI eval */
    }
})();

/* Return result for jsbridge_eval */
globalThis._v1984_test_result || 'RUNNING (async — check console)';
