<?php

declare(strict_types=1);

namespace App\Framework\Encryption;

use App\Framework\Random\RandomGenerator;
use RuntimeException;

/**
 * AES-256-CBC encryption implementation
 */
final readonly class AesEncryption implements EncryptionInterface
{
    private const METHOD = 'AES-256-CBC';
    private const PREFIX = 'ENC[';
    private const SUFFIX = ']';
    private const IV_LENGTH = 16;
    private const KEY_LENGTH = 32;

    public function __construct(
        private RandomGenerator $randomGenerator,
        private string $encryptionKey
    ) {
        if (! $this->validateKey($this->encryptionKey)) {
            throw new RuntimeException('Invalid encryption key length for AES-256');
        }

        if (! extension_loaded('openssl')) {
            throw new RuntimeException('OpenSSL extension is required for AES encryption');
        }
    }

    public function encrypt(string $data): string
    {
        $iv = $this->randomGenerator->bytes(self::IV_LENGTH);
        $encrypted = openssl_encrypt($data, self::METHOD, $this->encryptionKey, 0, $iv);

        if ($encrypted === false) {
            throw new RuntimeException('Failed to encrypt data: ' . openssl_error_string());
        }

        $payload = base64_encode($iv . $encrypted);

        return self::PREFIX . $payload . self::SUFFIX;
    }

    public function decrypt(string $encryptedData): string
    {
        if (! $this->isEncrypted($encryptedData)) {
            throw new RuntimeException('Data does not appear to be encrypted with this method');
        }

        // Remove prefix and suffix
        $payload = substr(
            $encryptedData,
            strlen(self::PREFIX),
            -strlen(self::SUFFIX)
        );

        $data = base64_decode($payload);
        if ($data === false) {
            throw new RuntimeException('Invalid base64 encoding in encrypted data');
        }

        if (strlen($data) < self::IV_LENGTH) {
            throw new RuntimeException('Encrypted data is too short to contain valid IV');
        }

        $iv = substr($data, 0, self::IV_LENGTH);
        $encrypted = substr($data, self::IV_LENGTH);

        $decrypted = openssl_decrypt($encrypted, self::METHOD, $this->encryptionKey, 0, $iv);

        if ($decrypted === false) {
            throw new RuntimeException('Failed to decrypt data: ' . openssl_error_string());
        }

        return $decrypted;
    }

    public function isEncrypted(string $data): bool
    {
        return str_starts_with($data, self::PREFIX) && str_ends_with($data, self::SUFFIX);
    }

    public function getMethod(): string
    {
        return self::METHOD;
    }

    public function validateKey(string $key): bool
    {
        return strlen($key) >= self::KEY_LENGTH;
    }

    /**
     * Generate a new encryption key suitable for AES-256
     */
    public function generateKey(): string
    {
        return base64_encode($this->randomGenerator->bytes(self::KEY_LENGTH));
    }

    /**
     * Get encryption metadata for debugging/logging
     */
    public function getMetadata(): array
    {
        return [
            'method' => self::METHOD,
            'key_length' => self::KEY_LENGTH,
            'iv_length' => self::IV_LENGTH,
            'prefix' => self::PREFIX,
            'suffix' => self::SUFFIX,
            'openssl_version' => OPENSSL_VERSION_TEXT,
        ];
    }
}
