<?php

declare(strict_types=1);

namespace App\Services;

require_once __DIR__ . '/../Config/AppConfig.php';
require_once __DIR__ . '/LogService.php';

use Exception;
use AppConfig;
use App\Services\LogService;

/**
 * Service de chiffrement et compression des sauvegardes
 *
 * Ce service gère le processus complet de sécurisation des backups JSON :
 * 1. Compression GZIP pour réduire la taille
 * 2. Chiffrement AES-256-CBC pour la sécurité
 * 3. Déchiffrement et décompression pour la restauration
 */
class BackupEncryptionService {
    private AppConfig $appConfig;
    private string $encryptionKey;
    private array $backupConfig;

    public function __construct() {
        $this->appConfig = AppConfig::getInstance();
        $this->encryptionKey = $this->appConfig->getBackupEncryptionKey();
        $this->backupConfig = $this->appConfig->getBackupConfig();
    }

    /**
     * Chiffre et compresse les données JSON d'un backup
     * 
     * @param string $jsonData Données JSON à sauvegarder
     * @return string Données chiffrées et compressées en base64
     * @throws Exception En cas d'erreur de compression ou chiffrement
     */
    public function encryptBackup(string $jsonData): string {
        try {
            // Étape 1: Compression GZIP si activée
            $dataToEncrypt = $jsonData;
            if ($this->backupConfig['compression']) {
                $compressed = gzencode($jsonData, $this->backupConfig['compression_level']);
                if ($compressed === false) {
                    throw new Exception('Erreur lors de la compression GZIP');
                }
                $dataToEncrypt = $compressed;

                LogService::log('Compression backup réussie', [
                    'level' => 'debug',
                    'original_size' => strlen($jsonData),
                    'compressed_size' => strlen($compressed),
                    'compression_ratio' => round((1 - strlen($compressed) / strlen($jsonData)) * 100, 2) . '%'
                ]);
            }

            // Étape 2: Génération d'un IV aléatoire pour AES-256-CBC
            $ivLength = openssl_cipher_iv_length($this->backupConfig['cipher']);
            $iv = openssl_random_pseudo_bytes($ivLength);

            if ($iv === false || strlen($iv) !== $ivLength) {
                throw new Exception('Erreur lors de la génération de l\'IV');
            }

            // Étape 3: Chiffrement AES-256-CBC
            $encrypted = openssl_encrypt(
                $dataToEncrypt,
                $this->backupConfig['cipher'],
                $this->encryptionKey,
                OPENSSL_RAW_DATA,
                $iv
            );

            if ($encrypted === false) {
                throw new Exception('Erreur lors du chiffrement AES-256');
            }

            // Étape 4: Concaténation IV + données chiffrées et encodage base64
            $finalData = base64_encode($iv . $encrypted);

            LogService::log('Chiffrement backup réussi', [
                'level' => 'debug',
                'data_size' => strlen($dataToEncrypt),
                'encrypted_size' => strlen($finalData),
                'cipher' => $this->backupConfig['cipher']
            ]);

            return $finalData;
        } catch (Exception $e) {
            LogService::log('Erreur lors du chiffrement du backup', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'data_size' => strlen($jsonData)
            ]);
            throw $e;
        }
    }

    /**
     * Déchiffre et décompresse les données d'un backup
     * 
     * @param string $encryptedData Données chiffrées en base64
     * @return string Données JSON originales
     * @throws Exception En cas d'erreur de déchiffrement ou décompression
     */
    public function decryptBackup(string $encryptedData): string {
        try {
            // Étape 1: Décodage base64
            $rawData = base64_decode($encryptedData);
            if ($rawData === false) {
                throw new Exception('Erreur lors du décodage base64');
            }

            // Étape 2: Extraction de l'IV
            $ivLength = openssl_cipher_iv_length($this->backupConfig['cipher']);
            if (strlen($rawData) < $ivLength) {
                throw new Exception('Données corrompues : taille insuffisante pour l\'IV');
            }

            $iv = substr($rawData, 0, $ivLength);
            $encryptedContent = substr($rawData, $ivLength);

            // Étape 3: Déchiffrement AES-256-CBC
            $decrypted = openssl_decrypt(
                $encryptedContent,
                $this->backupConfig['cipher'],
                $this->encryptionKey,
                OPENSSL_RAW_DATA,
                $iv
            );

            if ($decrypted === false) {
                throw new Exception('Erreur lors du déchiffrement : clé invalide ou données corrompues');
            }

            LogService::log('Déchiffrement backup réussi', [
                'level' => 'debug',
                'encrypted_size' => strlen($encryptedData),
                'decrypted_size' => strlen($decrypted)
            ]);

            // Étape 4: Décompression GZIP si les données sont compressées
            $finalData = $decrypted;
            if ($this->backupConfig['compression']) {
                // Vérifier si les données sont bien compressées (magic number GZIP)
                if (substr($decrypted, 0, 2) === "\x1f\x8b") {
                    $decompressed = gzdecode($decrypted);
                    if ($decompressed === false) {
                        throw new Exception('Erreur lors de la décompression GZIP');
                    }
                    $finalData = $decompressed;

                    LogService::log('Décompression backup réussie', [
                        'level' => 'debug',
                        'compressed_size' => strlen($decrypted),
                        'decompressed_size' => strlen($decompressed)
                    ]);
                }
            }

            // Étape 5: Validation que le résultat est du JSON valide
            $jsonTest = json_decode($finalData, true);
            if ($jsonTest === null && json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('Les données déchiffrées ne sont pas du JSON valide : ' . json_last_error_msg());
            }

            return $finalData;
        } catch (Exception $e) {
            LogService::log('Erreur lors du déchiffrement du backup', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'encrypted_size' => strlen($encryptedData)
            ]);
            throw $e;
        }
    }

    /**
     * Vérifie si un fichier de backup est chiffré
     * 
     * @param string $filePath Chemin vers le fichier
     * @return bool True si le fichier est chiffré
     */
    public function isEncryptedBackup(string $filePath): bool {
        return str_ends_with($filePath, '.json.gz.enc') || str_ends_with($filePath, '.enc');
    }

    /**
     * Vérifie si un fichier de backup est compressé (mais pas chiffré)
     * 
     * @param string $filePath Chemin vers le fichier
     * @return bool True si le fichier est compressé
     */
    public function isCompressedBackup(string $filePath): bool {
        return str_ends_with($filePath, '.json.gz') && !str_ends_with($filePath, '.enc');
    }

    /**
     * Lit un fichier de backup en détectant automatiquement le format
     * 
     * @param string $filePath Chemin vers le fichier de backup
     * @return array Données JSON décodées
     * @throws Exception En cas d'erreur de lecture ou format non supporté
     */
    public function readBackupFile(string $filePath): array {
        if (!file_exists($filePath)) {
            throw new Exception("Fichier de backup non trouvé : {$filePath}");
        }

        $fileContent = file_get_contents($filePath);
        if ($fileContent === false) {
            throw new Exception("Impossible de lire le fichier : {$filePath}");
        }

        try {
            // Fichier chiffré
            if ($this->isEncryptedBackup($filePath)) {
                $jsonContent = $this->decryptBackup($fileContent);
            }
            // Fichier compressé seulement
            elseif ($this->isCompressedBackup($filePath)) {
                $jsonContent = gzdecode($fileContent);
                if ($jsonContent === false) {
                    throw new Exception('Erreur lors de la décompression du fichier');
                }
            }
            // Fichier JSON brut
            else {
                $jsonContent = $fileContent;
            }

            // Décodage JSON
            $data = json_decode($jsonContent, true);
            if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('JSON invalide : ' . json_last_error_msg());
            }

            LogService::log('Lecture backup réussie', [
                'level' => 'info',
                'file_path' => $filePath,
                'file_size' => filesize($filePath),
                'is_encrypted' => $this->isEncryptedBackup($filePath),
                'is_compressed' => $this->isCompressedBackup($filePath)
            ]);

            return $data;
        } catch (Exception $e) {
            LogService::log('Erreur lors de la lecture du backup', [
                'level' => 'error',
                'file_path' => $filePath,
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }

    /**
     * Génère le nom de fichier approprié selon la configuration
     * 
     * @param int $operationId ID de l'opération
     * @param string $timestamp Timestamp pour l'unicité
     * @param string $type Type d'export (manual, auto, etc.)
     * @return string Nom du fichier avec extension appropriée
     */
    public function generateBackupFilename(int $operationId, string $timestamp, string $type = 'manual'): string {
        $baseName = "backup_operation_{$operationId}_{$timestamp}";

        if ($type !== 'manual') {
            $baseName .= "_{$type}";
        }

        // Extension selon la configuration
        $extension = '.json';

        if ($this->backupConfig['compression']) {
            $extension .= '.gz';
        }

        // Toujours chiffré
        $extension .= '.enc';

        return $baseName . $extension;
    }

    /**
     * Retourne les statistiques de compression et chiffrement
     * 
     * @param string $originalJson JSON original
     * @param string $finalData Données finales chiffrées
     * @return array Statistiques détaillées
     */
    public function getCompressionStats(string $originalJson, string $finalData): array {
        $originalSize = strlen($originalJson);
        $finalSize = strlen($finalData);

        return [
            'original_size' => $originalSize,
            'final_size' => $finalSize,
            'size_reduction' => $originalSize - $finalSize,
            'compression_ratio' => $originalSize > 0 ? round((1 - $finalSize / $originalSize) * 100, 2) : 0,
            'is_compressed' => $this->backupConfig['compression'],
            'is_encrypted' => true,
            'cipher' => $this->backupConfig['cipher']
        ];
    }
}
