<?php
/**
 * @package     MefisAssetBridge
 * @subpackage  MefisAssetBridgeCore_Traits
 *
 * @copyright   Copyright (C) 2025 Goran Usljebrka. All rights reserved.
 * @license     Proprietary — No resale or incorporation into other projects without explicit written permission. see LICENSE.txt
 */
declare (strict_types = 1);
namespace MefisAssetBridgeCore\Traits;

trait Encryptor
{
    private ?string $encryptorKey = null;
    public function setEncryptorKey(string $encryptorKey): void
    {
        if ($this->encryptorKey !== null) {
            return;
        }

        if (! $encryptorKey) {
            throw new \RuntimeException('API_MASTER_KEY not set in environment');
        }

        $this->encryptorKey = hash('sha256', $encryptorKey, true);
    }

    public function encrypt(string $plaintext): string
    {
        if ($this->encryptorKey === null) {
            throw new \RuntimeException('Encryptor key not set. Call setEncryptorKey() first.');
        }
        $key = $this->encryptorKey;

        if (in_array('aes-256-gcm', openssl_get_cipher_methods(), true)) {
            $cipher = 'aes-256-gcm';
            $ivLen  = openssl_cipher_iv_length($cipher);
            $iv     = random_bytes($ivLen);
            $tag    = '';
            $ct     = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
            return base64_encode(chr(1) . $iv . $tag . $ct); // store this in token_enc
        }

        // fallback AES-256-CBC + HMAC-SHA256
        $cipher = 'aes-256-cbc';
        $ivLen  = openssl_cipher_iv_length($cipher);
        $iv     = random_bytes($ivLen);
        $ct     = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
        $hmac   = hash_hmac('sha256', $iv . $ct, $key, true);
        return base64_encode(chr(0) . $iv . $hmac . $ct);
    }

    public function decrypt(string $payload, bool $isBase64 = true): ?string
    {
        if ($this->encryptorKey === null) {
            throw new \RuntimeException('Encryptor key not set. Call setEncryptorKey() first.');
        }
        $key = $this->encryptorKey;

        if ($isBase64) {
            $payload = base64_decode($payload, true);
            if ($payload === false) {
                return null;
            }
        }

        $method = ord($payload[0]); // 0 = CBC, 1 = GCM
        $payload = substr($payload, 1); // Ukloni prefiks
        if($method === 1) {
            if (in_array('aes-256-gcm', openssl_get_cipher_methods(), true)) {
                $cipher = 'aes-256-gcm';
                $ivLen  = openssl_cipher_iv_length($cipher);
                $iv     = substr($payload, 0, $ivLen);
                $tag    = substr($payload, $ivLen, 16); // GCM tag is 16 bytes
                $ct     = substr($payload, $ivLen + 16);
                $pt     = openssl_decrypt($ct, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
                return $pt === false ? null : $pt;
            }
        } elseif($method === 0) {
            // fallback AES-256-CBC + HMAC-SHA256
            $cipher = 'aes-256-cbc';
            $ivLen  = openssl_cipher_iv_length($cipher);
            $iv     = substr($payload, 0, $ivLen);
            $hmac   = substr($payload, $ivLen, 32); // sha256 binary = 32
            $ct     = substr($payload, $ivLen + 32);
            $calc   = hash_hmac('sha256', $iv . $ct, $key, true);
            if (! hash_equals($calc, $hmac)) {
                return null;
            }
            $pt = openssl_decrypt($ct, $cipher, $key, OPENSSL_RAW_DATA, $iv);
            return $pt === false ? null : $pt;
        }

        // unknown method
        return null;

    }
}
