/**
 * PR-39 / EA-8 smoke — single-instance guard wiring.
 *
 * Static assertions that the guard module + instrumentation hook +
 * deploy doc are in place. The actual lock acquisition + rejection-
 * on-conflict behaviour is exercised by:
 *   1. `npm run dev` in one terminal (acquires the lock)
 *   2. `npm run dev` in another (expects exit 1)
 *
 * The verification recipe lives in docs/deploy/single-instance.md.
 */
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-8: ${label} missing`, abs);
    return readFileSync(abs, 'utf8');
}

function main(): void {
    const guard = read('single-instance-guard.ts', 'src/lib/startup/single-instance-guard.ts');
    const inst = read('instrumentation.ts', 'instrumentation.ts');
    const doc = read('deploy doc', 'docs/deploy/single-instance.md');

    // 1. Guard module exports the acquire function + status check
    if (!/export\s+async\s+function\s+acquireSingleInstanceLock/.test(guard)) {
        fail('ea-8: guard does not export acquireSingleInstanceLock', 'API contract broken');
    }
    if (!/export\s+function\s+isSingleInstanceLockHeld/.test(guard)) {
        fail('ea-8: guard does not export isSingleInstanceLockHeld', 'status check missing');
    }
    ok('ea-8: guard module exports acquire + status fns');

    // 2. Guard uses MySQL GET_LOCK against the project-specific name
    if (!/GET_LOCK\s*\(\s*\?\s*,\s*0\s*\)/.test(guard)) {
        fail('ea-8: guard does not use GET_LOCK(?, 0)', 'non-blocking lock contract missing');
    }
    if (!/'uptime-sentinel-web'/.test(guard)) {
        fail('ea-8: guard does not use the project-specific lock name', 'name collision risk');
    }
    ok('ea-8: guard uses GET_LOCK("uptime-sentinel-web", 0) — non-blocking');

    // 3. Lock is held inside an interactive transaction that doesn't end
    if (!/prisma\.\$transaction/.test(guard)) {
        fail('ea-8: guard does not use Prisma interactive transaction', 'lock would auto-release on connection cycle');
    }
    if (!/await\s+new\s+Promise\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/.test(guard)) {
        fail('ea-8: guard transaction does not sleep forever', 'lock will release prematurely');
    }
    if (!/timeout:\s*Number\.MAX_SAFE_INTEGER/.test(guard)) {
        fail('ea-8: guard transaction timeout not raised', 'Prisma default 5s would abort the lock');
    }
    ok('ea-8: lock held in long-lived interactive transaction');

    // 4. Instrumentation calls the guard BEFORE other init
    if (!/acquireSingleInstanceLock/.test(inst)) {
        fail('ea-8: instrumentation does not call acquireSingleInstanceLock', 'guard not wired');
    }
    const guardPos = inst.indexOf('acquireSingleInstanceLock');
    const otelPos = inst.indexOf('startOtel');
    const workerPos = inst.indexOf('notification-worker');
    if (guardPos < 0 || (otelPos > 0 && guardPos > otelPos) || (workerPos > 0 && guardPos > workerPos)) {
        fail('ea-8: guard runs AFTER OTel or worker init', 'should be first — avoids wasted boot work');
    }
    ok('ea-8: instrumentation calls guard before OTel + worker');

    // 5. Instrumentation exits the process on lock-acquisition failure
    if (!/process\.exit\(1\)/.test(inst)) {
        fail('ea-8: instrumentation does not exit on guard failure', 'Next.js does not abort boot when register() throws');
    }
    ok('ea-8: instrumentation process.exit(1)s when lock acquisition fails');

    // 6. SINGLE_INSTANCE_GUARD env var provides an escape hatch
    if (!/SINGLE_INSTANCE_GUARD\s*!==\s*['"]false['"]/.test(inst)) {
        fail('ea-8: SINGLE_INSTANCE_GUARD=false escape hatch missing', 'no way to disable in emergencies');
    }
    ok('ea-8: SINGLE_INSTANCE_GUARD=false escape hatch present');

    // 7. Deploy doc covers what breaks + how to verify
    if (!/SSE bus|in-process EventEmitter/i.test(doc)) {
        fail('ea-8: deploy doc does not explain what breaks with 2 replicas', 'context missing');
    }
    if (!/GET_LOCK/.test(doc)) {
        fail('ea-8: deploy doc does not document the locking mechanism', 'operator visibility');
    }
    if (!/SINGLE_INSTANCE_GUARD/.test(doc)) {
        fail('ea-8: deploy doc does not mention the escape hatch', 'operators won\'t find the switch');
    }
    if (!/Roadmap to HA/i.test(doc)) {
        fail('ea-8: deploy doc has no roadmap to multi-replica', 'eventual scaling path undefined');
    }
    ok('ea-8: deploy doc covers symptoms + mechanism + escape hatch + HA roadmap');

    // AUDIT-3 (2026-05-28): watchdog + heartbeat-query additions.
    // We just lived through a 6-day outage where a stale MySQL
    // connection held the lock after the original process crashed.
    if (!/tryReleaseStaleHolder/.test(guard)) {
        fail(
            'audit3-1: tryReleaseStaleHolder() not exported from guard',
            'watchdog API contract broken — stale lock holders cannot be released',
        );
    }
    if (!/SELECT IS_USED_LOCK\(\?\)\s*AS holder/.test(guard)) {
        fail(
            'audit3-1: watchdog does not probe IS_USED_LOCK',
            'cannot identify the conn_id holding the stale lock',
        );
    }
    if (!/information_schema\.PROCESSLIST/.test(guard)) {
        fail(
            'audit3-1: watchdog does not inspect PROCESSLIST',
            'cannot tell live from stale holder',
        );
    }
    if (!/KILL\s+\$\{safeId\}/.test(guard)) {
        fail(
            'audit3-1: watchdog does not KILL stale connection',
            'detection without remediation is useless',
        );
    }
    if (!/state\.command\s*!==\s*'Sleep'/.test(guard)) {
        fail(
            'audit3-1: watchdog kills connections that are NOT idle',
            'safety check missing — could kill a live query',
        );
    }
    if (!/STALE_HOLDER_THRESHOLD_SEC/.test(guard)) {
        fail(
            'audit3-1: watchdog has no Time threshold',
            'safety check missing — could kill a live process',
        );
    }
    if (!/SINGLE_INSTANCE_LOCK_WATCHDOG\s*!==\s*['"]false['"]/.test(guard)) {
        fail(
            'audit3-1: SINGLE_INSTANCE_LOCK_WATCHDOG=false escape hatch missing',
            'no way to disable watchdog if it ever misbehaves',
        );
    }
    if (!/HEARTBEAT_QUERY_INTERVAL_MS/.test(guard) || !/tx\.\$queryRawUnsafe\('SELECT 1'\)/.test(guard)) {
        fail(
            'audit3-1: lock-holding transaction does not refresh PROCESSLIST.Time',
            'watchdog cannot distinguish live from stale holders without periodic SELECT 1',
        );
    }
    ok('audit3-1: watchdog wired (IS_USED_LOCK + PROCESSLIST + Sleep+Time guards + KILL + heartbeat-query)');

    ok('PR-39 single-instance guard', 'guard + instrumentation wired; deploy doc complete; audit3-1 watchdog added');
}

main();
