/**
 * Performance Tests
 * Validates that migrations complete within acceptable time limits
 * Tests scalability with various data sizes
 */

import { PrismaClient } from '@prisma/client'
import {
  setupTestEnvironment,
  createTestPrismaClient,
  initializeTestDatabase,
  cleanupTestDatabase,
  createSampleNotes,
  measureExecutionTime,
  getDatabaseSize
} from './setup'

describe('Performance Tests', () => {
  let prisma: PrismaClient

  beforeAll(async () => {
    await setupTestEnvironment()
    prisma = createTestPrismaClient()
    await initializeTestDatabase(prisma)
  })

  afterAll(async () => {
    await cleanupTestDatabase(prisma)
  })

  describe('Empty Migration Performance', () => {
    test('should complete empty database migration quickly', async () => {
      // Clean up any existing data
      await prisma.note.deleteMany({})
      
      // Measure time to "migrate" empty database
      const { result, duration } = await measureExecutionTime(async () => {
        const noteCount = await prisma.note.count()
        return { count: noteCount }
      })
      
      // Empty migration should complete instantly (< 1 second)
      expect(duration).toBeLessThan(1000)
      expect(result.count).toBe(0)
    })
  })

  describe('Small Dataset Performance (10 notes)', () => {
    beforeEach(async () => {
      await prisma.note.deleteMany({})
    })

    test('should complete migration for 10 notes within 1 second', async () => {
      // Create 10 notes
      const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
        return await createSampleNotes(prisma, 10)
      })
      
      expect(notes.length).toBe(10)
      expect(createDuration).toBeLessThan(1000)
      
      // Measure query performance
      const { result, duration: queryDuration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany()
      })
      
      expect(result.length).toBe(10)
      expect(queryDuration).toBeLessThan(500)
    })

    test('should complete create operation for 10 notes within 1 second', async () => {
      const { result, duration } = await measureExecutionTime(async () => {
        const notes = []
        for (let i = 0; i < 10; i++) {
          const note = await prisma.note.create({
            data: {
              title: `Perf Test Note ${i}`,
              content: `Test content ${i}`,
              userId: 'test-user-id',
              order: i
            }
          })
          notes.push(note)
        }
        return notes
      })
      
      expect(result.length).toBe(10)
      expect(duration).toBeLessThan(1000)
    })

    test('should complete update operation for 10 notes within 1 second', async () => {
      // Create notes first
      await createSampleNotes(prisma, 10)
      
      // Measure update performance
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.updateMany({
          data: { isPinned: true },
          where: { title: { contains: 'Test Note' } }
        })
      })
      
      expect(duration).toBeLessThan(1000)
    })
  })

  describe('Medium Dataset Performance (100 notes)', () => {
    beforeEach(async () => {
      await prisma.note.deleteMany({})
    })

    test('should complete migration for 100 notes within 5 seconds', async () => {
      // Create 100 notes
      const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
        return await createSampleNotes(prisma, 100)
      })
      
      expect(notes.length).toBe(100)
      expect(createDuration).toBeLessThan(5000)
      
      // Measure query performance
      const { result, duration: queryDuration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany()
      })
      
      expect(result.length).toBe(100)
      expect(queryDuration).toBeLessThan(1000)
    })

    test('should complete create operation for 100 notes within 5 seconds', async () => {
      const { result, duration } = await measureExecutionTime(async () => {
        const notes = []
        for (let i = 0; i < 100; i++) {
          const note = await prisma.note.create({
            data: {
              title: `Perf Test Note ${i}`,
              content: `Test content ${i}`,
              userId: 'test-user-id',
              order: i
            }
          })
          notes.push(note)
        }
        return notes
      })
      
      expect(result.length).toBe(100)
      expect(duration).toBeLessThan(5000)
    })

    test('should complete batch insert for 100 notes within 2 seconds', async () => {
      const notesData = Array.from({ length: 100 }, (_, i) => ({
        title: `Batch Note ${i}`,
        content: `Batch content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.createMany({
          data: notesData
        })
      })
      
      expect(result.count).toBe(100)
      expect(duration).toBeLessThan(2000)
    })

    test('should complete filtered query for 100 notes within 500ms', async () => {
      await createSampleNotes(prisma, 100)
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          where: {
            isPinned: true
          }
        })
      })
      
      expect(duration).toBeLessThan(500)
    })
  })

  describe('Target Dataset Performance (1,000 notes)', () => {
    beforeEach(async () => {
      await prisma.note.deleteMany({})
    })

    test('should complete migration for 1,000 notes within 30 seconds', async () => {
      // Create 1,000 notes in batches for better performance
      const { result: notes, duration: createDuration } = await measureExecutionTime(async () => {
        const allNotes = []
        const batchSize = 100
        const totalNotes = 1000
        
        for (let batch = 0; batch < totalNotes / batchSize; batch++) {
          const batchData = Array.from({ length: batchSize }, (_, i) => ({
            title: `Perf Note ${batch * batchSize + i}`,
            content: `Test content ${batch * batchSize + i}`,
            userId: 'test-user-id',
            order: batch * batchSize + i
          }))
          
          await prisma.note.createMany({ data: batchData })
        }
        
        return await prisma.note.findMany()
      })
      
      expect(notes.length).toBe(1000)
      expect(createDuration).toBeLessThan(30000)
    })

    test('should complete batch insert for 1,000 notes within 10 seconds', async () => {
      const notesData = Array.from({ length: 1000 }, (_, i) => ({
        title: `Batch Note ${i}`,
        content: `Batch content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.createMany({
          data: notesData
        })
      })
      
      expect(result.count).toBe(1000)
      expect(duration).toBeLessThan(10000)
    })

    test('should complete query for 1,000 notes within 1 second', async () => {
      const notesData = Array.from({ length: 1000 }, (_, i) => ({
        title: `Query Test Note ${i}`,
        content: `Query test content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany()
      })
      
      expect(result.length).toBe(1000)
      expect(duration).toBeLessThan(1000)
    })

    test('should complete filtered query for 1,000 notes within 1 second', async () => {
      // Create notes with various pinned states
      const notesData = Array.from({ length: 1000 }, (_, i) => ({
        title: `Filter Test Note ${i}`,
        content: `Filter test content ${i}`,
        userId: 'test-user-id',
        order: i,
        isPinned: i % 3 === 0 // Every 3rd note is pinned
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          where: {
            isPinned: true
          }
        })
      })
      
      expect(result.length).toBeGreaterThan(0)
      expect(duration).toBeLessThan(1000)
    })

    test('should complete indexed query for 1,000 notes within 500ms', async () => {
      // Create notes
      const notesData = Array.from({ length: 1000 }, (_, i) => ({
        title: `Index Test Note ${i}`,
        content: `Index test content ${i}`,
        userId: 'test-user-id',
        order: i,
        isPinned: i % 2 === 0
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      // Query using indexed field (isPinned)
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          where: {
            isPinned: true
          },
          orderBy: {
            order: 'asc'
          }
        })
      })
      
      expect(result.length).toBeGreaterThan(0)
      expect(duration).toBeLessThan(500)
    })
  })

  describe('Stress Test Performance (10,000 notes)', () => {
    beforeEach(async () => {
      await prisma.note.deleteMany({})
    })

    test('should complete batch insert for 10,000 notes within 30 seconds', async () => {
      const notesData = Array.from({ length: 10000 }, (_, i) => ({
        title: `Stress Note ${i}`,
        content: `Stress test content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.createMany({
          data: notesData
        })
      })
      
      expect(result.count).toBe(10000)
      expect(duration).toBeLessThan(30000)
    })

    test('should complete query for 10,000 notes within 2 seconds', async () => {
      const notesData = Array.from({ length: 10000 }, (_, i) => ({
        title: `Stress Query Note ${i}`,
        content: `Stress query content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          take: 100 // Limit to 100 for performance
        })
      })
      
      expect(result.length).toBe(100)
      expect(duration).toBeLessThan(2000)
    })

    test('should handle pagination for 10,000 notes efficiently', async () => {
      const notesData = Array.from({ length: 10000 }, (_, i) => ({
        title: `Pagination Note ${i}`,
        content: `Pagination content ${i}`,
        userId: 'test-user-id',
        order: i
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          skip: 100,
          take: 50
        })
      })
      
      expect(result.length).toBe(50)
      expect(duration).toBeLessThan(1000)
    })
  })

  describe('AI Features Performance', () => {
    beforeEach(async () => {
      await prisma.note.deleteMany({})
      await prisma.aiFeedback.deleteMany({})
    })

    test('should create AI-enabled notes efficiently (100 notes)', async () => {
      const { result, duration } = await measureExecutionTime(async () => {
        const notes = []
        for (let i = 0; i < 100; i++) {
          const note = await prisma.note.create({
            data: {
              title: `AI Note ${i}`,
              content: `AI content ${i}`,
              userId: 'test-user-id',
              autoGenerated: i % 2 === 0,
              aiProvider: i % 3 === 0 ? 'openai' : 'ollama',
              aiConfidence: 70 + i,
              language: i % 2 === 0 ? 'en' : 'fr',
              languageConfidence: 0.85 + (i * 0.001),
              lastAiAnalysis: new Date(),
              order: i
            }
          })
          notes.push(note)
        }
        return notes
      })
      
      expect(result.length).toBe(100)
      expect(duration).toBeLessThan(5000)
    })

    test('should query by AI fields efficiently (100 notes)', async () => {
      // Create AI notes
      const notes = []
      for (let i = 0; i < 100; i++) {
        const note = await prisma.note.create({
          data: {
            title: `AI Query Note ${i}`,
            content: `Content ${i}`,
            userId: 'test-user-id',
            autoGenerated: i % 2 === 0,
            aiProvider: i % 3 === 0 ? 'openai' : 'ollama',
            order: i
          }
        })
        notes.push(note)
      }
      
      // Query by autoGenerated
      const { result: autoGenerated, duration: duration1 } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          where: { autoGenerated: true }
        })
      })
      
      expect(autoGenerated.length).toBeGreaterThan(0)
      expect(duration1).toBeLessThan(500)
      
      // Query by aiProvider
      const { result: openaiNotes, duration: duration2 } = await measureExecutionTime(async () => {
        return await prisma.note.findMany({
          where: { aiProvider: 'openai' }
        })
      })
      
      expect(openaiNotes.length).toBeGreaterThan(0)
      expect(duration2).toBeLessThan(500)
    })

    test('should create AI feedback efficiently (100 feedback entries)', async () => {
      // Create a note
      const note = await prisma.note.create({
        data: {
          title: 'Feedback Performance Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      const { result, duration } = await measureExecutionTime(async () => {
        const feedbacks = []
        for (let i = 0; i < 100; i++) {
          const feedback = await prisma.aiFeedback.create({
            data: {
              noteId: note.id,
              userId: 'test-user-id',
              feedbackType: i % 3 === 0 ? 'thumbs_up' : 'thumbs_down',
              feature: 'title_suggestion',
              originalContent: `Feedback ${i}`,
              metadata: JSON.stringify({
                aiProvider: 'openai',
                confidence: 70 + i,
                timestamp: new Date().toISOString()
              })
            }
          })
          feedbacks.push(feedback)
        }
        return feedbacks
      })
      
      expect(result.length).toBe(100)
      expect(duration).toBeLessThan(5000)
    })

    test('should query feedback by note efficiently (100 feedback entries)', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Feedback Query Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      // Create feedback
      for (let i = 0; i < 100; i++) {
        await prisma.aiFeedback.create({
          data: {
            noteId: note.id,
            userId: 'test-user-id',
            feedbackType: 'thumbs_up',
            feature: 'title_suggestion',
            originalContent: `Feedback ${i}`
          }
        })
      }
      
      // Query by noteId (should use index)
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.aiFeedback.findMany({
          where: { noteId: note.id },
          orderBy: { createdAt: 'asc' }
        })
      })
      
      expect(result.length).toBe(100)
      expect(duration).toBeLessThan(500)
    })
  })

  describe('Database Size Performance', () => {
    test('should track database size growth', async () => {
      // Get initial size
      const initialSize = await getDatabaseSize(prisma)
      
      // Add 100 notes
      const notesData = Array.from({ length: 100 }, (_, i) => ({
        title: `Size Test Note ${i}`,
        content: `Size test content ${i}`.repeat(10), // Larger content
        userId: 'test-user-id',
        order: i
      }))
      
      await prisma.note.createMany({ data: notesData })
      
      // Get size after adding notes
      const sizeAfter = await getDatabaseSize(prisma)
      
      // Database should have grown
      expect(sizeAfter).toBeGreaterThan(initialSize)
    })

    test('should handle large content efficiently', async () => {
      const largeContent = 'A'.repeat(10000) // 10KB per note
      
      const { result, duration } = await measureExecutionTime(async () => {
        return await prisma.note.create({
          data: {
            title: 'Large Content Note',
            content: largeContent,
            userId: 'test-user-id'
          }
        })
      })
      
      expect(result.content).toHaveLength(10000)
      expect(duration).toBeLessThan(1000)
    })
  })

  describe('Concurrent Operations Performance', () => {
    test('should handle multiple concurrent reads', async () => {
      // Create test data
      await createSampleNotes(prisma, 100)
      
      // Measure concurrent read performance
      const { duration } = await measureExecutionTime(async () => {
        const promises = [
          prisma.note.findMany({ take: 10 }),
          prisma.note.findMany({ take: 10, skip: 10 }),
          prisma.note.findMany({ take: 10, skip: 20 }),
          prisma.note.findMany({ take: 10, skip: 30 }),
          prisma.note.findMany({ take: 10, skip: 40 })
        ]
        
        await Promise.all(promises)
      })
      
      // All concurrent reads should complete quickly
      expect(duration).toBeLessThan(2000)
    })
  })
})
