/**
 * @module tests/integration/aggregation-delete
 * @description Regression for BR-AGG-DELETE-HANG (consumer-reported on 7.x): once a
 * brain had ever deleted an entity, raw `queryAggregate` never resolved (7.x recomputed
 * MIN/MAX after a delete by SCANNING entities, which fed materialized-aggregate entities
 * back into aggregation → infinite loop). 8.0 must (a) RESOLVE after deletes — never hang —
 * and (b) return the CORRECT new min/max after deleting the current extreme (recomputed
 * from the in-memory value multiset, no entity scan).
 */
import { describe, it, expect, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js'

/** Deterministic 384-dim vectors so the embedder is never invoked. */
function vec(seed: number): number[] {
  return Array.from({ length: 384 }, (_, i) => ((seed * 31 + i * 7) % 100) / 100)
}

/** Resolve a query or fail loudly if it hangs (the 7.x symptom). */
function noHang<R>(p: Promise<R>, label: string, ms = 4000): Promise<R> {
  return Promise.race([
    p,
    new Promise<R>((_, rej) =>
      setTimeout(() => rej(new Error(`${label} HUNG (did not resolve in ${ms}ms)`)), ms)
    )
  ])
}

describe('queryAggregate across deletes — no hang + correct min/max', () => {
  const brains: Brainy[] = []
  afterEach(async () => {
    for (const b of brains.splice(0)) await b.close().catch(() => {})
  })

  it('resolves after deletes and recomputes min/max from the multiset', async () => {
    const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
    brains.push(brain)
    await brain.init()

    brain.defineAggregate({
      name: 'stats_by_cat',
      source: { type: NounType.Thing },
      groupBy: ['category'],
      metrics: {
        total: { op: 'sum', field: 'amount' },
        lo: { op: 'min', field: 'amount' },
        hi: { op: 'max', field: 'amount' },
        n: { op: 'count' }
      }
    })

    const ids: Record<string, string> = {}
    for (const [label, amount] of [
      ['a', 10],
      ['b', 20],
      ['c', 30],
      ['d', 40]
    ] as const) {
      ids[label] = await brain.add({
        data: `e-${label}`,
        type: NounType.Thing,
        vector: vec(amount),
        metadata: { category: 'x', amount }
      })
    }

    const pick = (rows: any[]) => rows.find((r) => r.groupKey.category === 'x')

    let g = pick(await noHang(brain.queryAggregate('stats_by_cat'), 'queryAggregate (initial)'))
    expect(g).toBeDefined()
    expect(g.metrics.total).toBe(100)
    expect(g.metrics.lo).toBe(10)
    expect(g.metrics.hi).toBe(40)
    expect(g.metrics.n).toBe(4)

    // Delete the CURRENT min (10) AND the CURRENT max (40) — the case that hung in 7.x.
    await brain.remove(ids.a)
    await brain.remove(ids.d)

    g = pick(await noHang(brain.queryAggregate('stats_by_cat'), 'queryAggregate (after delete)'))
    expect(g).toBeDefined()
    expect(g.metrics.n).toBe(2)
    expect(g.metrics.total).toBe(50) // 20 + 30
    expect(g.metrics.lo).toBe(20) // new min after deleting 10
    expect(g.metrics.hi).toBe(30) // new max after deleting 40

    // A further delete still resolves and stays correct.
    await brain.remove(ids.b)
    g = pick(await noHang(brain.queryAggregate('stats_by_cat'), 'queryAggregate (after 2nd delete)'))
    expect(g.metrics.n).toBe(1)
    expect(g.metrics.lo).toBe(30)
    expect(g.metrics.hi).toBe(30)
  })
})
