/**
 * Session Transcript Route Validation Tests — F24
 * Tests for strict integer parsing on cursor/limit query params.
 *
 * R7 P2: Number(' ') → 0, Number('10abc') → NaN but parseInt('10abc') → 10
 * Fix: strictParseInt using /^\d+$/ regex
 */

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

// We test the strictParseInt logic indirectly via the route,
// but first test the regex behavior directly for clarity.

describe('strictParseInt validation (R7 P2)', () => {
  // Inline the same logic as the route helper for unit testing
  function strictParseInt(s) {
    return /^\d+$/.test(s) ? Number(s) : NaN;
  }

  it('accepts pure digit strings', () => {
    assert.equal(strictParseInt('0'), 0);
    assert.equal(strictParseInt('1'), 1);
    assert.equal(strictParseInt('42'), 42);
    assert.equal(strictParseInt('200'), 200);
  });

  it('rejects whitespace-only strings (%20 → space)', () => {
    assert.ok(Number.isNaN(strictParseInt(' ')));
    assert.ok(Number.isNaN(strictParseInt('  ')));
    assert.ok(Number.isNaN(strictParseInt('\t')));
  });

  it('rejects leading/trailing whitespace', () => {
    assert.ok(Number.isNaN(strictParseInt(' 10')));
    assert.ok(Number.isNaN(strictParseInt('10 ')));
    assert.ok(Number.isNaN(strictParseInt(' 10 ')));
  });

  it('rejects partial numeric strings (parseInt would accept)', () => {
    assert.ok(Number.isNaN(strictParseInt('10abc')));
    assert.ok(Number.isNaN(strictParseInt('3.5')));
    assert.ok(Number.isNaN(strictParseInt('1e2')));
  });

  it('rejects empty string', () => {
    assert.ok(Number.isNaN(strictParseInt('')));
  });

  it('rejects negative numbers (regex requires only digits)', () => {
    assert.ok(Number.isNaN(strictParseInt('-1')));
    assert.ok(Number.isNaN(strictParseInt('-0')));
  });

  it('rejects hex/octal/binary prefixes', () => {
    assert.ok(Number.isNaN(strictParseInt('0x10')));
    assert.ok(Number.isNaN(strictParseInt('0o10')));
    assert.ok(Number.isNaN(strictParseInt('0b10')));
  });
});

describe('seal session cleanup timing (R7 P1)', () => {
  it('sessionManager.delete must be called immediately on requestSeal accept, not after finalize', async () => {
    // This test verifies the ordering guarantee:
    // requestSeal(accepted) → sessionManager.delete() → (async) finalize()
    //
    // The previous implementation called delete inside finalize().then(),
    // leaving a race window where the next invocation could --resume the sealed session.

    const events = [];

    const mockSessionManager = {
      delete() {
        events.push('session_delete');
        return Promise.resolve();
      },
    };

    const mockSealer = {
      requestSeal() {
        events.push('request_seal');
        return { accepted: true };
      },
      finalize() {
        events.push('finalize_start');
        // Simulate async finalize that takes time
        return new Promise((resolve) => {
          setTimeout(() => {
            events.push('finalize_end');
            resolve();
          }, 50);
        });
      },
    };

    // Simulate the fixed code path:
    // 1. requestSeal
    const sealResult = mockSealer.requestSeal();
    assert.ok(sealResult.accepted);

    // 2. Immediately delete session (R7 P1 fix)
    mockSessionManager.delete().catch(() => {});

    // 3. Background finalize
    mockSealer.finalize().catch(() => {});

    // After the synchronous block, delete should happen before finalize completes
    // Wait a tick for the delete promise
    await new Promise((r) => setTimeout(r, 10));
    assert.deepEqual(events, ['request_seal', 'session_delete', 'finalize_start']);

    // Wait for finalize to complete
    await new Promise((r) => setTimeout(r, 60));
    assert.deepEqual(events, ['request_seal', 'session_delete', 'finalize_start', 'finalize_end']);
  });
});
