/**
 * AUDIT-2 Tier-2 security bundle smoke — #2, #3, #6, #9.
 *
 * Static-only (offline-safe). Asserts that:
 *
 *   #2: ssrf-guard exposes the loopback/ULA tier and the strategies +
 *       pinned-fetch pass ALLOW_LOOPBACK_PROBES through to it.
 *   #3: http.strategy.test.ts contains the connect-IP pinning regression
 *       test (proves we can't silently drop options.hostname).
 *   #6: audit-chain helper has both v1 + v2 canonicalisation, the trigger
 *       migration uses length-prefixed CONCAT, and verifyChain accepts
 *       either scheme.
 *   #9: redactHighEntropy exists, the 4 outbound channels + outbox call
 *       it before slicing response bodies into error messages.
 */
import './_lib';
import { ok, fail } from './_lib';
import { readFileSync, existsSync } from 'fs';
import path from 'path';

const ROOT = path.resolve(__dirname, '../..');

function read(rel: string): string {
    const abs = path.join(ROOT, rel);
    if (!existsSync(abs)) fail(`read ${rel}`, `not found`);
    return readFileSync(abs, 'utf8');
}

function assertPresent(label: string, src: string, needle: string | RegExp): void {
    const re = typeof needle === 'string'
        ? new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
        : needle;
    if (!re.test(src)) fail(label, `missing pattern: ${String(needle)}`);
    ok(label);
}

function main(): void {
    // ============ #2: ALLOW_LOOPBACK_PROBES gate ============
    const ranges = read('src/lib/network-security/private-ranges.ts');
    assertPresent('audit2-2: private-ranges exports isLoopbackOrUla', ranges, /export function isLoopbackOrUla/);
    assertPresent('audit2-2: loopback tier blocks 127.0.0.0/8', ranges, /cidrV4\('127\.0\.0\.0\/8'\)/);
    assertPresent('audit2-2: loopback tier blocks ::1/128', ranges, /cidrV6\('::1\/128'\)/);
    assertPresent('audit2-2: loopback tier blocks fc00::/7', ranges, /cidrV6\('fc00::\/7'\)/);

    const guard = read('src/lib/network-security/ssrf-guard.ts');
    assertPresent('audit2-2: ssrf-guard imports isLoopbackOrUla', guard, /isLoopbackOrUla/);
    assertPresent('audit2-2: ResolveOptions.allowLoopback field', guard, /allowLoopback\?:\s*boolean/);

    const env = read('src/lib/env.ts');
    assertPresent('audit2-2: env declares ALLOW_LOOPBACK_PROBES', env, /ALLOW_LOOPBACK_PROBES:/);

    for (const strategyFile of [
        'src/lib/monitor-strategies/http.strategy.ts',
        'src/lib/monitor-strategies/tcp.strategy.ts',
        'src/lib/monitor-strategies/ping.strategy.ts',
    ]) {
        const src = read(strategyFile);
        assertPresent(`audit2-2: ${strategyFile} passes allowLoopback`, src, /allowLoopback:\s*env\.ALLOW_LOOPBACK_PROBES/);
    }

    const pinned = read('src/lib/network-security/pinned-fetch.ts');
    assertPresent('audit2-2: pinned-fetch reads ALLOW_LOOPBACK_PROBES from env', pinned, /readAllowLoopbackFromEnv/);

    const envExample = read('.env.example');
    assertPresent('audit2-2: .env.example documents ALLOW_LOOPBACK_PROBES', envExample, /ALLOW_LOOPBACK_PROBES=/);

    // ============ #3: connect-IP regression test ============
    const httpTest = read('src/lib/monitor-strategies/__tests__/http.strategy.test.ts');
    assertPresent('audit2-3: connect-IP regression describe block', httpTest,
        /AUDIT-2 #3: connect-IP pinning/);
    assertPresent('audit2-3: asserts options.hostname is resolved IP, not URL hostname', httpTest,
        /expect\(optionsArg\.hostname\)\.not\.toBe\(['"]example\.com['"]\)/);

    // ============ #6: audit-log v2 length-prefixed hash chain ============
    const chainHelper = read('src/lib/audit-chain.ts');
    assertPresent('audit2-6: chain helper has canonicalV1 (legacy)', chainHelper, /canonicalV1/);
    assertPresent('audit2-6: chain helper has canonicalV2 (length-prefixed)', chainHelper, /canonicalV2/);
    assertPresent('audit2-6: v2 prepends "v2:" sentinel', chainHelper, /['"]v2:['"]/);
    assertPresent('audit2-6: v2 uses padStart(8, "0")', chainHelper, /padStart\(8,\s*['"]0['"]\)/);
    assertPresent('audit2-6: v2 uses Buffer.byteLength (UTF-8 bytes, not chars)', chainHelper, /Buffer\.byteLength/);

    const v2Migration = read('prisma/migrations/20260524000003_audit2_6_audit_chain_v2/migration.sql');
    assertPresent('audit2-6: migration drops old trigger', v2Migration, /DROP TRIGGER IF EXISTS\s+`audit_log_chain_insert`/);
    assertPresent('audit2-6: migration recreates trigger with CONCAT (not CONCAT_WS)', v2Migration,
        /CREATE TRIGGER `audit_log_chain_insert`[\s\S]+SHA2\(CONCAT\(/);
    assertPresent('audit2-6: migration uses LPAD length prefix', v2Migration, /LPAD\(LENGTH\(.+?\),\s*8,\s*['"]0['"]\)/);
    assertPresent('audit2-6: migration includes v2: sentinel', v2Migration, /['"]v2:['"]/);

    const auditService = read('src/lib/services/audit.service.ts');
    assertPresent('audit2-6: verifyChain imports computeRowHashV1 + V2', auditService,
        /computeRowHashV1.*computeRowHashV2|computeRowHashV2.*computeRowHashV1/);
    assertPresent('audit2-6: verifyChain accepts EITHER v2 OR v1 hash per row', auditService,
        /rowHash\s*!==\s*expectedV2\s*&&\s*r\.rowHash\s*!==\s*expectedV1/);

    const runbook = read('docs/runbooks/prisma-migrations.md');
    assertPresent('audit2-6: runbook lists the v2 migration', runbook, /20260524000003_audit2_6_audit_chain_v2/);

    // ============ #9: high-entropy redactor on channel error bodies ============
    const redact = read('src/lib/notification-system/redact.ts');
    assertPresent('audit2-9: redact module exports redactHighEntropy', redact, /export function redactHighEntropy/);
    assertPresent('audit2-9: redact matches 32+ char hex', redact, /A-Fa-f0-9.{0,40}32,/);
    assertPresent('audit2-9: redact matches 32+ char base64', redact, /A-Za-z0-9.{0,40}32,/);

    for (const channelFile of [
        'src/lib/notification-system/channels/webhook.channel.ts',
        'src/lib/notification-system/channels/telegram.channel.ts',
        'src/lib/notification-system/channels/pagerduty.channel.ts',
        'src/lib/notification-system/channels/teams.channel.ts',
    ]) {
        const src = read(channelFile);
        assertPresent(`audit2-9: ${channelFile} imports redactHighEntropy`, src, /redactHighEntropy/);
        assertPresent(`audit2-9: ${channelFile} calls redactHighEntropy before .slice`, src,
            /redactHighEntropy\([^)]+\)\.slice\(0,\s*256\)/);
    }

    const outbox = read('src/lib/notification-system/outbox/outbox.service.ts');
    assertPresent('audit2-9: outbox redacts last-error before UserNotification.message',
        outbox, /redactHighEntropy\(error\)\.slice/);

    ok('audit2-tier2-sec', 'Tier-2 bundle wired: #2 loopback gate, #3 connect-IP test, #6 chain v2, #9 redactor');
}

main();
