<?php

declare(strict_types=1);

namespace WhmcsMigrate;

use RuntimeException;

final class Encryptor
{
    private const string CIPHER = 'aes-256-cbc';

    private const int IV_LENGTH = 16;

    public function __construct(
        private readonly string $key,
    ) {
        if (strlen($this->key) !== 32) {
            throw new RuntimeException(
                'Encryption key must be exactly 32 bytes for AES-256-CBC. Got ' . strlen($this->key) . ' bytes.',
            );
        }
    }

    /**
     * Encrypt a value using Laravel-compatible AES-256-CBC encryption.
     *
     * The output format matches Laravel's Illuminate\Encryption\Encrypter exactly:
     * base64(json({ iv: base64, value: base64(encrypted), mac: hmac_hex, tag: '' }))
     */
    public function encrypt(string $value): string
    {
        $iv = random_bytes(self::IV_LENGTH);

        // openssl_encrypt with default options (no OPENSSL_RAW_DATA flag) returns base64-encoded ciphertext
        $encrypted = openssl_encrypt($value, self::CIPHER, $this->key, 0, $iv);

        if ($encrypted === false) {
            throw new RuntimeException('Encryption failed: ' . openssl_error_string());
        }

        $ivBase64 = base64_encode($iv);

        // HMAC is computed over the base64-encoded IV concatenated with the base64-encoded ciphertext
        $mac = hash_hmac('sha256', $ivBase64 . $encrypted, $this->key);

        $payload = json_encode([
            'iv' => $ivBase64,
            'value' => $encrypted,
            'mac' => $mac,
            'tag' => '',
        ], JSON_THROW_ON_ERROR);

        return base64_encode($payload);
    }

    /**
     * Encrypt an array by JSON-encoding it first.
     */
    public function encryptArray(array $data): string
    {
        return $this->encrypt(json_encode($data, JSON_THROW_ON_ERROR));
    }
}
