/**
 * auditor-totp-aad smoke — guards the 2026-06-01 AAD binding on
 * TOTP secret encryption.
 *
 * Auditor finding: TOTP secrets were encrypted at rest (since the
 * 2026-05-23 AUDIT-3 fix) but the encryption was NOT bound to the
 * user row via AAD. An attacker who could move ciphertext across
 * rows would have a valid envelope on the wrong user.
 *
 * Fix: TwoFactorService now passes AAD = `user:<id>:totp` to encrypt
 * and decryptIfNeeded. The crypto module's enc_v2/enc_v3 envelopes
 * include the AAD in GCM auth-tag verification — decrypt throws if
 * the AAD doesn't match.
 */
import './_lib';
import { ok, fail } from './_lib';
import { readFileSync, existsSync } from 'fs';
import path from 'path';

const ROOT = path.resolve(__dirname, '../..');
const read = (rel: string): string => readFileSync(path.join(ROOT, rel), 'utf8');

function assert(label: string, cond: boolean, detail: string): void {
    if (!cond) fail(label, detail);
    ok(label);
}

function main(): void {
    const svc = read('src/lib/services/two-factor.service.ts');

    assert('two-factor.service: totpAad helper defined',
        /function totpAad\(userId:\s*number\)\s*:\s*string/.test(svc),
        'two-factor.service no longer defines the totpAad helper.');

    assert('two-factor.service: totpAad produces user:<id>:totp',
        /return `user:\$\{userId\}:totp`/.test(svc),
        'totpAad no longer produces the expected AAD shape `user:<id>:totp`.');

    // enable2FA encrypts with AAD.
    assert('two-factor.service: enable2FA passes AAD to encrypt',
        /encrypt\(secret,\s*totpAad\(userId\)\)/.test(svc),
        'enable2FA no longer encrypts with AAD — auditor T1C regression.');

    // getSecret decrypts with AAD.
    assert('two-factor.service: getSecret passes AAD to decryptIfNeeded',
        /decryptIfNeeded\(user\.totpSecret,\s*totpAad\(userId\)\)/.test(svc),
        'getSecret no longer decrypts with AAD.');

    // verifyCode signature requires userId.
    assert('two-factor.service: verifyCode signature includes userId',
        /verifyCode\(secret:\s*string,\s*code:\s*string,\s*userId:\s*number\)/.test(svc),
        'verifyCode no longer accepts userId — call sites lose the AAD-binding.');

    // All 4 production call sites pass a userId argument.
    const callers = [
        'src/app/api/auth/2fa-challenge/route.ts',
        'src/app/api/user/2fa/route.ts',
        'src/app/api/user/2fa/verify/route.ts',
    ];
    for (const file of callers) {
        const src = read(file);
        // Match: TwoFactorService.verifyCode(<any>, <any>, <any>) — three args.
        const calls = src.match(/TwoFactorService\.verifyCode\(([^)]*)\)/g) ?? [];
        for (const call of calls) {
            const argCount = call.split(',').length;
            if (argCount < 3) {
                fail('two-factor.service callers: every verifyCode call passes userId',
                    `${file} has a verifyCode call with ${argCount} args: ${call}`);
            }
        }
    }
    ok('two-factor.service callers: every verifyCode call passes userId');

    // Migration script exists.
    const migrationPath = 'scripts/maint/rebind-totp-secrets-aad.ts';
    assert('migration script: rebind-totp-secrets-aad.ts exists',
        existsSync(path.join(ROOT, migrationPath)),
        `${migrationPath} missing — operators can't upgrade existing enc_v1 rows.`);
    const migration = read(migrationPath);
    assert('migration script: dry-run by default, --apply to commit',
        /APPLY\s*=\s*process\.argv\.includes\(['"]--apply['"]\)/.test(migration),
        'Migration script lost its dry-run safety.');

    // Unit test coverage.
    const test = read('src/lib/services/__tests__/two-factor.service.test.ts');
    assert('test: auditor T1C describe block present',
        /AAD binding \(auditor T1C/.test(test),
        'Unit test for AAD binding is missing.');
    assert('test: cross-user AAD mismatch throws',
        /user:99:totp[\s\S]*?\.toThrow/.test(test),
        'Unit test no longer asserts cross-user AAD mismatch throws.');

    ok('auditor-totp-aad', 'TOTP secret encryption is now AAD-bound to user id');
}

main();
