<?php
/**
 * EnvCrypt Class
 *
 * @version     1.0.0
 * @package     bplan-base/globals
 * @subpackage  Env
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 * @copyright   Copyright (C) 2025, 2026 bplan-solutions GmbH & Co. KG <https://www.bplan-solutions.de/>
 * /Δ\
 */

namespace BplanBase\Globals\Env;


use RuntimeException;


/**
 * Encrypts and decrypts .env values using the application key (APP_KEY).
 *
 * Uses PHP's native OpenSSL functions so that decryption works before
 * Laravel's service container is booted (e.g. inside config/database.php).
 * APP_KEY is read directly from the environment via env().
 *
 * @version     1.0.0 / 2026-04-26
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class EnvCrypt
{


/* +++ TRAITS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS CONSTANTS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++ */


    const CIPHER = 'AES-256-CBC';


/* +++ OBJECT MEMBERS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS MEMBERS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT METHODS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS METHODS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * Returns the raw binary key derived from APP_KEY.
     *
     * @throws      RuntimeException
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-04-26
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    private static function _resolveKey(): string
    {
        $appKey = env('APP_KEY', '');

        if ($appKey === '') {
            throw new RuntimeException('EnvCrypt: APP_KEY is not set.');
        }
        if (str_starts_with($appKey, 'base64:')) {
            return base64_decode(substr($appKey, 7));
        }
        return $appKey;

    } // _resolveKey()


    /**
     * Decrypts a value that was previously encrypted with encrypt().
     *
     * Returns $default when the value is empty or cannot be decrypted.
     * Writes a plain PHP error_log() entry on failure because Laravel's
     * Log facade is not yet available when this is called from config files.
     *
     * @param       string      $encryptedValue
     *
     * @param       string|null $default
     *
     * @return      string|null
     *
     * @version     1.0.0 / 2026-04-26
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function decrypt(string $encryptedValue, string|null $default = null): string|null
    {
        if ($encryptedValue === '') {
            return $default;
        }
        try {
            $key      = self::_resolveKey();
            $payload  = json_decode(base64_decode($encryptedValue), true);

            if (!isset($payload['iv'], $payload['value'])) {
                throw new RuntimeException('Invalid payload structure.');
            }
            $iv        = base64_decode($payload['iv']);
            $decrypted = openssl_decrypt($payload['value'], self::CIPHER, $key, 0, $iv);

            if ($decrypted === false) {
                throw new RuntimeException('openssl_decrypt() returned false.');
            }
            return $decrypted;

        } catch (\Exception $E) {
            error_log('EnvCrypt: failed to decrypt .env value — APP_KEY may have changed or the value is corrupted. '.$E->getMessage());

            return $default;
        }
    } // decrypt()


    /**
     * Encrypts a plain-text value for safe storage in a .env file.
     *
     * The returned string can be passed directly to EnvFile::set().
     * Wrap it in double quotes when writing manually, as the encrypted
     * value contains characters (e.g. =, /) that require quoting in .env files.
     *
     * @param       string $plainValue
     *
     * @throws      RuntimeException
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-04-26
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function encrypt(string $plainValue): string
    {
        $key       = self::_resolveKey();
        $iv        = random_bytes(openssl_cipher_iv_length(self::CIPHER));
        $encrypted = openssl_encrypt($plainValue, self::CIPHER, $key, 0, $iv);

        if ($encrypted === false) {
            throw new RuntimeException('EnvCrypt: openssl_encrypt() failed.');
        }
        return base64_encode(json_encode([
            'iv'    => base64_encode($iv),
            'value' => $encrypted,
        ]));
    } // encrypt()


} // class EnvCrypt {}
