import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { createAddParams } from '../../helpers/test-factory'
import { NounType, VerbType } from '../../../src/types/graphTypes'

/**
 * Comprehensive test suite for Brainy's built-in augmentation system
 * Tests the actual augmentation functionality that's available in production
 */

describe('Brainy Built-in Augmentations', () => {
  let brain: Brainy<any>

  beforeEach(async () => {
    brain = new Brainy({
      storage: { type: 'memory' },
      augmentations: {
        cache: { enabled: true, maxSize: 1000 },
        display: { enabled: true },
        metrics: { enabled: true }
      }
    })
    await brain.init()
  })

  describe('1. Augmentation Registry', () => {
    it('should list all registered augmentations', async () => {
      const augmentations = brain.augmentations.list()
      expect(Array.isArray(augmentations)).toBe(true)
      expect(augmentations.length).toBeGreaterThan(0)
    })

    it('should have default augmentations', async () => {
      const augmentations = brain.augmentations.list()
      expect(augmentations).toContain('cache')
      expect(augmentations).toContain('display')
      expect(augmentations).toContain('metrics')
    })

    it('should get augmentation by name', async () => {
      const cacheAug = brain.augmentations.get('cache')
      expect(cacheAug).toBeDefined()
      expect(cacheAug?.name).toBe('cache')

      const displayAug = brain.augmentations.get('display')
      expect(displayAug).toBeDefined()
      expect(displayAug?.name).toBe('display')

      const metricsAug = brain.augmentations.get('metrics')
      expect(metricsAug).toBeDefined()
      expect(metricsAug?.name).toBe('metrics')
    })

    it('should check if augmentation exists', async () => {
      expect(brain.augmentations.has('cache')).toBe(true)
      expect(brain.augmentations.has('display')).toBe(true)
      expect(brain.augmentations.has('metrics')).toBe(true)
      expect(brain.augmentations.has('non-existent')).toBe(false)
    })

    it('should return undefined for non-existent augmentation', async () => {
      const nonExistent = brain.augmentations.get('does-not-exist')
      expect(nonExistent).toBeUndefined()
    })
  })

  describe('2. Cache Augmentation', () => {
    it('should cache get operations', async () => {
      const id = await brain.add(createAddParams({
        data: 'cached entity',
        metadata: { cached: true }
      }))

      // First get - should load from storage
      const start1 = Date.now()
      const entity1 = await brain.get(id)
      const duration1 = Date.now() - start1

      // Second get - should be cached (faster)
      const start2 = Date.now()
      const entity2 = await brain.get(id)
      const duration2 = Date.now() - start2

      // Compare key properties instead of full object equality
      expect(entity1?.id).toBe(entity2?.id)
      expect(entity1?.data).toBe(entity2?.data)
      expect(entity1?.data).toBe('cached entity')
      expect(entity1?.metadata?.cached).toBe(true)

      // Cache should make second call faster (though this might not be reliable)
      // expect(duration2).toBeLessThanOrEqual(duration1)
    })

    it('should handle cache misses gracefully', async () => {
      // Try to get non-existent entity
      const nonExistent = await brain.get('fake-id')
      expect(nonExistent).toBeNull()
    })

    it('should work with find operations', async () => {
      await brain.add(createAddParams({ data: 'findable content 1' }))
      await brain.add(createAddParams({ data: 'findable content 2' }))

      // First search
      const results1 = await brain.find({ query: 'findable' })

      // Second search (may be cached)
      const results2 = await brain.find({ query: 'findable' })

      expect(results1.length).toBe(results2.length)
      expect(results1.length).toBeGreaterThanOrEqual(2)
    })
  })

  describe('3. Display Augmentation', () => {
    it('should provide display functionality for entities', async () => {
      const id = await brain.add(createAddParams({
        data: 'Entity with display capabilities',
        type: NounType.Document,
        metadata: {
          title: 'Test Document',
          author: 'Test Author',
          category: 'test'
        }
      }))

      const entity = await brain.get(id)
      expect(entity).toBeDefined()

      // Check if getDisplay method is available
      if (entity && typeof entity.getDisplay === 'function') {
        const display = entity.getDisplay()
        expect(display).toBeDefined()
        expect(typeof display).toBe('object')
      }
    })

    it('should work with different entity types', async () => {
      const personId = await brain.add(createAddParams({
        data: 'John Doe',
        type: NounType.Person,
        metadata: { role: 'developer', age: 30 }
      }))

      const locationId = await brain.add(createAddParams({
        data: 'San Francisco',
        type: NounType.Location,
        metadata: { country: 'USA', population: 900000 }
      }))

      const person = await brain.get(personId)
      const location = await brain.get(locationId)

      expect(person).toBeDefined()
      expect(location).toBeDefined()

      // Display should work for all types
      if (person && typeof person.getDisplay === 'function') {
        const personDisplay = person.getDisplay()
        expect(personDisplay).toBeDefined()
      }

      if (location && typeof location.getDisplay === 'function') {
        const locationDisplay = location.getDisplay()
        expect(locationDisplay).toBeDefined()
      }
    })

    it('should handle entities without metadata', async () => {
      const id = await brain.add(createAddParams({
        data: 'Simple entity without metadata'
      }))

      const entity = await brain.get(id)
      expect(entity).toBeDefined()

      if (entity && typeof entity.getDisplay === 'function') {
        const display = entity.getDisplay()
        expect(display).toBeDefined()
      }
    })

    it('should provide schema information', async () => {
      const id = await brain.add(createAddParams({
        data: 'Entity with schema',
        metadata: {
          stringField: 'text',
          numberField: 42,
          booleanField: true,
          arrayField: [1, 2, 3]
        }
      }))

      const entity = await brain.get(id)
      expect(entity).toBeDefined()

      if (entity && typeof entity.getSchema === 'function') {
        const schema = entity.getSchema()
        expect(schema).toBeDefined()
        expect(typeof schema).toBe('object')
      }
    })
  })

  describe('4. Metrics Augmentation', () => {
    it('should track operations without interfering', async () => {
      // Perform various operations
      const id1 = await brain.add(createAddParams({ data: 'metrics test 1' }))
      const id2 = await brain.add(createAddParams({ data: 'metrics test 2' }))
      const id3 = await brain.add(createAddParams({ data: 'metrics test 3' }))

      // Read operations
      await brain.get(id1)
      await brain.get(id2)
      await brain.find({ query: 'metrics' })

      // Write operations
      await brain.update({ id: id1, data: 'updated metrics test 1' })
      await brain.delete(id3)

      // Relationship operations
      await brain.relate({
        from: id1,
        to: id2,
        type: VerbType.RelatedTo
      })

      // All operations should complete successfully
      const entity1 = await brain.get(id1)
      const entity2 = await brain.get(id2)
      const entity3 = await brain.get(id3)

      expect(entity1).toBeDefined()
      expect(entity1?.data).toBe('updated metrics test 1')
      expect(entity2).toBeDefined()
      expect(entity3).toBeNull() // Deleted

      // Check relationships (may be empty if relationship storage isn't implemented)
      const relations = await brain.getRelations(id1)
      expect(Array.isArray(relations)).toBe(true)
    })

    it('should handle high-frequency operations', async () => {
      // Create many entities rapidly
      const promises = Array.from({ length: 50 }, (_, i) =>
        brain.add(createAddParams({
          data: `High frequency test ${i}`,
          metadata: { index: i }
        }))
      )

      const ids = await Promise.all(promises)
      expect(ids.length).toBe(50)

      // Perform many reads
      const readPromises = ids.map(id => brain.get(id))
      const entities = await Promise.all(readPromises)

      expect(entities.length).toBe(50)
      expect(entities.every(e => e !== null)).toBe(true)
    })

    it('should handle error scenarios gracefully', async () => {
      // Try invalid operations
      await expect(brain.get('invalid-id')).resolves.toBeNull()
      await expect(brain.delete('invalid-id')).resolves.not.toThrow()

      // Try invalid find parameters
      await expect(brain.find({
        query: 'test',
        limit: -1
      } as any)).rejects.toThrow()

      await expect(brain.find({
        query: 'test',
        offset: -1
      } as any)).rejects.toThrow()
    })
  })

  describe('5. Augmentation Integration', () => {
    it('should apply all augmentations to add operations', async () => {
      const id = await brain.add(createAddParams({
        data: 'Full integration test',
        type: NounType.Document,
        metadata: {
          title: 'Integration Test',
          priority: 'high',
          tags: ['test', 'integration']
        }
      }))

      expect(id).toBeDefined()
      expect(typeof id).toBe('string')

      // Verify entity was created correctly
      const entity = await brain.get(id)
      expect(entity).toBeDefined()
      expect(entity?.data).toBe('Full integration test')
      expect(entity?.type).toBe(NounType.Document)
      expect(entity?.metadata?.title).toBe('Integration Test')
    })

    it('should apply all augmentations to find operations', async () => {
      // Add test data
      const id1 = await brain.add(createAddParams({
        data: 'Integration search test 1',
        metadata: { category: 'integration' }
      }))

      const id2 = await brain.add(createAddParams({
        data: 'Integration search test 2',
        metadata: { category: 'integration' }
      }))

      await brain.add(createAddParams({
        data: 'Different content',
        metadata: { category: 'other' }
      }))

      // Test text search
      const textResults = await brain.find({ query: 'Integration search' })
      expect(textResults.length).toBeGreaterThanOrEqual(2)

      // Test metadata filtering
      const categoryResults = await brain.find({
        query: '',
        where: { category: 'integration' }
      })
      expect(categoryResults.length).toBe(2)

      // Verify entities have display capabilities
      const firstResult = textResults[0]
      if (firstResult?.entity && typeof firstResult.entity.getDisplay === 'function') {
        const display = firstResult.entity.getDisplay()
        expect(display).toBeDefined()
      }
    })

    it('should apply augmentations to update operations', async () => {
      const id = await brain.add(createAddParams({
        data: 'Original data',
        metadata: { version: 1 }
      }))

      // Update should work with all augmentations
      await brain.update({
        id,
        data: 'Updated data',
        metadata: { version: 2, updated: true }
      })

      const updatedEntity = await brain.get(id)
      expect(updatedEntity?.data).toBe('Updated data')
      expect(updatedEntity?.metadata?.version).toBe(2)
      expect(updatedEntity?.metadata?.updated).toBe(true)
    })

    it('should apply augmentations to relationship operations', async () => {
      const id1 = await brain.add(createAddParams({ data: 'Entity 1' }))
      const id2 = await brain.add(createAddParams({ data: 'Entity 2' }))

      // Create relationship
      await brain.relate({
        from: id1,
        to: id2,
        type: VerbType.RelatedTo,
        metadata: { strength: 0.8 }
      })

      // Verify relationship exists (may be empty depending on storage implementation)
      const relations = await brain.getRelations(id1)
      expect(Array.isArray(relations)).toBe(true)

      // If relationships are stored, verify the properties
      if (relations.length > 0) {
        const relation = relations.find(r => r.to === id2)
        if (relation) {
          expect(relation.type).toBe(VerbType.RelatedTo)
          expect(relation.metadata?.strength).toBe(0.8)
        }
      }
    })
  })

  describe('6. Performance with Augmentations', () => {
    it('should perform well with many entities', async () => {
      const start = Date.now()

      // Create 100 entities
      const createPromises = Array.from({ length: 100 }, (_, i) =>
        brain.add(createAddParams({
          data: `Performance test entity ${i}`,
          metadata: { index: i, batch: 'performance' }
        }))
      )

      const ids = await Promise.all(createPromises)
      const createDuration = Date.now() - start

      expect(ids.length).toBe(100)
      expect(createDuration).toBeLessThan(5000) // Should complete in under 5 seconds

      // Search should be fast
      const searchStart = Date.now()
      const searchResults = await brain.find({
        query: 'Performance test',
        limit: 50
      })
      const searchDuration = Date.now() - searchStart

      expect(searchResults.length).toBeGreaterThan(0)
      expect(searchResults.length).toBeLessThanOrEqual(50) // May return fewer due to relevance
      expect(searchDuration).toBeLessThan(1000) // Should complete in under 1 second
    })

    it('should handle concurrent operations efficiently', async () => {
      const start = Date.now()

      // Perform 50 concurrent operations
      const operations = Array.from({ length: 50 }, (_, i) => {
        if (i % 4 === 0) {
          return brain.add(createAddParams({ data: `Concurrent add ${i}` }))
        } else if (i % 4 === 1) {
          return brain.find({ query: 'concurrent', limit: 5 })
        } else if (i % 4 === 2) {
          return brain.add(createAddParams({ data: `Another add ${i}` }))
        } else {
          return brain.find({ query: 'test', limit: 3 })
        }
      })

      const results = await Promise.all(operations)
      const duration = Date.now() - start

      expect(results.length).toBe(50)
      expect(duration).toBeLessThan(3000) // Should handle concurrency well

      // Verify some results
      const addResults = results.filter(r => typeof r === 'string')
      const findResults = results.filter(r => Array.isArray(r))

      expect(addResults.length).toBeGreaterThan(0)
      expect(findResults.length).toBeGreaterThan(0)
    })
  })

  describe('7. Error Handling with Augmentations', () => {
    it('should handle errors gracefully without breaking augmentations', async () => {
      // These should not crash the system
      await expect(brain.get('')).resolves.toBeNull()
      await expect(brain.update({
        id: 'non-existent',
        data: 'new data'
      })).rejects.toThrow()

      // Normal operations should still work
      const id = await brain.add(createAddParams({ data: 'After error test' }))
      const entity = await brain.get(id)
      expect(entity?.data).toBe('After error test')
    })

    it('should handle edge case data gracefully', async () => {
      // Add entity with minimal but valid data
      const id = await brain.add(createAddParams({
        data: 'minimal', // Valid minimal data
        metadata: { test: true }
      }))

      expect(id).toBeDefined()

      const entity = await brain.get(id)
      expect(entity).toBeDefined()
      expect(entity?.data).toBe('minimal')
    })
  })

  describe('8. Configuration and Customization', () => {
    it('should respect augmentation configuration', async () => {
      // Test that augmentations can be configured
      const configuredBrain = new Brainy({
        storage: { type: 'memory' },
        augmentations: {
          cache: {
            enabled: true,
            maxSize: 50,
            ttl: 60000
          },
          display: {
            enabled: true,
            lazyComputation: true
          },
          metrics: {
            enabled: true
          }
        }
      })

      await configuredBrain.init()

      // Should work with custom configuration
      const id = await configuredBrain.add(createAddParams({
        data: 'Configured test'
      }))

      const entity = await configuredBrain.get(id)
      expect(entity?.data).toBe('Configured test')

      await configuredBrain.close()
    })

    it('should work with disabled augmentations', async () => {
      const minimalBrain = new Brainy({
        storage: { type: 'memory' },
        augmentations: {
          cache: { enabled: false },
          display: { enabled: false },
          metrics: { enabled: false }
        }
      })

      await minimalBrain.init()

      // Should still work with augmentations disabled
      const id = await minimalBrain.add(createAddParams({
        data: 'Minimal test'
      }))

      const entity = await minimalBrain.get(id)
      expect(entity?.data).toBe('Minimal test')

      await minimalBrain.close()
    })
  })
})