/**
 * COMPREHENSIVE Integration Tests for Brainy 8.0 (Tier 1)
 *
 * Exercises the full public surface end-to-end against real code paths:
 * - find() vector / self-retrieval search
 * - find() metadata-only filtering (where + range operators)
 * - Triple Intelligence (getTripleIntelligence().find) — semantic + metadata + ranges
 * - getStats() entity/relationship statistics
 * - embed() vector generation
 * - add() / get() round-trips and consistency
 *
 * Runs under the DETERMINISTIC embedder (tests/setup-integration.ts): embedding
 * vectors are content-derived and stable, so self-identity cosine = 1.0 and two
 * DIFFERENT texts sit at ~0.23 similarity. Cross-text SEMANTIC RELEVANCE is NOT
 * meaningful here (that is covered by the Tier-2 real-model semantic suite); the
 * reliable signal is SELF-RETRIEVAL — querying a thing by its OWN text returns it
 * at the top. Assertions below are structural (counts, ids, shapes, metadata
 * filters, self-retrieval, stats), which is exactly what Tier 1 validates.
 */

import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { Brainy } from '../../src/brainy'
import { requiresMemory } from '../setup-integration.js'

describe('Brainy 8.0 Complete Feature Test (Tier 1, deterministic embedder)', () => {
  let brain: Brainy

  beforeAll(async () => {
    requiresMemory(16)

    // Full feature set; requireSubtype:false so plain add({type}) is allowed.
    brain = new Brainy({
      requireSubtype: false,
      storage: { type: 'memory' }
    })

    await brain.init()

    // Start with clean state
    await brain.clear()
  }, 300000)

  afterAll(async () => {
    if (brain) {
      try {
        await brain.clear()
        await brain.close()
      } catch (error) {
        console.warn('Cleanup warning:', error)
      }
    }

    if (global.gc) {
      global.gc()
    }
  }, 60000)

  describe('1. Core find() with vector search', () => {
    const searchItems = [
      'JavaScript is a programming language for web development',
      'Python is excellent for machine learning and AI applications',
      'React is a popular frontend framework for building user interfaces',
      'Vue.js provides reactive data binding for modern web apps',
      'Node.js enables server-side JavaScript development',
      'TensorFlow is used for deep learning and neural networks',
      'Docker containerizes applications for consistent deployment',
      'Kubernetes orchestrates containerized applications at scale',
      'PostgreSQL is a powerful relational database system',
      'MongoDB stores documents in a flexible NoSQL format'
    ]

    beforeAll(async () => {
      for (const item of searchItems) {
        await brain.add({ data: item, type: 'thing' })
      }
    })

    it('should self-retrieve a document by its own text (deterministic embedder)', async () => {
      // Self-retrieval: querying with an entity's OWN data text returns that entity
      // at the top. We use searchMode:'semantic' to exercise the pure vector path
      // (cosine); the default 'auto' mode is hybrid RRF, whose fused score is a
      // small rank-derived value rather than the raw cosine, so it cannot assert
      // the self-identity cosine = 1.0 property.
      const target = 'React is a popular frontend framework for building user interfaces'
      const results = await brain.find({ query: target, limit: 5, searchMode: 'semantic' })

      expect(results).toHaveLength(5)
      // Top hit is the exact item we queried for.
      expect(results[0].entity.data).toBe(target)
      // Self-identity cosine = 1.0 for the deterministic embedder.
      expect(results[0].score).toBeGreaterThan(0.99)

      // A second self-retrieval lands on its own item too.
      const dbTarget = 'PostgreSQL is a powerful relational database system'
      const dbResults = await brain.find({ query: dbTarget, limit: 3, searchMode: 'semantic' })
      expect(dbResults).toHaveLength(3)
      expect(dbResults[0].entity.data).toBe(dbTarget)
      expect(dbResults[0].score).toBeGreaterThan(0.99)
    })

    it('should return well-formed results with proper scoring and structure', async () => {
      const results = await brain.find({ query: searchItems[0], limit: 5 })
      expect(results).toHaveLength(5)

      // Every result has the documented Result shape.
      results.forEach(result => {
        expect(result).toHaveProperty('id')
        expect(result).toHaveProperty('score')
        expect(result).toHaveProperty('entity')
        expect(typeof result.score).toBe('number')
      })

      // Scores are sorted descending.
      for (let i = 0; i < results.length - 1; i++) {
        expect(results[i].score).toBeGreaterThanOrEqual(results[i + 1].score)
      }
    })

    it('should handle find() edge cases correctly', async () => {
      // A query with no vector criteria still returns the documented array shape.
      const limited = await brain.find({ query: searchItems[8], limit: 2 })
      expect(limited).toHaveLength(2)

      // Score ordering verification on a self-retrieval query.
      const ordered = await brain.find({ query: searchItems[2], limit: 5 })
      for (let i = 0; i < ordered.length - 1; i++) {
        expect(ordered[i].score).toBeGreaterThanOrEqual(ordered[i + 1].score)
      }
    })
  })

  describe('2. find() query-object form returns well-formed Result[]', () => {
    it('should return well-formed Result[] for query-object searches', async () => {
      // The query-OBJECT form ({ query }) is the deterministic, Tier-1 surface:
      // it embeds the query string and runs vector/hybrid search directly.
      const queries = [
        'frontend frameworks',
        'database technologies',
        'programming languages',
        'containerization and deployment'
      ]

      for (const query of queries) {
        const results = await brain.find({ query })

        expect(results).toBeInstanceOf(Array)
        expect(results.length).toBeGreaterThan(0)

        results.forEach(result => {
          expect(result).toHaveProperty('id')
          expect(result).toHaveProperty('score')
          expect(result).toHaveProperty('entity')
          expect(typeof result.score).toBe('number')
        })
      }
    })

    it('should honor the limit argument on query-object find()', async () => {
      const queries = [
        'frameworks for building websites',
        'tools for data analysis',
        'languages for machine learning',
        'databases for storing information'
      ]

      for (const query of queries) {
        const results = await brain.find({ query, limit: 3 })
        expect(results).toHaveLength(3)
        results.forEach(result => {
          expect(typeof result.score).toBe('number')
        })
      }
    })

    // TIER2: semantic relevance (real-model suite)
    //
    // The natural-language STRING form `find('show me frontend frameworks')` runs
    // the query through the NLP pattern library (220 embedded patterns) to infer
    // intent (filters, graph traversal, semantic terms). That inference depends on
    // the REAL embedding model — under the deterministic embedder the pattern
    // vectors are meaningless, so the parser produces non-semantic results. Asserting
    // that an imperative NL phrase returns the relevant entities is a real-model
    // concern; the deterministic Tier-1 surface for the same intent is the
    // query-object form exercised above.
    it.skip('should understand imperative natural-language string queries', async () => {
      const results = await brain.find('show me frontend frameworks')
      expect(results.length).toBeGreaterThan(0)
      expect(results.some(r => String(r.entity.data).toLowerCase().includes('react'))).toBe(true)
    })
  })

  describe('3. Triple Intelligence: semantic + metadata + range', () => {
    // NOTE: domain attributes live under non-reserved metadata keys. `type` is a
    // RESERVED entity field (it classifies the noun), so the framework's
    // frontend/backend kind is stored under `category` to remain queryable via
    // `where`.
    const frameworks = [
      { name: 'React', category: 'frontend', year: 2013, popularity: 95, language: 'JavaScript' },
      { name: 'Vue.js', category: 'frontend', year: 2014, popularity: 85, language: 'JavaScript' },
      { name: 'Angular', category: 'frontend', year: 2010, popularity: 75, language: 'TypeScript' },
      { name: 'Django', category: 'backend', year: 2005, popularity: 80, language: 'Python' },
      { name: 'FastAPI', category: 'backend', year: 2018, popularity: 70, language: 'Python' },
      { name: 'Express', category: 'backend', year: 2010, popularity: 90, language: 'JavaScript' }
    ]

    beforeAll(async () => {
      for (const fw of frameworks) {
        await brain.add({
          data: `${fw.name} framework for ${fw.category} development`,
          type: 'thing',
          metadata: fw
        })
      }
    })

    it('should combine semantic search with metadata + single-bound range filters (hard AND)', async () => {
      // find({ query, where }) resolves `where` to candidate IDs FIRST and runs the
      // vector search over only those candidates — so `where` is a hard AND filter
      // (true intersection), which is the 8.0 surface for "semantic + structured"
      // queries. searchMode:'semantic' keeps the score on the cosine path.
      const results = await brain.find({
        query: 'modern web development framework',
        where: {
          category: 'frontend',
          popularity: { greaterThan: 80 },
          year: { greaterThan: 2012 }
        },
        limit: 5,
        searchMode: 'semantic'
      })

      expect(results.length).toBeGreaterThan(0)
      expect(results.length).toBeLessThanOrEqual(5)

      // Every result must satisfy ALL metadata filters. With these fixtures only
      // React (95/2013) and Vue.js (85/2014) qualify.
      results.forEach(result => {
        expect(result.entity.metadata?.category).toBe('frontend')
        expect(result.entity.metadata?.popularity).toBeGreaterThan(80)
        expect(result.entity.metadata?.year).toBeGreaterThan(2012)
      })

      const names = results.map(r => r.entity.metadata?.name).sort()
      expect(names).toEqual(['React', 'Vue.js'])
    })

    it('should apply dual-bound range filters (greaterThan + lessThan on one field)', async () => {
      // year ∈ (2009, 2020) AND popularity ∈ (75, 95):
      //   React 2013/95 → 95 not < 95 → out
      //   Vue.js 2014/85 → in
      //   Angular 2010/75 → 75 not > 75 → out
      //   Django 2005/80 → 2005 not > 2009 → out
      //   FastAPI 2018/70 → 70 not > 75 → out
      //   Express 2010/90 → in
      const results = await brain.find({
        where: {
          year: { greaterThan: 2009, lessThan: 2020 },
          popularity: { greaterThan: 75, lessThan: 95 }
        },
        limit: 10
      })

      expect(results).toBeInstanceOf(Array)
      results.forEach(result => {
        expect(result.entity.metadata?.year).toBeGreaterThan(2009)
        expect(result.entity.metadata?.year).toBeLessThan(2020)
        expect(result.entity.metadata?.popularity).toBeGreaterThan(75)
        expect(result.entity.metadata?.popularity).toBeLessThan(95)
      })

      const names = results.map(r => r.entity.metadata?.name).sort()
      expect(names).toEqual(['Express', 'Vue.js'])
    })

    it('should expose Triple Intelligence RRF fusion (union ranking) shape', async () => {
      // getTripleIntelligence().find() is a soft RRF-FUSION ranker: it UNIONs the
      // vector ("like") and field ("where") signal sets and ranks by reciprocal
      // rank — it is NOT a hard intersection. The documented contract is: returns a
      // ranked array in which the field-matched entities are present.
      const triple = brain.getTripleIntelligence()
      const fused = await triple.find({
        like: 'web framework',
        where: { category: 'frontend' },
        limit: 10
      })

      expect(fused).toBeInstanceOf(Array)
      expect(fused.length).toBeGreaterThan(0)

      // All three frontend frameworks (the field-matched set) appear in the fused output.
      const names = new Set(fused.map(r => r.metadata?.name))
      expect(names.has('React')).toBe(true)
      expect(names.has('Vue.js')).toBe(true)
      expect(names.has('Angular')).toBe(true)

      // Every row carries a numeric fusion score and a full entity.
      fused.forEach(r => {
        expect(typeof r.score).toBe('number')
        expect(r.entity).toBeTruthy()
      })
    })
  })

  describe('4. Metadata filtering via find({ where })', () => {
    it('should perform metadata-only filtering with exact matches', async () => {
      // Metadata-only query (no vector search) — filters frameworks added above.
      const backendPython = await brain.find({
        where: { category: 'backend', language: 'Python' },
        limit: 10
      })

      expect(backendPython).toBeInstanceOf(Array)
      expect(backendPython.length).toBeGreaterThan(0)
      backendPython.forEach(result => {
        expect(result.entity.metadata?.category).toBe('backend')
        expect(result.entity.metadata?.language).toBe('Python')
      })

      // Django + FastAPI are the only Python backends.
      const names = backendPython.map(r => r.entity.metadata?.name).sort()
      expect(names).toEqual(['Django', 'FastAPI'])
    })

    it('should handle nested metadata on add() and retrieve it intact', async () => {
      const id = await brain.add({
        data: 'Advanced framework test',
        type: 'thing',
        metadata: {
          framework: {
            name: 'Next.js',
            version: '13.0',
            features: ['SSR', 'API', 'Routing']
          },
          tech: {
            language: 'JavaScript',
            runtime: 'Node.js'
          }
        }
      })

      const retrieved = await brain.get(id)
      expect(retrieved).toBeTruthy()
      expect(retrieved?.metadata?.framework?.name).toBe('Next.js')
      expect(retrieved?.metadata?.framework?.features).toEqual(['SSR', 'API', 'Routing'])
      expect(retrieved?.metadata?.tech?.runtime).toBe('Node.js')
    })
  })

  describe('5. Statistics and consistency', () => {
    it('should report growing entity statistics as data is added', async () => {
      const initialStats = await brain.getStats()
      const initialTotal = initialStats.entities.total

      // Add a batch and confirm the total grows by exactly that amount.
      const batchData = Array.from({ length: 20 }, (_, i) =>
        `Optimization test item ${i}: ${Math.random().toString(36).slice(2)}`
      )

      for (const item of batchData) {
        await brain.add({
          data: item,
          type: 'thing',
          metadata: { batch: 'optimization', index: Math.floor(Math.random() * 100) }
        })
      }

      const finalStats = await brain.getStats()
      expect(finalStats.entities.total).toBe(initialTotal + 20)
      expect(finalStats.entities.total).toBeGreaterThan(initialTotal)
    })

    it('should round-trip an entity through add/get and a metadata filter', async () => {
      // This brain already holds 80+ entities. Vector self-retrieval is exact only
      // at small scale (covered in section 1 and tests/integration/vector-recall.test.ts);
      // here we verify the DETERMINISTIC, exact round-trip paths: get() by id and the
      // metadata index via find({ where }).
      const data = 'Persistence test item with a unique phrase about consistency'
      const uniqueTag = `persist-${Date.now()}-${Math.random().toString(36).slice(2)}`
      const testId = await brain.add({
        data,
        type: 'thing',
        metadata: { test: 'persistence', tag: uniqueTag }
      })

      // Immediate metadata retrieval by id (exact).
      const retrieved = await brain.get(testId)
      expect(retrieved).toBeTruthy()
      expect(retrieved?.metadata?.test).toBe('persistence')
      expect(retrieved?.data).toBe(data)

      // Metadata-index lookup by a unique tag returns exactly this entity (exact).
      const byTag = await brain.find({ where: { tag: uniqueTag }, limit: 5 })
      expect(byTag).toHaveLength(1)
      expect(byTag[0].id).toBe(testId)
    })
  })

  describe('6. Embedding generation', () => {
    it('should produce a 384-dimension finite embedding vector', async () => {
      const embedding = await brain.embed('test embedding generation')
      expect(embedding).toBeInstanceOf(Array)
      expect(embedding).toHaveLength(384)

      // Tier-1 (deterministic embedder): assert each component is a finite number
      // of reasonable magnitude. The normalized (-1, 1) unit-vector property is a
      // real-model characteristic verified by the Tier-2 semantic suite, not by
      // the content-derived deterministic stand-in.
      embedding.forEach(val => {
        expect(typeof val).toBe('number')
        expect(Number.isFinite(val)).toBe(true)
        expect(Math.abs(val)).toBeLessThan(2)
      })
    })

    it('should produce identical embeddings for identical input (determinism)', async () => {
      const a = await brain.embed('determinism check phrase')
      const b = await brain.embed('determinism check phrase')
      expect(a).toEqual(b)
    })
  })

  describe('7. Bulk operations', () => {
    it('should ingest a batch with metadata and remain queryable', async () => {
      const performanceData = Array.from({ length: 50 }, (_, i) => ({
        content: `Bulk test ${i}: ${Array.from({ length: 20 }, () =>
          Math.random().toString(36).slice(2)).join(' ')}`,
        category: ['frontend', 'backend', 'database', 'ai', 'devops'][i % 5],
        priority: Math.floor(Math.random() * 100),
        timestamp: Date.now() + i
      }))

      const beforeStats = await brain.getStats()
      const ids: string[] = []
      for (const item of performanceData) {
        const id = await brain.add({
          data: item.content,
          type: 'thing',
          metadata: {
            category: item.category,
            priority: item.priority,
            timestamp: item.timestamp
          }
        })
        ids.push(id)
      }
      expect(ids).toHaveLength(50)

      // All 50 are counted (exact).
      const afterStats = await brain.getStats()
      expect(afterStats.entities.total).toBe(beforeStats.entities.total + 50)

      // Each ingested item is retrievable by id and carries its metadata (exact,
      // deterministic — independent of approximate vector recall at this scale).
      const firstBack = await brain.get(ids[0])
      expect(firstBack).toBeTruthy()
      expect(firstBack?.data).toBe(performanceData[0].content)
      expect(firstBack?.metadata?.category).toBe(performanceData[0].category)

      const lastBack = await brain.get(ids[49])
      expect(lastBack).toBeTruthy()
      expect(lastBack?.data).toBe(performanceData[49].content)

      // The batch is queryable by metadata: every 5th item is 'frontend'.
      const frontendInBatch = await brain.find({
        where: { category: 'frontend', priority: { greaterThanOrEqual: 0 } },
        limit: 100
      })
      // At least the 10 frontend items from this batch (indices 0,5,10,...,45).
      const frontendIds = new Set(frontendInBatch.map(r => r.id))
      const batchFrontendIds = ids.filter((_, i) => i % 5 === 0)
      batchFrontendIds.forEach(id => expect(frontendIds.has(id)).toBe(true))
    })
  })

  describe('8. Final integration verification', () => {
    it('should pass comprehensive cross-API verification', async () => {
      // 1. find() vector search returns well-formed, score-sorted results. (Exact
      //    self-retrieval at small scale is covered in section 1; this brain holds
      //    130+ entities, beyond the deterministic embedder's exact-recall envelope.)
      const searchResults = await brain.find({ query: 'web development framework', limit: 5 })
      expect(searchResults).toHaveLength(5)
      searchResults.forEach(r => {
        expect(typeof r.score).toBe('number')
        expect(r.entity).toBeTruthy()
      })

      // 2. Exact lookup by unique metadata still resolves a known entity.
      const vue = await brain.find({ where: { name: 'Vue.js' }, limit: 1 })
      expect(vue).toHaveLength(1)
      expect(vue[0].entity.metadata?.category).toBe('frontend')

      // 3. find() query-object form returns the documented array shape.
      const findResults = await brain.find({ query: 'frontend technologies', limit: 3 })
      expect(findResults).toHaveLength(3)

      // 4. find({ query, where }) hard-AND composition (semantic + metadata filter).
      const frontend = await brain.find({
        query: 'web framework',
        where: { category: 'frontend' },
        limit: 3,
        searchMode: 'semantic'
      })
      expect(frontend).toBeInstanceOf(Array)
      expect(frontend.length).toBeGreaterThan(0)
      frontend.forEach(r => expect(r.entity.metadata?.category).toBe('frontend'))

      // 5. Metadata-only filtering.
      const backend = await brain.find({ where: { category: 'backend' }, limit: 50 })
      expect(backend).toBeInstanceOf(Array)
      expect(backend.length).toBeGreaterThan(0)
      backend.forEach(r => expect(r.entity.metadata?.category).toBe('backend'))

      // 6. Statistics.
      const finalStats = await brain.getStats()
      expect(finalStats.entities.total).toBeGreaterThan(50)
    })
  })
})
