<?php
declare(strict_types=1);

namespace App;

final class Crypto
{
    private string $key;

    public function __construct(Config $config)
    {
        if (!extension_loaded('sodium')) {
            throw new \RuntimeException('libsodium extension not available');
        }

        $raw = getenv('DATA_KEY') ?: '';
        $raw = trim($raw);
        if ($raw === '') {
            throw new \RuntimeException('DATA_KEY env not set');
        }

        // base64?
        if (str_starts_with($raw, 'base64:')) {
            $raw = substr($raw, 7);
        }
        $decoded = base64_decode($raw, true);
        if ($decoded !== false && strlen($decoded) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
            $raw = $decoded;
        } elseif (ctype_xdigit($raw) && strlen($raw) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES * 2) {
            $raw = hex2bin($raw);
        }

        if (strlen($raw) < SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
            throw new \RuntimeException('DATA_KEY invalid length');
        }

        $this->key = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES);
    }

    public function encrypt(string $plaintext): string
    {
        if ($plaintext === '') {
            return '';
        }
        $nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
        $cipher = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($plaintext, '', $nonce, $this->key);
        return base64_encode($nonce . $cipher);
    }

    public function decrypt(?string $blob): string
    {
        if ($blob === null || $blob === '') {
            return '';
        }
        $raw = base64_decode($blob, true);
        if ($raw === false || strlen($raw) <= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
            return '';
        }
        $nonce = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
        $cipher = substr($raw, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
        try {
            $plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($cipher, '', $nonce, $this->key);
            return $plain === false ? '' : $plain;
        } catch (\Throwable) {
            return '';
        }
    }
}
