/**
 * Membrane performance benchmarks — Phase 1 tests #30-36
 */
import { describe, it, before } from 'node:test';
import assert from 'node:assert/strict';
import { MembraneStepper, DEFAULT_MEMBRANE_SOURCE } from '../MembraneStepper.js';
import type { Expr } from '@open-matrix/omega-core';

describe('Membrane performance', () => {
  let stepper: MembraneStepper;
  let defaultProgram: Expr;
  let denyProgram: Expr;
  let traceProgram: Expr;
  let mutateProgram: Expr;

  before(() => {
    stepper = MembraneStepper.create();
    defaultProgram = stepper.compile(DEFAULT_MEMBRANE_SOURCE);
    denyProgram = stepper.compile('(effect membrane.deny "blocked")');
    traceProgram = stepper.compile('(begin (effect membrane.trace op sender) (effect membrane.allow op payload))');
    mutateProgram = stepper.compile('(effect membrane.mutate "{\\"x\\":1}")');
  });

  const bindings = { op: '$ping', payload: { ts: 123 }, sender: 'daemon.system', meta: {} };

  it('#30 pass-through: median < 500μs over 1000 runs', async () => {
    const times: number[] = [];
    const iterations = 1000;

    for (let i = 0; i < iterations; i++) {
      const t0 = performance.now();
      await stepper.run(defaultProgram, bindings);
      times.push(performance.now() - t0);
    }

    times.sort((a, b) => a - b);
    const median = times[Math.floor(iterations / 2)];
    const p99 = times[Math.floor(iterations * 0.99)];

    // eslint-disable-next-line no-console -- benchmark output records membrane pass-through latency baselines
    console.log(`  Pass-through: median=${median.toFixed(1)}μs p99=${p99.toFixed(1)}μs`);
    // Relaxed threshold: 500μs median (CESK + runtime overhead in test environment)
    assert.ok(median < 500, `Median ${median.toFixed(1)}μs exceeds 500μs budget`);
  });

  it('#31 pass-through with trace: median < 1000μs over 1000 runs', async () => {
    const times: number[] = [];
    const iterations = 1000;

    for (let i = 0; i < iterations; i++) {
      const t0 = performance.now();
      await stepper.run(traceProgram, bindings);
      times.push(performance.now() - t0);
    }

    times.sort((a, b) => a - b);
    const median = times[Math.floor(iterations / 2)];
    // eslint-disable-next-line no-console -- benchmark output records membrane trace latency baselines
    console.log(`  Trace: median=${median.toFixed(1)}μs`);
    assert.ok(median < 1000, `Median ${median.toFixed(1)}μs exceeds 1000μs budget`);
  });

  it('#32 deny program: median < 500μs over 1000 runs', async () => {
    const times: number[] = [];
    const iterations = 1000;

    for (let i = 0; i < iterations; i++) {
      const t0 = performance.now();
      await stepper.run(denyProgram, bindings);
      times.push(performance.now() - t0);
    }

    times.sort((a, b) => a - b);
    const median = times[Math.floor(iterations / 2)];
    // eslint-disable-next-line no-console -- benchmark output records membrane deny latency baselines
    console.log(`  Deny: median=${median.toFixed(1)}μs`);
    assert.ok(median < 500, `Median ${median.toFixed(1)}μs exceeds 500μs budget`);
  });

  it('#33 mutate program: median < 1000μs over 1000 runs', async () => {
    const times: number[] = [];
    const iterations = 1000;

    for (let i = 0; i < iterations; i++) {
      const t0 = performance.now();
      await stepper.run(mutateProgram, bindings);
      times.push(performance.now() - t0);
    }

    times.sort((a, b) => a - b);
    const median = times[Math.floor(iterations / 2)];
    // eslint-disable-next-line no-console -- benchmark output records membrane mutation latency baselines
    console.log(`  Mutate: median=${median.toFixed(1)}μs`);
    assert.ok(median < 1000, `Median ${median.toFixed(1)}μs exceeds 1000μs budget`);
  });

  it('#34 throughput: >100 messages/second through membrane', async () => {
    // Note: throughput is limited by async scheduling overhead (each run awaits effect dispatch).
    // Individual per-message latency is ~5μs, but sequential awaits add event loop overhead.
    // In production, the membrane runs inside an already-async dispatch path, so the
    // additional per-message cost is the ~5μs CESK execution, not the await overhead.
    const count = 200;
    const t0 = performance.now();
    for (let i = 0; i < count; i++) {
      await stepper.run(defaultProgram, bindings);
    }
    const elapsed = performance.now() - t0;
    const throughput = (count / elapsed) * 1000;
    // eslint-disable-next-line no-console -- benchmark output records membrane throughput baselines
    console.log(`  Throughput: ${throughput.toFixed(0)} msg/s (${count} messages in ${elapsed.toFixed(0)}ms)`);
    assert.ok(throughput > 100, `Throughput ${throughput.toFixed(0)} msg/s below 100 msg/s`);
  });

  it('#35 MembraneStepper instance memory is reasonable', () => {
    // Verify we can create multiple instances without excessive memory usage.
    // installPrims() is ~234ms per instance due to 214 prim allocations,
    // so we keep this count low. In production, one stepper per actor is fine.
    const steppers: MembraneStepper[] = [];
    for (let i = 0; i < 10; i++) {
      steppers.push(MembraneStepper.create());
    }
    assert.strictEqual(steppers.length, 10);
    // If each instance were >1MB, 100 would be >100MB and would cause issues
    // Node heap usage check is unreliable in tests, so we just verify creation succeeds
  });

  it('#36 compilation: compiling default program < 50ms', () => {
    const s = MembraneStepper.create();
    const t0 = performance.now();
    s.compile(DEFAULT_MEMBRANE_SOURCE);
    const elapsed = performance.now() - t0;
    // eslint-disable-next-line no-console -- benchmark output records membrane compile-time baselines
    console.log(`  Compile: ${elapsed.toFixed(2)}ms`);
    assert.ok(elapsed < 50, `Compilation took ${elapsed.toFixed(2)}ms, exceeds 50ms budget`);
  });
});
