/**
 * Data Migration Tests
 * Validates that data migration scripts work correctly
 * Tests data transformation, integrity, and edge cases
 */

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

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

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

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

  describe('Empty Database Migration', () => {
    test('should migrate empty database successfully', async () => {
      // Verify database is empty
      const noteCount = await prisma.note.count()
      expect(noteCount).toBe(0)
      
      // Data migration should handle empty database gracefully
      // No data should be created or lost
      expect(noteCount).toBe(0)
    })
  })

  describe('Basic Data Migration', () => {
    beforeEach(async () => {
      // Clean up before each test
      await prisma.note.deleteMany({})
      await prisma.aiFeedback.deleteMany({})
    })

    test('should migrate basic notes without AI fields', async () => {
      // Create sample notes (simulating pre-migration data)
      await createSampleNotes(prisma, 10)
      
      // Verify notes are created
      const noteCount = await prisma.note.count()
      expect(noteCount).toBe(10)
      
      // All notes should have null AI fields (backward compatibility)
      const notes = await prisma.note.findMany()
      notes.forEach(note => {
        expect(note.autoGenerated).toBeNull()
        expect(note.aiProvider).toBeNull()
        expect(note.aiConfidence).toBeNull()
        expect(note.language).toBeNull()
        expect(note.languageConfidence).toBeNull()
        expect(note.lastAiAnalysis).toBeNull()
      })
    })

    test('should preserve existing note data during migration', async () => {
      // Create a note with all fields
      const originalNote = await prisma.note.create({
        data: {
          title: 'Original Note',
          content: 'Original content',
          color: 'blue',
          isPinned: true,
          isArchived: false,
          type: 'text',
          size: 'medium',
          userId: 'test-user-id',
          order: 0
        }
      })
      
      // Simulate migration by querying the note
      const noteAfterMigration = await prisma.note.findUnique({
        where: { id: originalNote.id }
      })
      
      // Verify all original fields are preserved
      expect(noteAfterMigration?.title).toBe('Original Note')
      expect(noteAfterMigration?.content).toBe('Original content')
      expect(noteAfterMigration?.color).toBe('blue')
      expect(noteAfterMigration?.isPinned).toBe(true)
      expect(noteAfterMigration?.isArchived).toBe(false)
      expect(noteAfterMigration?.type).toBe('text')
      expect(noteAfterMigration?.size).toBe('medium')
      expect(noteAfterMigration?.order).toBe(0)
    })
  })

  describe('AI Fields Data Migration', () => {
    test('should handle notes with all AI fields populated', async () => {
      const testNote = await prisma.note.create({
        data: {
          title: 'AI Test Note',
          content: 'Test content',
          userId: 'test-user-id',
          autoGenerated: true,
          aiProvider: 'openai',
          aiConfidence: 95,
          language: 'en',
          languageConfidence: 0.98,
          lastAiAnalysis: new Date()
        }
      })
      
      // Verify AI fields are correctly stored
      expect(testNote.autoGenerated).toBe(true)
      expect(testNote.aiProvider).toBe('openai')
      expect(testNote.aiConfidence).toBe(95)
      expect(testNote.language).toBe('en')
      expect(testNote.languageConfidence).toBe(0.98)
      expect(testNote.lastAiAnalysis).toBeDefined()
    })

    test('should handle notes with partial AI fields', async () => {
      // Create note with only some AI fields
      const note1 = await prisma.note.create({
        data: {
          title: 'Partial AI Note 1',
          content: 'Test content',
          userId: 'test-user-id',
          autoGenerated: true,
          aiProvider: 'ollama'
          // Other AI fields are null
        }
      })
      
      expect(note1.autoGenerated).toBe(true)
      expect(note1.aiProvider).toBe('ollama')
      expect(note1.aiConfidence).toBeNull()
      expect(note1.language).toBeNull()
      
      // Create note with different partial fields
      const note2 = await prisma.note.create({
        data: {
          title: 'Partial AI Note 2',
          content: 'Test content',
          userId: 'test-user-id',
          aiConfidence: 87,
          language: 'fr',
          languageConfidence: 0.92
          // Other AI fields are null
        }
      })
      
      expect(note2.autoGenerated).toBeNull()
      expect(note2.aiProvider).toBeNull()
      expect(note2.aiConfidence).toBe(87)
      expect(note2.language).toBe('fr')
      expect(note2.languageConfidence).toBe(0.92)
    })

    test('should handle null values in AI fields correctly', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Null AI Fields Note',
          content: 'Test content',
          userId: 'test-user-id'
          // All AI fields are null by default
        }
      })
      
      // Verify all AI fields are null
      expect(note.autoGenerated).toBeNull()
      expect(note.aiProvider).toBeNull()
      expect(note.aiConfidence).toBeNull()
      expect(note.language).toBeNull()
      expect(note.languageConfidence).toBeNull()
      expect(note.lastAiAnalysis).toBeNull()
    })
  })

  describe('AiFeedback Data Migration', () => {
    test('should create and retrieve AiFeedback entries', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Feedback Test Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      const feedback = await prisma.aiFeedback.create({
        data: {
          noteId: note.id,
          userId: 'test-user-id',
          feedbackType: 'thumbs_up',
          feature: 'title_suggestion',
          originalContent: 'AI suggested title',
          metadata: JSON.stringify({
            aiProvider: 'openai',
            confidence: 95,
            timestamp: new Date().toISOString()
          })
        }
      })
      
      // Verify feedback is correctly stored
      expect(feedback.noteId).toBe(note.id)
      expect(feedback.feedbackType).toBe('thumbs_up')
      expect(feedback.feature).toBe('title_suggestion')
      expect(feedback.originalContent).toBe('AI suggested title')
      expect(feedback.metadata).toBeDefined()
    })

    test('should handle different feedback types', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Feedback Types Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      const feedbackTypes = [
        { type: 'thumbs_up', feature: 'title_suggestion', content: 'Good suggestion' },
        { type: 'thumbs_down', feature: 'semantic_search', content: 'Bad result' },
        { type: 'correction', feature: 'title_suggestion', content: 'Wrong', corrected: 'Correct' }
      ]
      
      for (const fb of feedbackTypes) {
        const feedback = await prisma.aiFeedback.create({
          data: {
            noteId: note.id,
            userId: 'test-user-id',
            feedbackType: fb.type,
            feature: fb.feature,
            originalContent: fb.content,
            correctedContent: fb.corrected
          }
        })
        
        expect(feedback.feedbackType).toBe(fb.type)
      }
    })

    test('should store and retrieve metadata JSON correctly', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Metadata Test Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      const metadata = {
        aiProvider: 'ollama',
        model: 'llama2-7b',
        confidence: 87,
        timestamp: new Date().toISOString(),
        additional: {
          latency: 234,
          tokens: 456
        }
      }
      
      const feedback = await prisma.aiFeedback.create({
        data: {
          noteId: note.id,
          userId: 'test-user-id',
          feedbackType: 'thumbs_up',
          feature: 'title_suggestion',
          originalContent: 'Test suggestion',
          metadata: JSON.stringify(metadata)
        }
      })
      
      // Parse and verify metadata
      const parsedMetadata = JSON.parse(feedback.metadata || '{}')
      expect(parsedMetadata.aiProvider).toBe('ollama')
      expect(parsedMetadata.confidence).toBe(87)
      expect(parsedMetadata.additional).toBeDefined()
    })
  })

  describe('MemoryEchoInsight Data Migration', () => {
    test('should create and retrieve MemoryEchoInsight entries', async () => {
      const note1 = await prisma.note.create({
        data: {
          title: 'Echo Note 1',
          content: 'Content about programming',
          userId: 'test-user-id'
        }
      })
      
      const note2 = await prisma.note.create({
        data: {
          title: 'Echo Note 2',
          content: 'Content about coding',
          userId: 'test-user-id'
        }
      })
      
      const insight = await prisma.memoryEchoInsight.create({
        data: {
          userId: 'test-user-id',
          note1Id: note1.id,
          note2Id: note2.id,
          similarityScore: 0.85,
          insight: 'These notes are similar because they both discuss programming concepts',
          insightDate: new Date()
        }
      })
      
      expect(insight.note1Id).toBe(note1.id)
      expect(insight.note2Id).toBe(note2.id)
      expect(insight.similarityScore).toBe(0.85)
      expect(insight.insight).toBeDefined()
    })

    test('should handle insight feedback and dismissal', async () => {
      const note1 = await prisma.note.create({
        data: {
          title: 'Echo Feedback Note 1',
          content: 'Content A',
          userId: 'test-user-id'
        }
      })
      
      const note2 = await prisma.note.create({
        data: {
          title: 'Echo Feedback Note 2',
          content: 'Content B',
          userId: 'test-user-id'
        }
      })
      
      const insight = await prisma.memoryEchoInsight.create({
        data: {
          userId: 'test-user-id',
          note1Id: note1.id,
          note2Id: note2.id,
          similarityScore: 0.75,
          insight: 'Test insight',
          feedback: 'useful',
          dismissed: false
        }
      })
      
      expect(insight.feedback).toBe('useful')
      expect(insight.dismissed).toBe(false)
      
      // Update insight to mark as dismissed
      const updatedInsight = await prisma.memoryEchoInsight.update({
        where: { id: insight.id },
        data: { dismissed: true }
      })
      
      expect(updatedInsight.dismissed).toBe(true)
    })
  })

  describe('UserAISettings Data Migration', () => {
    test('should create and retrieve UserAISettings', async () => {
      const user = await prisma.user.create({
        data: {
          email: 'ai-settings@test.com',
          name: 'AI Settings User'
        }
      })
      
      const settings = await prisma.userAISettings.create({
        data: {
          userId: user.id,
          titleSuggestions: true,
          semanticSearch: false,
          memoryEcho: true,
          memoryEchoFrequency: 'weekly',
          aiProvider: 'ollama',
          preferredLanguage: 'fr'
        }
      })
      
      expect(settings.userId).toBe(user.id)
      expect(settings.titleSuggestions).toBe(true)
      expect(settings.semanticSearch).toBe(false)
      expect(settings.memoryEchoFrequency).toBe('weekly')
      expect(settings.aiProvider).toBe('ollama')
      expect(settings.preferredLanguage).toBe('fr')
    })

    test('should handle default values correctly', async () => {
      const user = await prisma.user.create({
        data: {
          email: 'default-ai-settings@test.com',
          name: 'Default AI User'
        }
      })
      
      const settings = await prisma.userAISettings.create({
        data: {
          userId: user.id
          // All other fields should use defaults
        }
      })
      
      expect(settings.titleSuggestions).toBe(true)
      expect(settings.semanticSearch).toBe(true)
      expect(settings.memoryEcho).toBe(true)
      expect(settings.memoryEchoFrequency).toBe('daily')
      expect(settings.aiProvider).toBe('auto')
      expect(settings.preferredLanguage).toBe('auto')
    })
  })

  describe('Data Integrity', () => {
    test('should verify no data loss after migration', async () => {
      // Create initial data
      const initialNotes = await createSampleNotes(prisma, 50)
      const initialCount = initialNotes.length
      
      // Simulate migration by querying data
      const notesAfterMigration = await prisma.note.findMany()
      const finalCount = notesAfterMigration.length
      
      // Verify no data loss
      expect(finalCount).toBe(initialCount)
      
      // Verify each note's data is intact
      for (const note of notesAfterMigration) {
        expect(note.title).toBeDefined()
        expect(note.content).toBeDefined()
      }
    })

    test('should verify no data corruption after migration', async () => {
      // Create notes with complex data
      await prisma.note.create({
        data: {
          title: 'Complex Data Note',
          content: 'This is a note with **markdown** formatting',
          checkItems: JSON.stringify([{ text: 'Task 1', done: false }, { text: 'Task 2', done: true }]),
          images: JSON.stringify([{ url: 'image1.jpg', caption: 'Caption 1' }]),
          userId: 'test-user-id'
        }
      })
      
      const note = await prisma.note.findFirst({
        where: { title: 'Complex Data Note' }
      })
      
      // Verify complex data is preserved
      expect(note?.content).toContain('**markdown**')
      
      if (note?.checkItems) {
        const checkItems = note.checkItems as any[]
        expect(checkItems.length).toBe(2)
      }

      if (note?.images) {
        const images = note.images as any[]
        expect(images.length).toBe(1)
      }
    })

    test('should maintain foreign key relationships', async () => {
      // Create a user
      const user = await prisma.user.create({
        data: {
          email: 'fk-test@test.com',
          name: 'FK Test User'
        }
      })
      
      // Create a notebook for the user
      const notebook = await prisma.notebook.create({
        data: {
          name: 'FK Test Notebook',
          order: 0,
          userId: user.id
        }
      })
      
      // Create notes in the notebook
      await prisma.note.createMany({
        data: [
          {
            title: 'FK Note 1',
            content: 'Content 1',
            userId: user.id,
            notebookId: notebook.id
          },
          {
            title: 'FK Note 2',
            content: 'Content 2',
            userId: user.id,
            notebookId: notebook.id
          }
        ]
      })
      
      // Verify relationships are maintained
      const retrievedNotebook = await prisma.notebook.findUnique({
        where: { id: notebook.id },
        include: { notes: true }
      })
      
      expect(retrievedNotebook?.notes.length).toBe(2)
      expect(retrievedNotebook?.userId).toBe(user.id)
    })
  })

  describe('Edge Cases', () => {
    test('should handle empty strings in text fields', async () => {
      const note = await prisma.note.create({
        data: {
          title: '',
          content: 'Content with empty title',
          userId: 'test-user-id'
        }
      })
      
      expect(note.title).toBe('')
      expect(note.content).toBe('Content with empty title')
    })

    test('should handle very long text content', async () => {
      const longContent = 'A'.repeat(10000)
      
      const note = await prisma.note.create({
        data: {
          title: 'Long Content Note',
          content: longContent,
          userId: 'test-user-id'
        }
      })
      
      expect(note.content).toHaveLength(10000)
    })

    test('should handle special characters in text fields', async () => {
      const specialChars = 'Note with émojis 🎉 and spëcial çharacters & spåcial ñumbers 123'
      
      const note = await prisma.note.create({
        data: {
          title: specialChars,
          content: specialChars,
          userId: 'test-user-id'
        }
      })
      
      expect(note.title).toBe(specialChars)
      expect(note.content).toBe(specialChars)
    })

    test('should handle null userId in some tables (optional relationships)', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'No User Note',
          content: 'Note without userId',
          userId: null
        }
      })
      
      expect(note.userId).toBeNull()
    })
  })

  describe('Migration Performance', () => {
    test('should complete migration within acceptable time for 100 notes', async () => {
      // Clean up
      await prisma.note.deleteMany({})
      
      // Create 100 notes and measure time
      const { result, duration } = await measureExecutionTime(async () => {
        await createSampleNotes(prisma, 100)
      })
      
      // Migration should complete quickly (< 5 seconds for 100 notes)
      expect(duration).toBeLessThan(5000)
      expect(result.length).toBe(100)
    })
  })

  describe('Batch Operations', () => {
    test('should handle batch insert of notes', async () => {
      const notesData = Array.from({ length: 20 }, (_, i) => ({
        title: `Batch Note ${i + 1}`,
        content: `Batch content ${i + 1}`,
        userId: 'test-user-id',
        order: i
      }))
      
      await prisma.note.createMany({
        data: notesData
      })
      
      const count = await prisma.note.count()
      expect(count).toBe(20)
    })

    test('should handle batch insert of feedback', async () => {
      const note = await prisma.note.create({
        data: {
          title: 'Batch Feedback Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })
      
      const feedbackData = Array.from({ length: 10 }, (_, i) => ({
        noteId: note.id,
        userId: 'test-user-id',
        feedbackType: 'thumbs_up',
        feature: 'title_suggestion',
        originalContent: `Feedback ${i + 1}`
      }))
      
      await prisma.aiFeedback.createMany({
        data: feedbackData
      })
      
      const count = await prisma.aiFeedback.count()
      expect(count).toBe(10)
    })
  })
})
