/**
 * Migration LOCK (#18) — coordinated, automatic 7.x → 8.0 auto-upgrade.
 *
 * Exercises the real block-and-queue behavior of `awaitMigrationLock` at the
 * `ensureInitialized` choke point: while ANY index provider reports
 * `isMigrating() === true`, data-plane reads and writes WAIT (no operation
 * touches a half-built index); observability (`getIndexStatus`, `health`) and
 * the lock-clearing `stampBrainFormat` stay exempt; and a lock that outlives the
 * configured window surfaces a retryable `MigrationInProgressError`.
 *
 * A native (cortex) provider owns the real migration; here we simulate the lock
 * by feature-detect-injecting `isMigrating()` onto a live provider, exactly as
 * the production feature-detection reads it.
 */

import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType, MigrationInProgressError } from '../../src/index.js'
import { GraphAdjacencyIndex } from '../../src/graph/graphAdjacencyIndex.js'

/** Force the graph provider to hold (or release) the migration lock. */
function setMigrating(brain: any, migrating: boolean): void {
  brain.graphIndex.isMigrating = () => migrating
}

describe('Migration LOCK (#18) — coordinated 7.x→8.0 auto-upgrade', () => {
  let brain: any

  beforeEach(async () => {
    brain = new Brainy({
      requireSubtype: false,
      storage: { type: 'memory' },
      migrationWaitTimeoutMs: 5000 // generous; timing tests clear well within it
    })
    await brain.init()
  })

  it('does not gate operations when no provider is migrating (fast path)', async () => {
    const id = await brain.add({ data: 'hello', type: NounType.Concept })
    expect(id).toBeTruthy()
    const status = await brain.getIndexStatus()
    expect(status.migrating).toBe(false)
    expect(status.migration).toBeUndefined()
  })

  it('getIndexStatus() reports migrating WITHOUT blocking (lock-exempt observability)', async () => {
    setMigrating(brain, true)
    const status = await brain.getIndexStatus() // must resolve, never hang
    expect(status.migrating).toBe(true)
    expect(status.migration).toBeDefined()
    expect(typeof status.migration.elapsedMs).toBe('number')
  })

  it('health() surfaces the migration as a warn check without blocking', async () => {
    setMigrating(brain, true)
    const h = await brain.health() // lock-exempt
    expect(h.overall).toBe('warn')
    expect(h.checks.some((c: any) => c.name === 'migration')).toBe(true)
  })

  it('checkHealth() answers during a migration (lock-exempt diagnostics)', async () => {
    setMigrating(brain, true)
    const r = await brain.checkHealth()
    expect(r).toHaveProperty('healthy')
  })

  it('blocks a write while migrating (poll path), then completes when the lock clears', async () => {
    setMigrating(brain, true)
    let resolved = false
    const p = brain
      .add({ data: 'queued write', type: NounType.Concept })
      .then((id: string) => {
        resolved = true
        return id
      })

    // Still blocked shortly after issue.
    await new Promise((r) => setTimeout(r, 40))
    expect(resolved).toBe(false)

    // Clearing the lock releases the queued write against the good indexes.
    setMigrating(brain, false)
    const id = await p
    expect(resolved).toBe(true)
    expect(id).toBeTruthy()
  })

  it('blocks a read while migrating, then releases when the flag clears (poll path)', async () => {
    let migrating = true
    brain.graphIndex.isMigrating = () => migrating

    const p = brain.find({ type: NounType.Concept }) // a read → gated
    let resolved = false
    p.then(() => {
      resolved = true
    })

    await new Promise((r) => setTimeout(r, 40))
    expect(resolved).toBe(false)

    // Clearing the flag lets the next poll release the read against good indexes.
    migrating = false
    await expect(p).resolves.toEqual([])
  })

  it('throws a retryable MigrationInProgressError when the lock outlives the timeout', async () => {
    const shortBrain: any = new Brainy({
      requireSubtype: false,
      storage: { type: 'memory' },
      migrationWaitTimeoutMs: 120
    })
    await shortBrain.init()
    setMigrating(shortBrain, true) // never clears

    await expect(shortBrain.find({ type: NounType.Concept })).rejects.toBeInstanceOf(
      MigrationInProgressError
    )
    try {
      await shortBrain.add({ data: 'x', type: NounType.Concept })
      throw new Error('expected MigrationInProgressError')
    } catch (e: any) {
      expect(e).toBeInstanceOf(MigrationInProgressError)
      expect(e.retryable).toBe(true)
      expect(typeof e.elapsedMs).toBe('number')
    }
  })

  it('stampBrainFormat() is NOT gated — cor clears the lock through it (no deadlock)', async () => {
    setMigrating(brain, true)
    // Must resolve, not hang, even while a migration is "in progress".
    await expect(brain.stampBrainFormat()).resolves.toBeUndefined()
  })

  it('close() is NOT gated — an instance can shut down mid-migration', async () => {
    setMigrating(brain, true)
    await expect(brain.close()).resolves.toBeUndefined()
  })

  it('C1: init() waits out a migration BEFORE VFS bootstrap (no deadlock, no failure)', async () => {
    // The critical regression: init()'s own VFS bootstrap (get/add/find on the
    // root) routes through the gated ensureInitialized. If a provider migrates
    // during init, init must WAIT for the lock to clear BEFORE bootstrapping VFS —
    // otherwise it deadlocks (< timeout) or throws MigrationInProgressError past
    // the timeout. We make the graph provider report migrating for a short window
    // spanning init, then clear it, and assert init completes and the brain works.
    const orig = (GraphAdjacencyIndex.prototype as any).isMigrating
    let migrating = true
    ;(GraphAdjacencyIndex.prototype as any).isMigrating = () => migrating
    const clearMigration = setTimeout(() => {
      migrating = false
    }, 60)
    try {
      const b: any = new Brainy({
        requireSubtype: false,
        storage: { type: 'memory' },
        migrationWaitTimeoutMs: 5000
      })
      await b.init() // must resolve once the migration clears — not hang, not throw
      expect(b.isInitialized).toBe(true)
      // VFS bootstrapped post-migration → normal write/read work end-to-end.
      const id = await b.add({ data: 'after upgrade', type: NounType.Concept })
      expect(id).toBeTruthy()
      expect(await b.get(id)).not.toBeNull()
      await b.close()
    } finally {
      clearTimeout(clearMigration)
      if (orig) (GraphAdjacencyIndex.prototype as any).isMigrating = orig
      else delete (GraphAdjacencyIndex.prototype as any).isMigrating
    }
  })
})
