/**
 * adaptive-quotient-filter.test.js — focused unit tests for the bit-layout
 * fix (was AQF-001 in Phase 0).
 *
 * The historical implementation placed IS_OCCUPIED at bit 0, which
 * collided with the stored remainder's lowest bit — corrupting every
 * slot whose remainder had bit 0 set. After insert the slot's
 * `_getSlotRemainder()` returned remainder | 1 instead of the original
 * remainder, and subsequent `query()` lookups for that fingerprint failed.
 *
 * The fix moves IS_OCCUPIED up to bit r (matching the published AQF
 * specification). This test pins the corrected behavior:
 *   - Insert + query round-trip for fingerprints whose remainder has bit 0 set
 *   - Multi-insert batched correctness
 *   - Delete + insert cycle preserves bit-perfect state
 *   - _getSlotRemainder returns the exact remainder that was inserted
 *   - The OCCUPIED bit does not bleed into read-back remainder
 */
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import AdaptiveQuotientFilter from '../../src/core/AdaptiveQuotientFilter.js';

describe('AdaptiveQuotientFilter (bit layout)', () => {
  it('insert + query round-trip for a fingerprint with remainder bit 0 set', () => {
    // Find a key whose remainder has bit 0 set under remainderBits=9.
    let probe = '';
    const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits: 9 });
    for (let i = 0; i < 10_000; i++) {
      probe = `key-${i}`;
      const fp = f._computeFingerprint(probe);
      const r = f._extractRemainder(fp);
      if ((r & 1) === 1) break;
    }
    f.insert(probe);
    // AQF-001 was: query returned false even though the key was just
    // inserted, because _getSlotRemainder() returned remainder | 1
    // (occupied bit bleeding into the read-back remainder).
    assert.equal(f.query(probe), true);
  });

  it('insert/query/delete cycle preserves bit-perfect state', () => {
    const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits: 9 });
    for (let i = 0; i < 50; i++) {
      f.insert(`cycle-${i}`);
    }
    for (let i = 0; i < 50; i++) {
      assert.equal(f.query(`cycle-${i}`), true);
    }
    for (let i = 0; i < 50; i++) {
      f.delete(`cycle-${i}`);
    }
    for (let i = 0; i < 50; i++) {
      assert.equal(f.query(`cycle-${i}`), false);
    }
  });

  it('_getSlotRemainder returns the exact remainder (no occupied-bit bleed)', () => {
    const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits: 9 });
    f.insert('inspect-me');

    // Find the slot that holds this fingerprint by scanning
    const fp = f._computeFingerprint('inspect-me');
    const q = f._extractQuotient(fp);
    const r = f._extractRemainder(fp);
    const canonical = q % f.tableSize;
    let slotIndex = canonical;
    let foundSlot = -1;
    for (let i = 0; i < f.tableSize; i++) {
      const idx = (canonical + i) % f.tableSize;
      if (f._isSlotOccupied(idx) && f._getSlotRemainder(idx) === r) {
        foundSlot = idx;
        break;
      }
    }
    assert.notEqual(foundSlot, -1, 'inserted fingerprint should be findable in a slot');
    // The slot's stored remainder must match the inserted remainder
    // exactly — no occupied-bit contamination.
    assert.equal(f._getSlotRemainder(foundSlot), r);
  });

  it('insert with count increments itemCount without bit bleed', () => {
    const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits: 9 });
    f.insert('multi', 3);
    assert.equal(f.itemCount, 3);
    assert.equal(f.query('multi'), true);

    // Inserting the same key again should add to the count, not
    // produce a duplicate slot with corrupted state.
    f.insert('multi', 2);
    assert.equal(f.itemCount, 5);
    assert.equal(f.query('multi'), true);
  });

  it('supports a range of remainderBits without layout breakage', () => {
    for (const remainderBits of [4, 8, 9, 12, 16]) {
      const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits });
      for (let i = 0; i < 20; i++) {
        f.insert(`wide-${remainderBits}-${i}`);
      }
      for (let i = 0; i < 20; i++) {
        assert.equal(f.query(`wide-${remainderBits}-${i}`), true,
          `query failed for remainderBits=${remainderBits} key=${i}`);
      }
    }
  });

  it('OCCUPIED bit does not corrupt stored remainder even at the boundary', () => {
    // Force a remainder = 0xFF (all bits set) — this is the case most
    // likely to expose any layout corruption.
    const f = new AdaptiveQuotientFilter({ capacity: 1024, remainderBits: 9 });
    let boundaryKey = null;
    for (let i = 0; i < 100_000 && !boundaryKey; i++) {
      const k = `boundary-${i}`;
      const r = f._extractRemainder(f._computeFingerprint(k));
      if (r === 0x1FF) {
        boundaryKey = k;
      }
    }
    if (boundaryKey) {
      f.insert(boundaryKey);
      assert.equal(f.query(boundaryKey), true);
      const fp = f._computeFingerprint(boundaryKey);
      const canonical = f._extractQuotient(fp) % f.tableSize;
      // Find slot
      for (let i = 0; i < f.tableSize; i++) {
        const idx = (canonical + i) % f.tableSize;
        if (f._isSlotOccupied(idx)) {
          // The remainder stored in the slot must be exactly 0x1FF
          // — no corruption from any metadata bit.
          assert.equal(f._getSlotRemainder(idx), 0x1FF);
          break;
        }
      }
    }
  });
});