#!/usr/bin/env node
'use strict';
// בדיקות יחידה לשכבות Ω/Θ החדשות: unzip טהור, שעון לוגי היברידי (HLC),
// וגלאי הכשל phi-accrual + ציון בריאות EWMA.
process.env.SC_TEST = '1';
const t = require('./dump-test.js').__test;
const assert = require('node:assert');
const zlib = require('node:zlib');

let pass = 0;
const ok = (name, cond) => { assert.ok(cond, name); console.log('PASS ' + name); pass++; };

// ── בונה ZIP מינימלי (stored + deflate) כדי לבדוק את הפורק ──
function buildZip(entries) {
  const locals = [];
  const central = [];
  let offset = 0;
  for (const e of entries) {
    const nameBuf = Buffer.from(e.name, 'utf8');
    const raw = Buffer.from(e.data);
    const method = e.deflate ? 8 : 0;
    const comp = e.deflate ? zlib.deflateRawSync(raw) : raw;
    const crc = 0; // הפורק לא בודק CRC — 0 מקובל לבדיקה
    const lh = Buffer.alloc(30);
    lh.writeUInt32LE(0x04034b50, 0);
    lh.writeUInt16LE(20, 4);
    lh.writeUInt16LE(method, 8);
    lh.writeUInt32LE(crc, 14);
    lh.writeUInt32LE(comp.length, 18);
    lh.writeUInt32LE(raw.length, 22);
    lh.writeUInt16LE(nameBuf.length, 26);
    const localRec = Buffer.concat([lh, nameBuf, comp]);
    locals.push(localRec);

    const ch = Buffer.alloc(46);
    ch.writeUInt32LE(0x02014b50, 0);
    ch.writeUInt16LE(method, 10);
    ch.writeUInt32LE(crc, 16);
    ch.writeUInt32LE(comp.length, 20);
    ch.writeUInt32LE(raw.length, 24);
    ch.writeUInt16LE(nameBuf.length, 28);
    ch.writeUInt32LE(offset, 42);
    central.push(Buffer.concat([ch, nameBuf]));
    offset += localRec.length;
  }
  const localBlob = Buffer.concat(locals);
  const centralBlob = Buffer.concat(central);
  const eocd = Buffer.alloc(22);
  eocd.writeUInt32LE(0x06054b50, 0);
  eocd.writeUInt16LE(entries.length, 8);
  eocd.writeUInt16LE(entries.length, 10);
  eocd.writeUInt32LE(centralBlob.length, 12);
  eocd.writeUInt32LE(localBlob.length, 16);
  return Buffer.concat([localBlob, centralBlob, eocd]);
}

// ── unzip ──
const bigText = 'x'.repeat(5000) + 'END';
const zip = buildZip([
  { name: 'index.html', data: '<h1>hi</h1>' },
  { name: 'assets/app.js', data: 'console.log(1)', deflate: true },
  { name: 'big.txt', data: bigText, deflate: true },
]);
const out = t.unzip(zip);
ok('unzip returns all files', out.length === 3);
ok('unzip stored file exact', out.find((f) => f.name === 'index.html').data.toString() === '<h1>hi</h1>');
ok('unzip deflate file exact', out.find((f) => f.name === 'assets/app.js').data.toString() === 'console.log(1)');
ok('unzip large deflate byte-exact', out.find((f) => f.name === 'big.txt').data.toString() === bigText);

// ── HLC ──
const a = t.hlcNow();
const b = t.hlcNow();
ok('HLC monotonic strictly increasing', b > a);
t.hlcObserve(a + 1_000_000);
const c = t.hlcNow();
ok('HLC jumps past observed remote time', c > a + 1_000_000);
let last = 0, mono = true;
for (let i = 0; i < 10000; i++) { const v = t.hlcNow(); if (v <= last) mono = false; last = v; }
ok('HLC 10k calls never repeat/regress', mono);

// ── phi-accrual + EWMA ──
const h = new t.RelayHealth();
for (let i = 0; i < 20; i++) h.observe();               // מרווחים סדירים
const phiFresh = h.phi();
ok('phi low right after a message', phiFresh < 1.5);
h.last = Date.now() - 60_000;                            // דממה ארוכה
ok('phi rises sharply after long silence', h.phi() > 6);

const healthy = new t.RelayHealth(); healthy.latency(30); healthy.latency(40); healthy.observe();
const slow = new t.RelayHealth(); slow.latency(3000); slow.miss(); slow.miss();
ok('healthy relay scores higher than slow/failing', healthy.score() > slow.score());

// ── שכבה מטמורפית: טביעת-רגל שונה בכל הרצה ──
ok('runFingerprint is stable within a run', t.runFingerprint() === t.runFingerprint());
const pads = new Set(); for (let i = 0; i < 200; i++) pads.add(t.metaPad());
ok('metaPad produces varied padding (polymorphism)', pads.size > 150);
const base = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let anyReorder = false;
for (let i = 0; i < 30; i++) { const s = t.shuffle(base); if (s.join() !== base.join()) anyReorder = true; }
ok('shuffle reorders connect sequence', anyReorder);
ok('shuffle preserves all elements', t.shuffle(base).slice().sort((a, b) => a - b).join() === base.join());
ok('shuffle does not mutate input', base.join() === '1,2,3,4,5,6,7,8,9,10');

// ── חלוקה א-סימטרית משוקללת לפי עומס ──
const scored = [{ id: 'A', score: 100 }, { id: 'B', score: 1 }, { id: 'C', score: 0 }];
ok('pickWeighted never returns zero-score targets', Array.from({ length: 50 }, () => t.pickWeighted(scored, 2)).flat().every((x) => x !== 'C'));
ok('pickWeighted returns distinct targets (no dup within a pick)', (() => { const p = t.pickWeighted([{ id: 'A', score: 5 }, { id: 'B', score: 5 }], 2); return new Set(p).size === p.length; })());
let aWins = 0;
for (let i = 0; i < 3000; i++) { const p = t.pickWeighted([{ id: 'A', score: 90 }, { id: 'B', score: 10 }], 1); if (p[0] === 'A') aWins++; }
ok('pickWeighted honours weights (~90% to heavy target)', aWins > 2400 && aWins < 2999);

// ── החלטת החלפת-מסלול (Auto-Proxy / Protocol Hopping) ──
const pool = new t.RelayPool(['a', 'b', 'c', 'd'], ['e', 'f']);
ok('hop when too few connections', pool._hopDecision([{ phi: 0 }, { phi: 0 }], 2) === true);
ok('hop when majority of lane degraded', pool._hopDecision([{ phi: 9 }, { phi: 9 }, { phi: 0 }, { phi: 0 }], 4) === true);
ok('no hop on healthy lane', pool._hopDecision([{ phi: 0 }, { phi: 1 }, { phi: 0 }, { phi: 0 }], 4) === false);

console.log('\nALL ' + pass + ' OMEGA/THETA TESTS PASSED');
