/**
 * @module tests/integration/db-asof-vector-defer
 * @description 8.0 #35 — a FILTERED semantic/vector read at a historical generation
 * defers to a native at-gen `VersionedIndexProvider` instead of the O(n@G) JS-HNSW
 * materialization. Brainy resolves the at-gen metadata∩graph universe from the record
 * overlay (no rebuild) and hands the provider `{ allowedIds, generation }`; the
 * provider serves the vector leg, brainy composes. CI has no native provider, so this
 * registers a faithful MOCK versioned vector index (it advertises visibility + records
 * what it receives) to lock the routing + the seam. The JS index is NOT versioned, so
 * the un-mocked path correctly falls through to materialization.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/index.js'
import { NounType } from '../../src/types/graphTypes.js'
import { createTestConfig } from '../helpers/test-factory.js'

/** A versioned vector provider that advertises visibility + records each search. */
function makeVersionedVectorMock(ranking: string[]) {
  const calls: Array<{
    generation?: bigint
    allowedIds?: ReadonlySet<string>
    atGenerationVectors?: { ids: BigInt64Array; vectors: Float32Array; dim: number }
  }> = []
  const provider = {
    // VersionedIndexProvider quartet (duck-typed by isVersionedIndexProvider).
    generation: () => 0n,
    isGenerationVisible: (_g: bigint) => true,
    pin: (_g: bigint) => {},
    release: (_g: bigint) => {},
    // The at-gen vector leg: record the seam fields, return ranked allowed ids.
    search: async (_vec: number[], _k: number, _filter: unknown, opts: any) => {
      calls.push({
        generation: opts?.generation,
        allowedIds: opts?.allowedIds,
        atGenerationVectors: opts?.atGenerationVectors
      })
      const allowed = opts?.allowedIds as ReadonlySet<string> | undefined
      const out: Array<[string, number]> = []
      let distance = 0.1
      for (const id of ranking) {
        if (!allowed || allowed.has(id)) {
          out.push([id, distance])
          distance += 0.1
        }
      }
      return out
    },
    // Unused VectorIndexProvider surface — no-op stubs so close()/flush() are safe.
    addItem: async () => '',
    removeItem: async () => true,
    size: () => 0,
    clear: () => {},
    rebuild: async () => {},
    flush: async () => 0,
    getPersistMode: () => 'immediate' as const
  }
  return { provider, calls }
}

describe('#35 asOf filtered vector read defers to the versioned provider', () => {
  let brain: Brainy
  let a: string, b: string, c: string

  beforeEach(async () => {
    brain = new Brainy(createTestConfig())
    await brain.init()
    a = await brain.add({ type: NounType.Document, data: 'alpha machine learning', metadata: { team: 'x' } })
    b = await brain.add({ type: NounType.Document, data: 'beta machine learning', metadata: { team: 'x' } })
    c = await brain.add({ type: NounType.Document, data: 'gamma cooking', metadata: { team: 'y' } })
  })

  afterEach(async () => {
    await brain.close()
  })

  it('routes to the provider with {allowedIds, generation} (no materialization) — at-gen universe is correct', async () => {
    const db = brain.now() // pin the generation after a, b, c
    // A later write advances the generation → `db` is now historical, and `later`
    // (team x) did NOT exist at the pinned generation.
    const later = await brain.add({ type: NounType.Document, data: 'delta machine learning', metadata: { team: 'x' } })

    // Inject a versioned vector provider that can serve the pinned generation.
    const mock = makeVersionedVectorMock([b, a]) // ranks b before a
    ;(brain as any).index = mock.provider

    const results = await db.find({ query: 'machine learning', where: { team: 'x' } })

    // Served natively — the provider was hit exactly once with the seam fields.
    expect(mock.calls).toHaveLength(1)
    expect(typeof mock.calls[0].generation).toBe('bigint')

    // allowedIds = the at-gen `team: x` universe = {a, b}; NOT `later` (born after the pin).
    const allowed = mock.calls[0].allowedIds!
    expect(allowed.has(a)).toBe(true)
    expect(allowed.has(b)).toBe(true)
    expect(allowed.has(later)).toBe(false)
    expect(allowed.has(c)).toBe(false) // team y, filtered out

    // Results = the at-gen entities, in the provider's ranking (b before a).
    expect(results.map((r) => r.id)).toEqual([b, a])

    await db.release()
  })

  it('respects the page window on the provider-served path', async () => {
    const db = brain.now()
    await brain.add({ type: NounType.Document, data: 'epsilon', metadata: { team: 'z' } }) // advance gen
    const mock = makeVersionedVectorMock([b, a])
    ;(brain as any).index = mock.provider

    const page = await db.find({ query: 'machine learning', where: { team: 'x' }, limit: 1 })
    expect(page.map((r) => r.id)).toEqual([b]) // top-1 by the provider ranking
    await db.release()
  })

  it('supplies the at-gen candidate vectors (part-3) — row-major, dim-correct, ids = the universe', async () => {
    const db = brain.now()
    await brain.add({ type: NounType.Document, data: 'zeta machine learning', metadata: { team: 'z' } }) // advance gen
    const mock = makeVersionedVectorMock([b, a])
    ;(brain as any).index = mock.provider

    await db.find({ query: 'machine learning', where: { team: 'x' } })

    const agv = mock.calls[0].atGenerationVectors
    expect(agv).toBeDefined()
    // ids = the at-gen `team: x` universe {a, b}, interned via the shared mapper.
    expect(agv!.ids.length).toBe(2)
    expect(agv!.dim).toBeGreaterThan(0)
    // The row-major invariant cor asserts on ingest.
    expect(agv!.vectors.length).toBe(agv!.ids.length * agv!.dim)
    // Each interned id resolves back to a universe entity.
    const mapper = (brain as any).metadataIndex.getIdMapper()
    const uuids = Array.from(agv!.ids).map((i) => mapper.getUuid(Number(i)))
    expect(new Set(uuids)).toEqual(new Set([a, b]))
    await db.release()
  })
})
