#!/usr/bin/env node
/**
 * Script zum Neuverschlüsseln aller verschlüsselten Daten
 * 
 * Verwendung:
 *   node scripts/re-encrypt-data.js [--old-key="alter-schlüssel"]
 * 
 * Wenn --old-key nicht angegeben wird, wird der Standard-Entwicklungsschlüssel verwendet.
 * Der neue Schlüssel wird aus der .env Datei gelesen (ENCRYPTION_KEY).
 */

import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import { decryptObject, encryptObject, decrypt, encrypt } from '../server/utils/encryption.js'
import dotenv from 'dotenv'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

// Lade .env Datei
dotenv.config({ path: path.join(__dirname, '..', '.env') })

// Alte Standard-Schlüssel (die möglicherweise verwendet wurden)
const OLD_DEFAULT_KEYS = [
  'default-key-change-in-production',
  'local_development_encryption_key_change_in_production'
]

// Neuer Schlüssel aus .env
const NEW_KEY = process.env.ENCRYPTION_KEY

if (!NEW_KEY) {
  console.error('❌ FEHLER: ENCRYPTION_KEY ist nicht in der .env Datei gesetzt!')
  console.error('Bitte setzen Sie ENCRYPTION_KEY in der .env Datei.')
  process.exit(1)
}

// Alten Schlüssel aus Kommandozeilenargumenten oder Standard verwenden
const args = process.argv.slice(2)
let oldKeyArg = null
for (const arg of args) {
  if (arg.startsWith('--old-key=')) {
    oldKeyArg = arg.split('=')[1]
  }
}

// Pfade bestimmen
function getDataPath(filename) {
  const cwd = process.cwd()
  if (cwd.endsWith('.output')) {
    // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
    return path.join(cwd, '../server/data', filename)
  }
  // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
  return path.join(cwd, 'server/data', filename)
}

const USERS_FILE = getDataPath('users.json')
const MEMBERS_FILE = getDataPath('members.json')
const MEMBERSHIP_APPLICATIONS_DIR = getDataPath('membership-applications')

// Backup-Verzeichnis erstellen
async function createBackup() {
  // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
  const backupDir = path.join(__dirname, '..', 'backups', `re-encrypt-${Date.now()}`)
  await fs.mkdir(backupDir, { recursive: true })
  // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
  console.log(`📦 Backup-Verzeichnis erstellt: ${backupDir}`)
  return backupDir
}

// Prüft ob Daten verschlüsselt sind
function isEncrypted(data) {
  try {
    const parsed = JSON.parse(data.trim())
    if (Array.isArray(parsed)) {
      return false
    }
    if (typeof parsed === 'object' && parsed !== null && !parsed.encryptedData) {
      return false
    }
    return false
  } catch (e) {
    return true
  }
}

// Prüft ob Daten bereits mit dem neuen Schlüssel verschlüsselt sind
async function isEncryptedWithNewKey(encryptedData) {
  try {
    await decryptObject(encryptedData, NEW_KEY)
    return true
  } catch {
    return false
  }
}

// Versucht mit verschiedenen Schlüsseln zu entschlüsseln
async function decryptWithFallback(encryptedData, keys) {
  const errors = []
  
  // Prüfe zuerst, ob die Daten bereits mit dem neuen Schlüssel verschlüsselt sind
  if (await isEncryptedWithNewKey(encryptedData)) {
    throw new Error('ALREADY_ENCRYPTED_WITH_NEW_KEY')
  }
  
  // encryptedData sollte bereits ein String sein (entweder direkt verschlüsselt oder aus einem JSON-Objekt extrahiert)
  // Versuche mit jedem Schlüssel zu entschlüsseln
  for (const key of keys) {
    try {
      // Direkt verschlüsselte Base64-Daten
      return decryptObject(encryptedData, key)
    } catch (error) {
      errors.push({ key: key.length > 30 ? key.substring(0, 30) + '...' : key, error: error.message })
      continue
    }
  }
  
  throw new Error(`Entschlüsselung fehlgeschlagen mit allen Schlüsseln:\n${errors.map(e => `  - ${e.key}: ${e.error}`).join('\n')}`)
}

// Neuverschlüsselt users.json
async function reencryptUsers(backupDir, oldKeys) {
  try {
    const data = await fs.readFile(USERS_FILE, 'utf-8')
    
    // Backup erstellen
    // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
    await fs.copyFile(USERS_FILE, path.join(backupDir, 'users.json'))
    console.log('✅ Backup von users.json erstellt')
    
    if (!isEncrypted(data)) {
      console.log('ℹ️  users.json ist nicht verschlüsselt, überspringe...')
      return
    }
    
    // Prüfe ob bereits mit neuem Schlüssel verschlüsselt
    if (await isEncryptedWithNewKey(data)) {
      console.log('ℹ️  users.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
      return
    }
    
    console.log('🔄 Entschlüssele users.json...')
    const decrypted = await decryptWithFallback(data, oldKeys)
    
    console.log('🔐 Verschlüssele users.json mit neuem Schlüssel...')
    const reencrypted = encryptObject(decrypted, NEW_KEY)
    
    await fs.writeFile(USERS_FILE, reencrypted, 'utf-8')
    console.log('✅ users.json erfolgreich neu verschlüsselt')
    
  } catch (error) {
    if (error.code === 'ENOENT') {
      console.log('ℹ️  users.json existiert nicht, überspringe...')
      return
    }
    if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
      console.log('ℹ️  users.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
      return
    }
    throw error
  }
}

// Neuverschlüsselt members.json
async function reencryptMembers(backupDir, oldKeys) {
  try {
    const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
    
    // Backup erstellen
    // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
    await fs.copyFile(MEMBERS_FILE, path.join(backupDir, 'members.json'))
    console.log('✅ Backup von members.json erstellt')
    
    if (!isEncrypted(data)) {
      console.log('ℹ️  members.json ist nicht verschlüsselt, überspringe...')
      return
    }
    
    // Prüfe ob bereits mit neuem Schlüssel verschlüsselt
    if (await isEncryptedWithNewKey(data)) {
      console.log('ℹ️  members.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
      return
    }
    
    console.log('🔄 Entschlüssele members.json...')
    const decrypted = await decryptWithFallback(data, oldKeys)
    
    console.log('🔐 Verschlüssele members.json mit neuem Schlüssel...')
    const reencrypted = encryptObject(decrypted, NEW_KEY)
    
    await fs.writeFile(MEMBERS_FILE, reencrypted, 'utf-8')
    console.log('✅ members.json erfolgreich neu verschlüsselt')
    
  } catch (error) {
    if (error.code === 'ENOENT') {
      console.log('ℹ️  members.json existiert nicht, überspringe...')
      return
    }
    if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
      console.log('ℹ️  members.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
      return
    }
    throw error
  }
}

// Neuverschlüsselt Mitgliedschaftsanträge
async function reencryptMembershipApplications(backupDir, oldKeys) {
  try {
    await fs.access(MEMBERSHIP_APPLICATIONS_DIR)
  } catch {
    console.log('ℹ️  membership-applications Verzeichnis existiert nicht, überspringe...')
    return
  }
  
  const files = await fs.readdir(MEMBERSHIP_APPLICATIONS_DIR)
  let processed = 0
  let skipped = 0
  
  for (const file of files) {
      // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
      const filePath = path.join(MEMBERSHIP_APPLICATIONS_DIR, file)
    const stat = await fs.stat(filePath)
    
    if (stat.isDirectory()) {
      continue
    }
    
    try {
      // Backup erstellen
      // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
      const backupPath = path.join(backupDir, 'membership-applications', file)
      await fs.mkdir(path.dirname(backupPath), { recursive: true })
      await fs.copyFile(filePath, backupPath)
      
      const content = await fs.readFile(filePath, 'utf-8')
      const parsed = JSON.parse(content)
      
      // Prüfe ob encryptedData Feld vorhanden ist
      if (parsed.encryptedData) {
        // Prüfe ob bereits mit neuem Schlüssel verschlüsselt
        if (await isEncryptedWithNewKey(parsed.encryptedData)) {
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`ℹ️  ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
          skipped++
        } else {
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`🔄 Entschlüssele ${file}...`)
          // Nur das encryptedData Feld entschlüsseln
          const decrypted = await decryptWithFallback(parsed.encryptedData, oldKeys)
          
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`)
          const reencrypted = encryptObject(decrypted, NEW_KEY)
          
          parsed.encryptedData = reencrypted
          await fs.writeFile(filePath, JSON.stringify(parsed, null, 2), 'utf-8')
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`✅ ${file} erfolgreich neu verschlüsselt`)
          processed++
        }
      } else if (file.endsWith('.data')) {
        // .data Dateien sind direkt verschlüsselt
        // Prüfe ob bereits mit neuem Schlüssel verschlüsselt
        if (await isEncryptedWithNewKey(content)) {
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`ℹ️  ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
          skipped++
        } else {
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`🔄 Entschlüssele ${file}...`)
          const decrypted = await decryptWithFallback(content, oldKeys)
          
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`)
          const reencrypted = encrypt(JSON.stringify(decrypted), NEW_KEY)
          
          await fs.writeFile(filePath, reencrypted, 'utf-8')
          // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
          console.log(`✅ ${file} erfolgreich neu verschlüsselt`)
          processed++
        }
      } else {
        // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
        console.log(`ℹ️  ${file} enthält keine verschlüsselten Daten, überspringe...`)
        skipped++
      }
    } catch (error) {
      // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
      // file is from readdir, not user input; error.message is safe
      // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
      console.error(`❌ Fehler beim Verarbeiten von ${file}:`, error.message)
      throw error
    }
  }
  
  // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
  console.log(`✅ Mitgliedschaftsanträge verarbeitet: ${processed} neu verschlüsselt, ${skipped} übersprungen`)
}

// Hauptfunktion
async function main() {
  console.log('🔐 Daten-Neuverschlüsselung gestartet\n')
  
  // Alte Schlüssel zusammenstellen
  const oldKeys = oldKeyArg ? [oldKeyArg, ...OLD_DEFAULT_KEYS] : OLD_DEFAULT_KEYS
  
  console.log('Alte Schlüssel (werden nacheinander versucht):')
  oldKeys.forEach((key, i) => {
    const displayKey = key.length > 50 ? key.substring(0, 50) + '...' : key
    // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
    console.log(`  ${i + 1}. ${displayKey}`)
  })
  // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
  console.log(`\nNeuer Schlüssel: ${NEW_KEY.length > 50 ? NEW_KEY.substring(0, 50) + '...' : NEW_KEY}\n`)
  
  // Bestätigung
  console.log('⚠️  WICHTIG: Dieses Script wird alle verschlüsselten Daten neu verschlüsseln!')
  console.log('📦 Backups werden automatisch erstellt.\n')
  
  // Backup-Verzeichnis erstellen
  const backupDir = await createBackup()
  
  try {
    // Dateien neu verschlüsseln
    await reencryptUsers(backupDir, oldKeys)
    console.log('')
    
    await reencryptMembers(backupDir, oldKeys)
    console.log('')
    
    await reencryptMembershipApplications(backupDir, oldKeys)
    console.log('')
    
    console.log('✅ Alle Daten erfolgreich neu verschlüsselt!')
    // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
    console.log(`📦 Backups gespeichert in: ${backupDir}`)
    
  } catch (error) {
    console.error('\n❌ FEHLER beim Neuverschlüsseln:', error.message)
    console.error('\nDie Backups sind sicher gespeichert. Sie können die Dateien manuell wiederherstellen.')
    process.exit(1)
  }
}

// Script ausführen
main().catch(error => {
  console.error('Unerwarteter Fehler:', error)
  process.exit(1)
})

