/**
 * Rollback Tests
 * Validates that migrations can be safely rolled back
 * Tests schema rollback, data recovery, and cleanup
 */

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

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

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

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

  describe('Schema Rollback', () => {
    test('should verify schema state before migration', async () => {
      // Verify basic tables exist (pre-migration state)
      const hasUser = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
        'User'
      )
      expect(hasUser.length).toBeGreaterThan(0)
    })

    test('should verify AI tables exist after migration', async () => {
      // Verify AI tables exist (post-migration state)
      const hasAiFeedback = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
        'AiFeedback'
      )
      expect(hasAiFeedback.length).toBeGreaterThan(0)

      const hasMemoryEcho = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
        'MemoryEchoInsight'
      )
      expect(hasMemoryEcho.length).toBeGreaterThan(0)

      const hasUserAISettings = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
        'UserAISettings'
      )
      expect(hasUserAISettings.length).toBeGreaterThan(0)
    })

    test('should verify Note AI columns exist after migration', async () => {
      // Check if AI columns exist in Note table
      const noteSchema = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `PRAGMA table_info(Note)`
      )

      const aiColumns = ['autoGenerated', 'aiProvider', 'aiConfidence', 'language', 'languageConfidence', 'lastAiAnalysis']
      
      for (const column of aiColumns) {
        const columnExists = noteSchema.some((col: any) => col.name === column)
        expect(columnExists).toBe(true)
      }
    })

    test('should simulate dropping AI columns (rollback scenario)', async () => {
      // In a real rollback, you would execute ALTER TABLE DROP COLUMN
      // For SQLite, this requires creating a new table and copying data
      // This test verifies we can identify which columns would be dropped
      
      const noteSchema = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `PRAGMA table_info(Note)`
      )

      const aiColumns = ['autoGenerated', 'aiProvider', 'aiConfidence', 'language', 'languageConfidence', 'lastAiAnalysis']
      const allColumns = noteSchema.map((col: any) => col.name)
      
      // Verify all AI columns exist
      for (const column of aiColumns) {
        expect(allColumns).toContain(column)
      }
    })

    test('should simulate dropping AI tables (rollback scenario)', async () => {
      // In a real rollback, you would execute DROP TABLE
      // This test verifies we can identify which tables would be dropped
      
      const aiTables = ['AiFeedback', 'MemoryEchoInsight', 'UserAISettings']
      
      for (const table of aiTables) {
        const exists = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
          `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
          table
        )
        expect(exists.length).toBeGreaterThan(0)
      }
    })
  })

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

    test('should preserve basic note data if AI columns are dropped', async () => {
      // Create notes with AI fields
      const noteWithAI = await prisma.note.create({
        data: {
          title: 'Note with AI',
          content: 'This note has AI fields',
          userId: 'test-user-id',
          autoGenerated: true,
          aiProvider: 'openai',
          aiConfidence: 95,
          language: 'en',
          languageConfidence: 0.98,
          lastAiAnalysis: new Date()
        }
      })

      // Verify basic fields are present
      expect(noteWithAI.id).toBeDefined()
      expect(noteWithAI.title).toBe('Note with AI')
      expect(noteWithAI.content).toBe('This note has AI fields')
      expect(noteWithAI.userId).toBe('test-user-id')

      // In a rollback, AI columns would be dropped but basic data should remain
      // This verifies basic data integrity independent of AI fields
      const basicNote = await prisma.note.findUnique({
        where: { id: noteWithAI.id },
        select: {
          id: true,
          title: true,
          content: true,
          userId: true
        }
      })

      expect(basicNote?.id).toBe(noteWithAI.id)
      expect(basicNote?.title).toBe(noteWithAI.title)
      expect(basicNote?.content).toBe(noteWithAI.content)
    })

    test('should preserve note relationships if AI tables are dropped', async () => {
      // Create a user and note
      const user = await prisma.user.create({
        data: {
          email: 'rollback-test@test.com',
          name: 'Rollback User'
        }
      })

      const notebook = await prisma.notebook.create({
        data: {
          name: 'Rollback Notebook',
          order: 0,
          userId: user.id
        }
      })

      const note = await prisma.note.create({
        data: {
          title: 'Rollback Test Note',
          content: 'Test content',
          userId: user.id,
          notebookId: notebook.id
        }
      })

      // Verify relationships exist
      expect(note.userId).toBe(user.id)
      expect(note.notebookId).toBe(notebook.id)

      // After rollback (dropping AI tables), basic relationships should be preserved
      const retrievedNote = await prisma.note.findUnique({
        where: { id: note.id },
        include: {
          notebook: true,
          user: true
        }
      })

      expect(retrievedNote?.userId).toBe(user.id)
      expect(retrievedNote?.notebookId).toBe(notebook.id)
    })

    test('should handle orphaned records after table drop', async () => {
      // Create a note with AI feedback
      const note = await prisma.note.create({
        data: {
          title: 'Orphan 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: 'Test feedback'
        }
      })

      // Verify feedback is linked to note
      expect(feedback.noteId).toBe(note.id)

      // After rollback (dropping AiFeedback table), the note should still exist
      // but feedback would be orphaned/deleted
      const noteExists = await prisma.note.findUnique({
        where: { id: note.id }
      })

      expect(noteExists).toBeDefined()
      expect(noteExists?.id).toBe(note.id)
    })

    test('should verify no orphaned records exist after proper migration', async () => {
      // Create note with feedback
      const note = await prisma.note.create({
        data: {
          title: 'Orphan Check Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })

      await prisma.aiFeedback.create({
        data: {
          noteId: note.id,
          userId: 'test-user-id',
          feedbackType: 'thumbs_up',
          feature: 'title_suggestion',
          originalContent: 'Test feedback'
        }
      })

      // Verify no orphaned feedback (all feedback should have valid noteId)
      const allFeedback = await prisma.aiFeedback.findMany()
      
      for (const fb of allFeedback) {
        const noteExists = await prisma.note.findUnique({
          where: { id: fb.noteId }
        })
        expect(noteExists).toBeDefined()
      }
    })
  })

  describe('Rollback Safety Checks', () => {
    test('should verify data before attempting rollback', async () => {
      // Create test data
      await createSampleNotes(prisma, 10)
      
      // Count data before rollback
      const noteCountBefore = await prisma.note.count()
      expect(noteCountBefore).toBe(10)
      
      // In a real rollback scenario, you would:
      // 1. Create backup of data
      // 2. Verify backup integrity
      // 3. Execute rollback migration
      // 4. Verify data integrity after rollback
      
      // For this test, we verify we can count and validate data
      const notes = await prisma.note.findMany()
      expect(notes.length).toBe(10)
      
      for (const note of notes) {
        expect(note.id).toBeDefined()
        expect(note.title).toBeDefined()
        expect(note.content).toBeDefined()
      }
    })

    test('should identify tables created by migration', async () => {
      // Get all tables in database
      const allTables = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`
      )

      const tableNames = allTables.map((t: any) => t.name)
      
      // Identify AI-related tables (created by migration)
      const aiTables = tableNames.filter((name: string) => 
        name === 'AiFeedback' || 
        name === 'MemoryEchoInsight' || 
        name === 'UserAISettings'
      )

      // Verify AI tables exist
      expect(aiTables.length).toBeGreaterThanOrEqual(3)
    })

    test('should identify columns added by migration', async () => {
      // Get all columns in Note table
      const noteSchema = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
        `PRAGMA table_info(Note)`
      )

      const allColumns = noteSchema.map((col: any) => col.name)
      
      // Identify AI-related columns (added by migration)
      const aiColumns = allColumns.filter((name: string) =>
        name === 'autoGenerated' ||
        name === 'aiProvider' ||
        name === 'aiConfidence' ||
        name === 'language' ||
        name === 'languageConfidence' ||
        name === 'lastAiAnalysis'
      )

      // Verify all AI columns exist
      expect(aiColumns.length).toBe(6)
    })
  })

  describe('Rollback with Data', () => {
    test('should preserve essential note data', async () => {
      // Create comprehensive test data
      const notes = await createSampleAINotes(prisma, 20)
      
      // Verify all notes have essential data
      for (const note of notes) {
        expect(note.id).toBeDefined()
        expect(note.content).toBeDefined()
      }

      // After rollback, essential data should be preserved
      const allNotes = await prisma.note.findMany()
      expect(allNotes.length).toBe(20)
    })

    test('should handle rollback with complex data structures', async () => {
      // Create note with complex data
      const complexNote = await prisma.note.create({
        data: {
          title: 'Complex Note',
          content: '**Markdown** content with [links](https://example.com)',
          checkItems: JSON.stringify([
            { text: 'Task 1', done: false },
            { text: 'Task 2', done: true },
            { text: 'Task 3', done: false }
          ]),
          images: JSON.stringify([
            { url: 'image1.jpg', caption: 'Caption 1' },
            { url: 'image2.jpg', caption: 'Caption 2' }
          ]),
          labels: JSON.stringify(['label1', 'label2', 'label3']),
          userId: 'test-user-id'
        }
      })

      // Verify complex data is stored
      const retrieved = await prisma.note.findUnique({
        where: { id: complexNote.id }
      })

      expect(retrieved?.content).toContain('**Markdown**')
      expect(retrieved?.checkItems).toBeDefined()
      expect(retrieved?.images).toBeDefined()
      expect(retrieved?.labels).toBeDefined()

      // After rollback, complex data should be preserved
      if (retrieved?.checkItems) {
        const checkItems = JSON.parse(retrieved.checkItems)
        expect(checkItems.length).toBe(3)
      }
    })
  })

  describe('Rollback Error Handling', () => {
    test('should handle rollback when AI data exists', async () => {
      // Create notes with AI data
      await createSampleAINotes(prisma, 10)
      
      // Verify AI data exists
      const aiNotes = await prisma.note.findMany({
        where: {
          OR: [
            { autoGenerated: true },
            { aiProvider: { not: null } },
            { language: { not: null } }
          ]
        }
      })
      
      expect(aiNotes.length).toBeGreaterThan(0)
      
      // In a rollback scenario, this data would be lost
      // This test verifies we can detect it before rollback
      const hasAIData = await prisma.note.findFirst({
        where: {
          autoGenerated: true
        }
      })
      
      expect(hasAIData).toBeDefined()
    })

    test('should handle rollback when feedback exists', async () => {
      // Create note with feedback
      const note = await prisma.note.create({
        data: {
          title: 'Feedback Note',
          content: 'Test content',
          userId: 'test-user-id'
        }
      })

      await prisma.aiFeedback.createMany({
        data: [
          {
            noteId: note.id,
            userId: 'test-user-id',
            feedbackType: 'thumbs_up',
            feature: 'title_suggestion',
            originalContent: 'Feedback 1'
          },
          {
            noteId: note.id,
            userId: 'test-user-id',
            feedbackType: 'thumbs_down',
            feature: 'semantic_search',
            originalContent: 'Feedback 2'
          }
        ]
      })

      // Verify feedback exists
      const feedbackCount = await prisma.aiFeedback.count()
      expect(feedbackCount).toBe(2)
      
      // In a rollback scenario, this feedback would be lost
      // This test verifies we can detect it before rollback
      const feedbacks = await prisma.aiFeedback.findMany()
      expect(feedbacks.length).toBe(2)
    })
  })

  describe('Rollback Validation', () => {
    test('should validate database state after simulated rollback', async () => {
      // Create test data
      await createSampleNotes(prisma, 5)

      // Verify current state
      const noteCount = await prisma.note.count()
      expect(noteCount).toBe(5)

      // In a real rollback, we would:
      // 1. Verify data is backed up
      // 2. Execute rollback migration
      // 3. Verify AI tables/columns are removed
      // 4. Verify core data is intact
      // 5. Verify no orphaned records

      // For this test, we verify we can validate current state
      const notes = await prisma.note.findMany()
      expect(notes.every(n => n.id && n.content)).toBe(true)
    })

    test('should verify no data corruption in core tables', async () => {
      // Create comprehensive test data
      const user = await prisma.user.create({
        data: {
          email: 'corruption-test@test.com',
          name: 'Corruption Test User'
        }
      })

      const notebook = await prisma.notebook.create({
        data: {
          name: 'Corruption Test Notebook',
          order: 0,
          userId: user.id
        }
      })

      await prisma.note.create({
        data: {
          title: 'Corruption Test Note',
          content: 'Test content',
          userId: user.id,
          notebookId: notebook.id
        }
      })

      // Verify relationships are intact
      const retrievedUser = await prisma.user.findUnique({
        where: { id: user.id },
        include: { notebooks: true, notes: true }
      })

      expect(retrievedUser?.notebooks.length).toBe(1)
      expect(retrievedUser?.notes.length).toBe(1)
    })
  })
})
