<?php

declare(strict_types=1);

/**
 * Service de chiffrement/déchiffrement AES-256-CBC
 * Compatible avec le système de chiffrement de l'API Geosector
 */
class CryptoService {
    private string $encryptionKey;
    private string $cipher = 'AES-256-CBC';

    public function __construct(string $encryptionKey) {
        // Décoder la clé base64
        $this->encryptionKey = base64_decode($encryptionKey);

        if (strlen($this->encryptionKey) !== 32) {
            throw new RuntimeException("La clé de chiffrement doit faire 32 bytes (256 bits)");
        }
    }

    /**
     * Déchiffre les données "searchable" (encrypted_user_name, encrypted_email)
     * Format: base64 simple avec IV fixe
     */
    public function decryptSearchable(?string $encryptedData): ?string {
        if (empty($encryptedData)) {
            return null;
        }

        $encrypted = base64_decode($encryptedData);
        if ($encrypted === false) {
            return null;
        }

        $iv = str_repeat("\0", 16); // IV fixe

        $decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);

        if ($decrypted === false) {
            return null;
        }

        // Supprimer le caractère de contrôle ajouté
        if (substr($decrypted, -1) === "\x01") {
            return substr($decrypted, 0, -1);
        }

        return $decrypted;
    }

    /**
     * Déchiffre les données avec IV aléatoire (encrypted_name, encrypted_phone, etc.)
     * Format: base64(IV + encrypted)
     */
    public function decryptWithIV(?string $encryptedData): ?string {
        if (empty($encryptedData)) {
            return null;
        }

        $data = base64_decode($encryptedData);
        if ($data === false) {
            return null;
        }

        $ivLength = openssl_cipher_iv_length($this->cipher);

        if (strlen($data) <= $ivLength) {
            return null;
        }

        $iv = substr($data, 0, $ivLength);
        $encrypted = substr($data, $ivLength);

        $decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);

        return $decrypted !== false ? $decrypted : null;
    }

    /**
     * Déchiffre une valeur chiffrée
     *
     * @param string|null $encryptedValue Valeur chiffrée (format base64:iv:data)
     * @return string|null Valeur déchiffrée ou null
     */
    public function decrypt(?string $encryptedValue): ?string {
        if (empty($encryptedValue)) {
            return null;
        }

        // Le format de l'API est : base64:iv:encrypted_data
        $parts = explode(':', $encryptedValue);

        if (count($parts) !== 3 || $parts[0] !== 'base64') {
            // Format invalide, peut-être déjà en clair
            return $encryptedValue;
        }

        $iv = base64_decode($parts[1]);
        $encrypted = base64_decode($parts[2]);

        if ($iv === false || $encrypted === false) {
            throw new RuntimeException("Impossible de décoder les données chiffrées");
        }

        $decrypted = openssl_decrypt(
            $encrypted,
            $this->cipher,
            $this->encryptionKey,
            OPENSSL_RAW_DATA,
            $iv
        );

        if ($decrypted === false) {
            throw new RuntimeException("Échec du déchiffrement : " . openssl_error_string());
        }

        return $decrypted;
    }

    /**
     * Chiffre une valeur
     *
     * @param string $value Valeur à chiffrer
     * @return string Valeur chiffrée (format base64:iv:data)
     */
    public function encrypt(string $value): string {
        $ivLength = openssl_cipher_iv_length($this->cipher);
        $iv = openssl_random_pseudo_bytes($ivLength);

        $encrypted = openssl_encrypt(
            $value,
            $this->cipher,
            $this->encryptionKey,
            OPENSSL_RAW_DATA,
            $iv
        );

        if ($encrypted === false) {
            throw new RuntimeException("Échec du chiffrement : " . openssl_error_string());
        }

        return 'base64:' . base64_encode($iv) . ':' . base64_encode($encrypted);
    }

    /**
     * Déchiffre plusieurs colonnes d'un tableau
     *
     * @param array $row Ligne de base de données
     * @param array $encryptedColumns Liste des colonnes à déchiffrer (sans le préfixe encrypted_)
     * @return array Tableau avec colonnes déchiffrées
     */
    public function decryptRow(array $row, array $encryptedColumns): array {
        $decrypted = $row;

        foreach ($encryptedColumns as $column) {
            $encryptedColumn = 'encrypted_' . $column;

            if (isset($row[$encryptedColumn])) {
                $decrypted[$column] = $this->decrypt($row[$encryptedColumn]);
            }
        }

        return $decrypted;
    }

    /**
     * Déchiffre les colonnes encrypted_* d'un utilisateur
     *
     * @param array $user Données utilisateur
     * @return array Utilisateur avec données déchiffrées
     */
    public function decryptUser(array $user): array {
        $columns = ['user_name', 'email', 'name', 'phone', 'mobile'];
        return $this->decryptRow($user, $columns);
    }

    /**
     * Déchiffre les colonnes encrypted_* d'une entité
     *
     * @param array $entite Données entité
     * @return array Entité avec données déchiffrées
     */
    public function decryptEntite(array $entite): array {
        $columns = ['name', 'email', 'phone', 'mobile', 'iban', 'bic'];
        return $this->decryptRow($entite, $columns);
    }
}
