<?php

namespace Modules\SmartStorage\Entities\Services;

use RuntimeException;

/**
 * At-rest file encryption using AES-256-GCM.
 *
 * The key is derived from the app's APP_KEY (so it rotates with the app secret
 * and is never stored alongside the data). Each file gets a fresh random IV and
 * an authentication tag, both persisted on the ss_files row. v1 encrypts whole
 * files in memory and is therefore bounded by smartstorage.encrypt_max_bytes.
 */
class FileEncryptionService
{
    private const ALGO = 'aes-256-gcm';

    private function key(): string
    {
        $appKey = (string) config('app.key');
        if (str_starts_with($appKey, 'base64:')) {
            $appKey = base64_decode(substr($appKey, 7));
        }
        // 32-byte key for AES-256, salted to keep it distinct from app encryption.
        return hash('sha256', 'smart-storage|' . $appKey, true);
    }

    public function algo(): string
    {
        return self::ALGO;
    }

    /**
     * @return array{cipher:string,iv:string,tag:string}  iv/tag are base64-encoded
     */
    public function encrypt(string $plaintext): array
    {
        $ivLen = openssl_cipher_iv_length(self::ALGO);
        $iv = random_bytes($ivLen);
        $tag = '';

        $cipher = openssl_encrypt($plaintext, self::ALGO, $this->key(), OPENSSL_RAW_DATA, $iv, $tag);
        if ($cipher === false) {
            throw new RuntimeException('SmartStorage: encryption failed.');
        }

        return [
            'cipher' => $cipher,
            'iv'     => base64_encode($iv),
            'tag'    => base64_encode($tag),
        ];
    }

    public function decrypt(string $cipher, string $ivB64, string $tagB64): string
    {
        $plain = openssl_decrypt(
            $cipher,
            self::ALGO,
            $this->key(),
            OPENSSL_RAW_DATA,
            base64_decode($ivB64),
            base64_decode($tagB64)
        );

        if ($plain === false) {
            throw new RuntimeException('SmartStorage: decryption failed (wrong key or tampered file).');
        }

        return $plain;
    }
}
