/**
 * PR-38 / EA-7 smoke — outbound webhook HMAC signing.
 *
 * Static assertions that the contract is wired end-to-end:
 *   - ChannelConfig.signingSecret field exists in the channel type
 *   - WebhookChannel computes HMAC-SHA256 against `${timestamp}.${body}`
 *   - Headers are X-Sentinel-Timestamp + X-Sentinel-Signature: sha256=<hex>
 *   - Unit test locks the canonical recipe
 *   - Receiver-verification docs ship with the PR
 */
import './_lib';
import { ok, fail } from './_lib';
import { readFileSync, existsSync } from 'fs';
import path from 'path';

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

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

function main(): void {
    const types = read('types.ts', 'src/lib/notification-system/types.ts');
    const channel = read('webhook.channel.ts', 'src/lib/notification-system/channels/webhook.channel.ts');
    const unit = read('webhook-hmac.test.ts', 'src/lib/notification-system/channels/__tests__/webhook-hmac.test.ts');
    const docs = read('webhook-hmac.md', 'docs/notification-channels/webhook-hmac.md');

    // 1. Channel config exposes signingSecret
    if (!/signingSecret\?\s*:\s*string/.test(types)) {
        fail('ea-7: ChannelConfig.signingSecret missing', 'config shape change required');
    }
    ok('ea-7: ChannelConfig.signingSecret declared');

    // 2. Webhook channel computes HMAC when secret present
    if (!/createHmac\(\s*['"]sha256['"]/.test(channel)) {
        fail('ea-7: webhook channel does not use HMAC-SHA256', 'wrong algorithm or not wired');
    }
    if (!/config\.signingSecret/.test(channel)) {
        fail('ea-7: webhook channel never reads signingSecret', 'feature not wired');
    }
    ok('ea-7: webhook channel uses createHmac sha256 + signingSecret');

    // 3. Canonical input is `${timestamp}.${rawBody}` (or equivalent shape)
    if (!/`\$\{timestamp\}\.\$\{rawBody\}`|`\$\{ts\}\.\$\{body\}`/.test(channel)) {
        fail('ea-7: signed input is not `${timestamp}.${rawBody}`', 'receivers will reject');
    }
    ok('ea-7: signed input is timestamp + "." + rawBody');

    // 4. Both headers are emitted with the right names
    if (!/X-Sentinel-Timestamp/.test(channel)) {
        fail('ea-7: X-Sentinel-Timestamp header missing', 'replay defence not wired');
    }
    if (!/X-Sentinel-Signature/.test(channel)) {
        fail('ea-7: X-Sentinel-Signature header missing', 'signature not transmitted');
    }
    if (!/`sha256=\$\{sig\}`/.test(channel)) {
        fail('ea-7: signature header is not "sha256=<hex>" format', 'GitHub/Stripe convention broken');
    }
    ok('ea-7: emits X-Sentinel-Timestamp + X-Sentinel-Signature: sha256=<hex>');

    // 5. Timestamp is unix SECONDS not milliseconds
    if (!/Math\.floor\(Date\.now\(\)\s*\/\s*1000\)/.test(channel)) {
        fail('ea-7: timestamp is not unix seconds', 'milliseconds would surprise receivers');
    }
    ok('ea-7: timestamp is unix seconds (Math.floor(Date.now()/1000))');

    // 6. Backwards-compatible: existing channels without secret still work
    if (!/if\s*\(\s*config\.signingSecret\s*\)/.test(channel)) {
        fail('ea-7: signing not gated on signingSecret presence', 'breaks existing unsigned channels');
    }
    ok('ea-7: signing is opt-in (channels without secret still send)');

    // 7. Unit test locks the recipe
    if (!/produces a different signature for a different timestamp/.test(unit)) {
        fail('ea-7: unit test does not cover replay defence', 'missing test');
    }
    if (!/produces a different signature for a different body/.test(unit)) {
        fail('ea-7: unit test does not cover tamper defence', 'missing test');
    }
    if (!/matches a receiver implementing the same recipe/.test(unit)) {
        fail('ea-7: unit test does not verify sender/receiver byte-equivalence', 'wire contract not locked');
    }
    ok('ea-7: unit test locks signature recipe (5 cases)');

    // 8. Receiver docs ship with code samples in 2+ languages
    if (!/```js|```javascript/i.test(docs)) {
        fail('ea-7: receiver docs missing Node.js code sample', 'integrators have no template');
    }
    if (!/```go/.test(docs)) {
        fail('ea-7: receiver docs missing Go code sample', 'should cover >1 language');
    }
    if (!/timingSafeEqual|hmac\.Equal/.test(docs)) {
        fail('ea-7: receiver docs do not show constant-time compare', 'timing-oracle vulnerability if missed');
    }
    ok('ea-7: receiver docs cover Node + Go with constant-time compare');

    ok('PR-38 webhook HMAC signing', 'config + channel + test + docs wired');
}

main();
