/**
 * TASKMATE - Encryption Utilities
 * ================================
 * Verschlüsselung für Backups und sensitive Daten
 */

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const logger = require('./logger');

const ALGORITHM = 'aes-256-cbc';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16;  // 128 bits
const SALT_LENGTH = 32;
const TAG_LENGTH = 16;

/**
 * Encryption Key aus Umgebung oder generiert
 */
function getEncryptionKey() {
  let key = process.env.ENCRYPTION_KEY;
  
  if (!key) {
    // Generiere neuen Key falls nicht vorhanden
    key = crypto.randomBytes(KEY_LENGTH).toString('hex');
    logger.warn('Encryption Key wurde automatisch generiert. Speichere ihn in der .env: ENCRYPTION_KEY=' + key);
    return Buffer.from(key, 'hex');
  }
  
  // Validiere Key-Length
  if (key.length !== KEY_LENGTH * 2) { // Hex-String ist doppelt so lang
    throw new Error(`Encryption Key muss ${KEY_LENGTH * 2} Hex-Zeichen haben`);
  }
  
  return Buffer.from(key, 'hex');
}

/**
 * Key aus Passwort ableiten (PBKDF2)
 */
function deriveKeyFromPassword(password, salt) {
  return crypto.pbkdf2Sync(password, salt, 100000, KEY_LENGTH, 'sha256');
}

/**
 * Datei verschlüsseln
 */
function encryptFile(inputPath, outputPath, password = null) {
  try {
    const data = fs.readFileSync(inputPath);
    
    // Salt und IV generieren
    const salt = crypto.randomBytes(SALT_LENGTH);
    const iv = crypto.randomBytes(IV_LENGTH);
    
    // Key ableiten
    const key = password 
      ? deriveKeyFromPassword(password, salt)
      : getEncryptionKey();
    
    // Verschlüsselung
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
    
    const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
    
    // Header + Salt + IV + verschlüsselte Daten  
    const header = Buffer.from('TMENC001', 'ascii'); // TaskMate Encryption v1
    const result = Buffer.concat([
      header,
      salt,
      iv,
      encrypted
    ]);
    
    fs.writeFileSync(outputPath, result);
    logger.info(`Datei verschlüsselt: ${path.basename(inputPath)} -> ${path.basename(outputPath)}`);
    return true;
    
  } catch (error) {
    logger.error(`Verschlüsselung fehlgeschlagen: ${error.message}`);
    return false;
  }
}

/**
 * Datei entschlüsseln
 */
function decryptFile(inputPath, outputPath, password = null) {
  try {
    const encryptedData = fs.readFileSync(inputPath);
    
    // Header prüfen
    const header = encryptedData.subarray(0, 8);
    if (header.toString('ascii') !== 'TMENC001') {
      throw new Error('Ungültiges verschlüsseltes Datei-Format');
    }
    
    // Komponenten extrahieren
    let offset = 8;
    const salt = encryptedData.subarray(offset, offset + SALT_LENGTH);
    offset += SALT_LENGTH;
    
    const iv = encryptedData.subarray(offset, offset + IV_LENGTH);
    offset += IV_LENGTH;
    
    const encrypted = encryptedData.subarray(offset);
    
    // Key ableiten
    const key = password 
      ? deriveKeyFromPassword(password, salt)
      : getEncryptionKey();
    
    // Entschlüsselung
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    
    const decrypted = Buffer.concat([
      decipher.update(encrypted),
      decipher.final()
    ]);
    
    fs.writeFileSync(outputPath, decrypted);
    logger.info(`Datei entschlüsselt: ${path.basename(inputPath)} -> ${path.basename(outputPath)}`);
    return true;
    
  } catch (error) {
    logger.error(`Entschlüsselung fehlgeschlagen: ${error.message}`);
    return false;
  }
}

/**
 * String verschlüsseln (für Passwörter etc.)
 */
function encryptString(plaintext, password = null) {
  try {
    const salt = crypto.randomBytes(SALT_LENGTH);
    const iv = crypto.randomBytes(IV_LENGTH);
    
    const key = password 
      ? deriveKeyFromPassword(password, salt)
      : getEncryptionKey();
    
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
    
    const encrypted = Buffer.concat([
      cipher.update(Buffer.from(plaintext, 'utf8')),
      cipher.final()
    ]);
    
    // Base64 kodiert zurückgeben
    const result = Buffer.concat([salt, iv, encrypted]);
    return result.toString('base64');
    
  } catch (error) {
    logger.error(`String-Verschlüsselung fehlgeschlagen: ${error.message}`);
    return null;
  }
}

/**
 * String entschlüsseln
 */
function decryptString(encryptedString, password = null) {
  try {
    const data = Buffer.from(encryptedString, 'base64');
    
    let offset = 0;
    const salt = data.subarray(offset, offset + SALT_LENGTH);
    offset += SALT_LENGTH;
    
    const iv = data.subarray(offset, offset + IV_LENGTH);
    offset += IV_LENGTH;
    
    const encrypted = data.subarray(offset);
    
    const key = password 
      ? deriveKeyFromPassword(password, salt)
      : getEncryptionKey();
    
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    
    const decrypted = Buffer.concat([
      decipher.update(encrypted),
      decipher.final()
    ]);
    
    return decrypted.toString('utf8');
    
  } catch (error) {
    logger.error(`String-Entschlüsselung fehlgeschlagen: ${error.message}`);
    return null;
  }
}

/**
 * Sicheres Löschen einer Datei (Überschreiben)
 */
function secureDelete(filePath) {
  try {
    if (!fs.existsSync(filePath)) {
      return true;
    }
    
    const stats = fs.statSync(filePath);
    const fileSize = stats.size;
    
    // Datei mehrfach mit Zufallsdaten überschreiben
    const fd = fs.openSync(filePath, 'r+');
    
    for (let pass = 0; pass < 3; pass++) {
      const randomData = crypto.randomBytes(fileSize);
      fs.writeSync(fd, randomData, 0, fileSize, 0);
      fs.fsyncSync(fd);
    }
    
    fs.closeSync(fd);
    fs.unlinkSync(filePath);
    
    logger.info(`Datei sicher gelöscht: ${path.basename(filePath)}`);
    return true;
    
  } catch (error) {
    logger.error(`Sicheres Löschen fehlgeschlagen: ${error.message}`);
    return false;
  }
}

module.exports = {
  encryptFile,
  decryptFile,
  encryptString,
  decryptString,
  secureDelete,
  getEncryptionKey
};