/**
 * Batch Operations Test Suite for Brainy v3.0
 * 
 * Comprehensive testing of batch operations including:
 * - addMany: Bulk entity creation
 * - deleteMany: Bulk deletion
 * - Performance validation at scale
 * - Memory efficiency testing
 * - Error recovery scenarios
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy'
import { performance } from 'perf_hooks'

interface BatchMetrics {
  operation: string
  totalItems: number
  successCount: number
  failureCount: number
  duration: number
  throughput: number
  memoryUsed: number
  errors: string[]
}

class BatchMetricsCollector {
  private metrics: BatchMetrics[] = []

  recordBatch(
    operation: string,
    totalItems: number,
    results: any[],
    duration: number,
    memoryUsed: number
  ): BatchMetrics {
    const successCount = results.filter(r => 
      r?.status === 'fulfilled' || r === true || (r && !r.error)
    ).length
    const failureCount = totalItems - successCount
    const errors = results
      .filter(r => r?.status === 'rejected' || r?.error)
      .map(r => r?.reason?.message || r?.error || 'Unknown error')
      .slice(0, 5) // Limit to first 5 errors

    const metric: BatchMetrics = {
      operation,
      totalItems,
      successCount,
      failureCount,
      duration,
      throughput: (totalItems / duration) * 1000,
      memoryUsed,
      errors
    }

    this.metrics.push(metric)
    return metric
  }

  getMetrics(): BatchMetrics[] {
    return this.metrics
  }

  generateReport(): void {
    console.log('\n=== Batch Operations Performance Report ===\n')
    console.table(this.metrics.map(m => ({
      Operation: m.operation,
      'Total Items': m.totalItems,
      'Success': `${m.successCount}/${m.totalItems} (${((m.successCount/m.totalItems)*100).toFixed(1)}%)`,
      'Duration (ms)': m.duration.toFixed(2),
      'Throughput (items/s)': m.throughput.toFixed(0),
      'Memory (MB)': (m.memoryUsed / 1024 / 1024).toFixed(2),
      'Errors': m.errors.length > 0 ? m.errors[0] : 'None'
    })))

    // Summary statistics
    const totalOps = this.metrics.reduce((sum, m) => sum + m.totalItems, 0)
    const totalSuccess = this.metrics.reduce((sum, m) => sum + m.successCount, 0)
    const avgThroughput = this.metrics.reduce((sum, m) => sum + m.throughput, 0) / this.metrics.length

    console.log('\nSummary:')
    console.log(`Total Operations: ${totalOps}`)
    console.log(`Success Rate: ${((totalSuccess/totalOps)*100).toFixed(2)}%`)
    console.log(`Average Throughput: ${avgThroughput.toFixed(0)} items/sec`)
  }
}

describe('Batch Operations - Public API Testing', () => {
  let brainy: Brainy
  let metricsCollector: BatchMetricsCollector

  beforeEach(async () => {
    brainy = new Brainy({
      storage: { type: 'memory' }
    })
    await brainy.init()
    metricsCollector = new BatchMetricsCollector()
  })

  afterEach(async () => {
    await brainy.close()
    if (global.gc) global.gc()
  })

  describe('addMany - Bulk Entity Creation', () => {
    it('should add 100 entities efficiently', async () => {
      const entities = Array(100).fill(null).map((_, i) => ({
        data: `Batch entity ${i}: Test content for bulk operations`,
        type: 'document' as const,
        metadata: {
          batchIndex: i,
          batchId: 'batch-100',
          timestamp: Date.now()
        }
      }))

      const startMemory = process.memoryUsage().heapUsed
      const startTime = performance.now()

      const result = await brainy.addMany({ items: entities })

      const duration = performance.now() - startTime
      const memoryUsed = process.memoryUsage().heapUsed - startMemory

      const metric = metricsCollector.recordBatch(
        'addMany-100',
        entities.length,
        result.successful,
        duration,
        memoryUsed
      )

      expect(result.successful).toHaveLength(100)
      expect(result.failed).toHaveLength(0)
      expect(metric.throughput).toBeGreaterThan(100) // > 100 items/sec
    })

    it('should handle 1,000 entities with proper batching', async () => {
      const entities = Array(1000).fill(null).map((_, i) => ({
        data: `Entity ${i}: ${' '.repeat(100)}`, // ~100 bytes each
        type: 'document' as const,
        metadata: {
          index: i,
          category: `cat-${i % 10}`,
          tags: [`tag-${i % 5}`, `tag-${i % 7}`]
        }
      }))

      const startMemory = process.memoryUsage().heapUsed
      const startTime = performance.now()

      const result = await brainy.addMany({ 
        items: entities,
        chunkSize: 100, // Process in batches
        parallel: true
      })

      const duration = performance.now() - startTime
      const memoryUsed = process.memoryUsage().heapUsed - startMemory

      metricsCollector.recordBatch(
        'addMany-1k',
        entities.length,
        result.successful,
        duration,
        memoryUsed
      )

      expect(result.successful.length).toBe(1000)
      expect(result.failed.length).toBe(0)
      expect(duration).toBeLessThan(10000) // < 10 seconds

      // Verify data integrity
      const sampleIds = result.successful.slice(0, 10)
      for (const id of sampleIds) {
        const entity = await brainy.get(id)
        expect(entity).toBeDefined()
      }
    })

    it('should handle 10,000 entities with memory efficiency', async () => {
      const chunkSize = 1000
      const totalEntities = 10000
      let allSuccessful: string[] = []
      let allFailed: any[] = []
      
      const startMemory = process.memoryUsage().heapUsed
      const startTime = performance.now()

      // Process in chunks to avoid memory issues
      for (let chunk = 0; chunk < totalEntities; chunk += chunkSize) {
        const entities = Array(Math.min(chunkSize, totalEntities - chunk))
          .fill(null)
          .map((_, i) => ({
            data: `Chunk entity ${chunk + i}`,
            type: 'document' as const,
            metadata: { globalIndex: chunk + i }
          }))

        const result = await brainy.addMany({ items: entities })
        allSuccessful.push(...result.successful)
        allFailed.push(...result.failed)

        // Check memory usage
        const currentMemory = process.memoryUsage().heapUsed
        const memoryGrowth = currentMemory - startMemory
        expect(memoryGrowth).toBeLessThan(500 * 1024 * 1024) // < 500MB total
      }

      const duration = performance.now() - startTime
      const finalMemory = process.memoryUsage().heapUsed - startMemory

      metricsCollector.recordBatch(
        'addMany-10k',
        totalEntities,
        allSuccessful,
        duration,
        finalMemory
      )

      expect(allSuccessful.length).toBe(10000)
      expect(allFailed.length).toBe(0)
    })

    it('should handle partial failures gracefully', async () => {
      const entities = [
        { data: 'Valid entity 1', type: 'document' as const },
        { data: '', type: 'document' as const }, // Invalid: empty data
        { data: 'Valid entity 2', type: 'document' as const },
        { data: null as any, type: 'document' as const }, // Invalid: null data
        { data: 'Valid entity 3', type: 'document' as const },
      ]

      const result = await brainy.addMany({ 
        items: entities,
        continueOnError: true
      })

      // Should process valid entities even if some fail
      expect(result.successful.length).toBeGreaterThanOrEqual(3)
      expect(result.failed.length).toBeGreaterThanOrEqual(0) // May or may not validate
      
      // Verify valid entities were added
      for (const id of result.successful) {
        const entity = await brainy.get(id)
        expect(entity).toBeDefined()
      }
    })

    it('should support concurrent batch operations', async () => {
      const batches = Array(5).fill(null).map((_, batchIdx) => 
        Array(100).fill(null).map((_, i) => ({
          data: `Batch ${batchIdx} Entity ${i}`,
          type: 'document' as const,
          metadata: { batchId: batchIdx, index: i }
        }))
      )

      const startTime = performance.now()

      // Execute batches concurrently
      const results = await Promise.all(
        batches.map(entities => brainy.addMany({ items: entities }))
      )

      const duration = performance.now() - startTime

      expect(results).toHaveLength(5)
      const totalSuccess = results.reduce((sum, r) => sum + r.successful.length, 0)
      expect(totalSuccess).toBe(500)
      expect(duration).toBeLessThan(5000) // Concurrent should be faster
    })
  })

    let testIds: string[] = []

    beforeEach(async () => {
      // Create test entities
      const entities = Array(100).fill(null).map((_, i) => ({
        data: `Original content ${i}`,
        type: 'document' as const,
        metadata: { version: 1, index: i }
      }))

      const result = await brainy.addMany({ items: entities })
      testIds = result.successful
    })

    it('should update multiple entities by IDs', async () => {
      const updates = testIds.slice(0, 50).map(id => ({
        id,
        updates: { 
          metadata: { 
            version: 2, 
            updatedAt: Date.now() 
          }
        }
      }))

      const startTime = performance.now()
      
      const results = await Promise.all(
        updates.map(u => brainy.update({ id: u.id, ...u.updates }))
      )

      const duration = performance.now() - startTime

      metricsCollector.recordBatch(
        updates.length,
        results,
        duration,
        0
      )

      // Verify updates
      const sample = await brainy.get(testIds[0])
      expect(sample?.metadata?.version).toBe(2)
    })

    it('should update entities matching criteria', async () => {
      // Update all entities with index < 20
      const targetIds = testIds.slice(0, 20)
      
      const startTime = performance.now()
      
      const results = await Promise.all(
        targetIds.map(id => 
          brainy.update({
            id,
            metadata: { 
              status: 'updated',
              processedAt: Date.now()
            }
          })
        )
      )

      const duration = performance.now() - startTime

      metricsCollector.recordBatch(
        targetIds.length,
        results,
        duration,
        0
      )

      // Verify updates
      for (const id of targetIds.slice(0, 5)) {
        const entity = await brainy.get(id)
        expect(entity?.metadata?.status).toBe('updated')
      }
    })
  })

  describe('deleteMany - Bulk Deletion', () => {
    let testIds: string[] = []

    beforeEach(async () => {
      // Create test entities
      const entities = Array(200).fill(null).map((_, i) => ({
        data: `Delete test ${i}`,
        type: 'document' as const,
        metadata: { deleteGroup: i % 4 }
      }))

      const result = await brainy.addMany({ items: entities })
      testIds = result.successful
    })

    it('should delete multiple entities by IDs', async () => {
      const toDelete = testIds.slice(0, 100)
      
      const startMemory = process.memoryUsage().heapUsed
      const startTime = performance.now()

      // Use deleteMany if available, otherwise individual deletes
      let results: any[]
      if ('deleteMany' in brainy && typeof brainy.deleteMany === 'function') {
        const result = await brainy.deleteMany({ ids: toDelete })
        results = result.successful
      } else {
        results = await Promise.all(
          toDelete.map(id => brainy.delete(id))
        )
      }

      const duration = performance.now() - startTime
      const memoryUsed = process.memoryUsage().heapUsed - startMemory

      metricsCollector.recordBatch(
        'deleteMany-100',
        toDelete.length,
        results,
        duration,
        memoryUsed
      )

      // Verify deletion
      for (const id of toDelete.slice(0, 10)) {
        const entity = await brainy.get(id)
        expect(entity).toBeNull()
      }

      // Remaining entities should still exist
      const remaining = testIds.slice(100, 110)
      for (const id of remaining) {
        const entity = await brainy.get(id)
        expect(entity).toBeDefined()
      }
    })

    it('should handle large-scale deletion efficiently', async () => {
      // Create a large dataset
      const largeDataset = Array(1000).fill(null).map((_, i) => ({
        data: `Large scale delete test ${i}`,
        type: 'document' as const
      }))

      const addResult = await brainy.addMany({ items: largeDataset })
      const idsToDelete = addResult.successful

      const startTime = performance.now()

      // Delete in batches
      const batchSize = 100
      for (let i = 0; i < idsToDelete.length; i += batchSize) {
        const batch = idsToDelete.slice(i, i + batchSize)
        await Promise.all(batch.map(id => brainy.delete(id)))
      }

      const duration = performance.now() - startTime

      metricsCollector.recordBatch(
        'deleteMany-1k',
        idsToDelete.length,
        idsToDelete.map(() => true),
        duration,
        0
      )

      expect(duration).toBeLessThan(10000) // < 10 seconds
    })
  })

    let nodeIds: string[] = []

    beforeEach(async () => {
      // Create nodes for relationships
      const nodes = Array(50).fill(null).map((_, i) => ({
        data: `Node ${i}`,
        type: 'thing' as const,
        metadata: { nodeIndex: i }
      }))

      const result = await brainy.addMany({ items: nodes })
      nodeIds = result.successful
    })

    it('should create multiple relationships efficiently', async () => {
      const relationships: Array<{from: string, to: string, type: string}> = []
      
      // Create a connected graph
      for (let i = 0; i < nodeIds.length - 1; i++) {
        relationships.push({
          from: nodeIds[i],
          to: nodeIds[i + 1],
          type: 'connected_to'
        })
      }

      const startTime = performance.now()

      const results = await Promise.all(
        relationships.map(r => 
          brainy.relate({ from: r.from, to: r.to, type: r.type as any })
        )
      )

      const duration = performance.now() - startTime

      metricsCollector.recordBatch(
        relationships.length,
        results,
        duration,
        0
      )

      expect(results.every(r => typeof r === 'string')).toBe(true) // relate returns ID
      expect(duration).toBeLessThan(5000)
    })

    it('should create complex graph structures', async () => {
      // Create a fully connected subgraph (first 10 nodes)
      const subgraph = nodeIds.slice(0, 10)
      const relationships: Array<{from: string, to: string}> = []

      for (let i = 0; i < subgraph.length; i++) {
        for (let j = i + 1; j < subgraph.length; j++) {
          relationships.push({
            from: subgraph[i],
            to: subgraph[j]
          })
        }
      }

      const startTime = performance.now()

      const results = await Promise.all(
        relationships.map(r => 
          brainy.relate({ from: r.from, to: r.to, type: 'relatedTo' })
        )
      )

      const duration = performance.now() - startTime

      metricsCollector.recordBatch(
        relationships.length,
        results,
        duration,
        0
      )

      expect(relationships.length).toBe(45) // 10 choose 2
      expect(results.every(r => typeof r === 'string')).toBe(true) // relate returns ID
    })
  })

  describe('Mixed Batch Operations', () => {
    it('should handle mixed operation types concurrently', async () => {
      // Prepare different operation types
      const addOps = Array(100).fill(null).map((_, i) => ({
        data: `Mixed add ${i}`,
        type: 'document' as const
      }))

      // Add initial entities for update/delete
      const setupResult = await brainy.addMany({ 
        items: Array(100).fill(null).map((_, i) => ({
          data: `Setup ${i}`,
          type: 'document' as const
        }))
      })

      const updateIds = setupResult.successful.slice(0, 50)
      const deleteIds = setupResult.successful.slice(50, 100)

      const startTime = performance.now()

      // Execute all operations concurrently
      const [addResults, updateResults, deleteResults] = await Promise.all([
        brainy.addMany({ items: addOps }),
        Promise.all(updateIds.map(id => 
          brainy.update({ id, metadata: { updated: true } })
        )),
        Promise.all(deleteIds.map(id => brainy.delete(id)))
      ])

      const duration = performance.now() - startTime

      console.log(`Mixed operations completed in ${duration.toFixed(2)}ms`)

      expect(addResults.successful.length).toBe(100)
      expect(updateResults.length).toBe(50)
      // Delete returns void, so just check length
      expect(deleteResults).toHaveLength(50)
    })
  })

  describe('Error Recovery', () => {
    it('should recover from transient failures', async () => {
      let attemptCount = 0
      const entities = Array(10).fill(null).map((_, i) => ({
        data: `Retry test ${i}`,
        type: 'document' as const
      }))

      // Simulate transient failures
      const originalAdd = brainy.add.bind(brainy)
      brainy.add = async function(params: any) {
        attemptCount++
        if (attemptCount % 3 === 0) {
          throw new Error('Transient failure')
        }
        return originalAdd(params)
      }

      const results: any[] = []
      for (const entity of entities) {
        let retries = 0
        while (retries < 3) {
          try {
            const id = await brainy.add(entity)
            results.push({ status: 'fulfilled', value: id })
            break
          } catch (error) {
            retries++
            if (retries === 3) {
              results.push({ status: 'rejected', reason: error })
            }
          }
        }
      }

      const successful = results.filter(r => r.status === 'fulfilled')
      expect(successful.length).toBeGreaterThanOrEqual(6) // Most should succeed with retry
    })

    it('should handle memory pressure gracefully', async () => {
      const largeEntities = Array(100).fill(null).map((_, i) => ({
        data: 'x'.repeat(100000), // 100KB each = 10MB total
        type: 'document' as const,
        metadata: { index: i }
      }))

      const startMemory = process.memoryUsage().heapUsed
      
      // Process with memory monitoring
      const batchSize = 10
      const results: any[] = []
      
      for (let i = 0; i < largeEntities.length; i += batchSize) {
        const batch = largeEntities.slice(i, i + batchSize)
        
        // Check memory before processing
        const currentMemory = process.memoryUsage().heapUsed
        const memoryUsed = currentMemory - startMemory
        
        if (memoryUsed > 100 * 1024 * 1024) { // If > 100MB
          if (global.gc) global.gc() // Force GC if available
        }
        
        const batchResult = await brainy.addMany({ items: batch })
        results.push(...batchResult.successful)
      }

      expect(results.length).toBe(100)
      
      const finalMemory = process.memoryUsage().heapUsed - startMemory
      expect(finalMemory).toBeLessThan(200 * 1024 * 1024) // Should stay under 200MB
    })
  })

  describe('Performance Report', () => {
    it('should generate comprehensive batch operations report', async () => {
      metricsCollector.generateReport()
      
      const metrics = metricsCollector.getMetrics()
      expect(metrics.length).toBeGreaterThan(0)
      
      // Validate performance targets
      for (const metric of metrics) {
        expect(metric.successCount).toBeGreaterThan(0)
        if (metric.operation.includes('100')) {
          expect(metric.throughput).toBeGreaterThan(50) // At least 50 items/sec
        }
      }
    })
  })
})