/**
 * Core Functionality Tests
 * Tests core Brainy features as a consumer would use them
 */

import { describe, it, expect, beforeAll } from 'vitest'

/**
 * Helper function to create a 512-dimensional vector for testing
 * @param primaryIndex The index to set to 1.0, all other indices will be 0.0
 * @returns A 512-dimensional vector with a single 1.0 value at the specified index
 */
function createTestVector(primaryIndex: number = 0): number[] {
  const vector = new Array(512).fill(0)
  vector[primaryIndex % 512] = 1.0
  return vector
}

describe('Brainy Core Functionality', () => {
  let brainy: any

  beforeAll(async () => {
    // Load brainy library as a consumer would
    brainy = await import('../dist/unified.js')
  })

  describe('Library Exports', () => {
    it('should export BrainyData class', () => {
      expect(brainy.BrainyData).toBeDefined()
      expect(typeof brainy.BrainyData).toBe('function')
    })

    it('should export environment detection functions', () => {
      expect(typeof brainy.isBrowser).toBe('function')
      expect(typeof brainy.isNode).toBe('function')
      expect(typeof brainy.isWebWorker).toBe('function')
      expect(typeof brainy.areWebWorkersAvailable).toBe('function')
      expect(typeof brainy.isThreadingAvailable).toBe('function')
    })

    it('should export embedding function creator', () => {
      expect(typeof brainy.createEmbeddingFunction).toBe('function')
    })

    it('should export environment object', () => {
      expect(brainy.environment).toBeDefined()
      expect(typeof brainy.environment).toBe('object')
      expect(brainy.environment).toHaveProperty('isBrowser')
      expect(brainy.environment).toHaveProperty('isNode')
      expect(brainy.environment).toHaveProperty('isServerless')
    })
  })

  describe('BrainyData Configuration', () => {
    it('should create instance with minimal configuration', () => {
      const data = new brainy.BrainyData({})

      expect(data).toBeDefined()
      expect(data.dimensions).toBe(512)
    })

    it('should create instance with full configuration', () => {
      const data = new brainy.BrainyData({
        metric: 'cosine',
        maxConnections: 32,
        efConstruction: 200,
        storage: 'memory'
      })

      expect(data).toBeDefined()
      expect(data.dimensions).toBe(512)
    })

    it('should not throw with valid configuration parameters', () => {
      // Dimensions are now fixed at 512 and not configurable
      expect(() => {
        new brainy.BrainyData({
          metric: 'cosine'
        })
      }).not.toThrow()

      expect(() => {
        new brainy.BrainyData({
          metric: 'euclidean'
        })
      }).not.toThrow()
    })

    it('should use default values for optional parameters', () => {
      const data = new brainy.BrainyData({})

      expect(data.dimensions).toBe(512)
      // Should have reasonable defaults for other parameters
      expect(data.maxConnections).toBeGreaterThan(0)
      expect(data.efConstruction).toBeGreaterThan(0)
    })
  })

  describe('Vector Operations', () => {
    it('should handle vector addition and search', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()
      await data.clear() // Clear any existing data

      // Add vectors using helper function
      await data.add(createTestVector(0), { id: 'v1', label: 'x-axis' })
      await data.add(createTestVector(1), { id: 'v2', label: 'y-axis' })
      await data.add(createTestVector(2), { id: 'v3', label: 'z-axis' })

      // Search for similar vector
      const results = await data.search(createTestVector(0), 1)

      expect(results).toBeDefined()
      expect(results.length).toBe(1)
      expect(results[0].metadata.id).toBe('v1')
    })

    it('should handle batch vector operations', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()
      await data.clear() // Clear any existing data

      // Add multiple vectors
      const vectors = [
        { vector: createTestVector(10), metadata: { id: 'batch1' } },
        { vector: createTestVector(20), metadata: { id: 'batch2' } },
        { vector: createTestVector(30), metadata: { id: 'batch3' } }
      ]

      for (const { vector, metadata } of vectors) {
        await data.add(vector, metadata)
      }

      // Search should return results
      const results = await data.search(createTestVector(15), 3)
      expect(results.length).toBe(3)
    })

    it('should handle different distance metrics', async () => {
      const euclideanData = new brainy.BrainyData({
        metric: 'euclidean'
      })

      const cosineData = new brainy.BrainyData({
        metric: 'cosine'
      })

      await euclideanData.init()
      await cosineData.init()

      // Clear any existing data to ensure test isolation
      await euclideanData.clear()
      await cosineData.clear()

      const vector = createTestVector(5)
      const metadata = { id: 'test' }

      await euclideanData.add(vector, metadata)
      await cosineData.add(vector, metadata)

      const euclideanResults = await euclideanData.search(vector, 1)
      const cosineResults = await cosineData.search(vector, 1)

      expect(euclideanResults.length).toBe(1)
      expect(cosineResults.length).toBe(1)

      // Both should find the exact match, but distances might differ
      expect(euclideanResults[0].metadata.id).toBe('test')
      expect(cosineResults[0].metadata.id).toBe('test')
    })
  })

  describe('Text Processing', () => {
    it(
      'should handle text items with embedding function',
      async () => {
        const embeddingFunction = brainy.createEmbeddingFunction()

        const data = new brainy.BrainyData({
          embeddingFunction,
          dimensions: 512, // Universal Sentence Encoder produces 512-dimensional vectors
          metric: 'cosine',
          storage: {
            forceMemoryStorage: true
          }
        })

        await data.init()

        // Add text items
        await data.addItem('Hello world', { id: 'greeting', type: 'text' })
        await data.addItem('Goodbye world', { id: 'farewell', type: 'text' })

        // Search with text
        const results = await data.search('Hi there', 1)

        expect(results).toBeDefined()
        expect(results.length).toBeGreaterThan(0)
        expect(results[0].metadata).toHaveProperty('id')
      },
      globalThis.testUtils?.timeout || 30000
    )

    it(
      'should handle mixed vector and text operations',
      async () => {
        const embeddingFunction = brainy.createEmbeddingFunction()

        const data = new brainy.BrainyData({
          embeddingFunction,
          dimensions: 512, // Universal Sentence Encoder produces 512-dimensional vectors
          metric: 'cosine'
        })

        await data.init()

        // Add text item
        await data.addItem('Machine learning', { id: 'text1', type: 'text' })

        // Add vector item (using embedding of similar text)
        const embedding = await embeddingFunction('Artificial intelligence')
        await data.add(embedding, { id: 'vector1', type: 'vector' })

        // Search should find both
        const results = await data.search('AI and ML', 2)

        expect(results).toBeDefined()
        expect(results.length).toBeGreaterThan(0)
      },
      globalThis.testUtils?.timeout || 30000
    )
  })

  describe('Error Handling', () => {
    it('should handle invalid vector dimensions', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()

      // Try to add vector with wrong dimensions
      await expect(data.add([1, 2], { id: 'wrong' })).rejects.toThrow()
      await expect(
        data.add(new Array(100).fill(0), { id: 'wrong' })
      ).rejects.toThrow()
    })

    it('should handle search before initialization', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      // Try to search without initialization
      await expect(data.search(createTestVector(0), 1)).rejects.toThrow()
    })

    it('should handle empty search results gracefully', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()
      await data.clear() // Clear any existing data

      // Search in empty database
      const results = await data.search(createTestVector(0), 1)
      expect(results).toBeDefined()
      expect(Array.isArray(results)).toBe(true)
      expect(results.length).toBe(0)
    })
  })

  describe('Performance and Scalability', () => {
    it('should handle moderate number of vectors efficiently', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()

      const startTime = Date.now()

      // Add 100 test vectors
      for (let i = 0; i < 100; i++) {
        await data.add(createTestVector(i), { id: `item_${i}`, index: i })
      }

      const addTime = Date.now() - startTime

      // Search should be fast
      const searchStart = Date.now()
      const results = await data.search(createTestVector(50), 10)
      const searchTime = Date.now() - searchStart

      expect(results.length).toBeLessThanOrEqual(10)
      expect(addTime).toBeLessThan(10000) // Should complete within 10 seconds
      expect(searchTime).toBeLessThan(1000) // Search should be under 1 second
    })

    it('should maintain search quality with more data', async () => {
      // Create database with proper configuration for testing
      const db = new brainy.BrainyData({
        embeddingFunction: brainy.createEmbeddingFunction(),
        metric: 'cosine'
      })

      await db.init()
      await db.clear() // Clear any existing data

      // Add known data
      await db.add('known data', { id: 'known' })

      // Add noise data
      for (let i = 0; i < 100; i++) {
        await db.add(`noise_${i}`, { id: `noise_${i}` })
      }

      // Perform search using the correct method
      const results = await db.search('known data', 10)

      // Debugging output
      console.log(
        'Search results:',
        results.map((r) => r.metadata?.id)
      )

      // Assertions
      expect(results.length).toBeGreaterThan(0)
      // The 'known' item should be found in the results, but not necessarily first
      // due to potential variations in embedding similarity calculations
      const knownItemFound = results.some((r) => r.metadata?.id === 'known')
      expect(knownItemFound).toBe(true)
    })
  })

  describe('Database Statistics', () => {
    it('should provide accurate statistics about the database', async () => {
      const data = new brainy.BrainyData({
        metric: 'euclidean'
      })

      await data.init()
      await data.clear() // Clear any existing data

      // Add some vectors (nouns)
      await data.add(createTestVector(0), { id: 'v1', label: 'x-axis' })
      await data.add(createTestVector(1), { id: 'v2', label: 'y-axis' })
      await data.add(createTestVector(2), { id: 'v3', label: 'z-axis' })

      // Add some connections (verbs)
      await data.connect('v1', 'v2', 'related_to')
      await data.connect('v2', 'v3', 'related_to')

      // Get statistics
      const stats = await data.getStatistics()

      // Debug: Log all nouns in the database
      const allNouns = await data.getAllNouns()
      console.log(
        'All nouns in database:',
        allNouns.map((n) => n.id)
      )

      // Debug: Log all verbs in the database
      const allVerbs = await data.getAllVerbs()
      console.log(
        'All verbs in database:',
        allVerbs.map((v) => v.id)
      )

      // Debug: Log the verb IDs set used in getStatistics
      const verbIds = new Set(allVerbs.map((verb) => verb.id))
      console.log('Verb IDs set:', Array.from(verbIds))

      // Verify statistics
      expect(stats).toBeDefined()
      expect(stats).toHaveProperty('nounCount')
      expect(stats).toHaveProperty('verbCount')
      expect(stats).toHaveProperty('metadataCount')
      expect(stats).toHaveProperty('hnswIndexSize')

      // Verify counts
      expect(stats.nounCount).toBe(3)
      expect(stats.verbCount).toBe(2)
      expect(stats.hnswIndexSize).toBe(1)
    })
  })
})
