<?php
declare(strict_types=1);

/**
 * UltraCrypt PHP SDK v1 — dependency-free AES-256-GCM helper.
 * Keep this file outside public web roots when possible.
 */
final class UltraCrypt
{
    private const PREFIX = 'UC01';
    private const CIPHER = 'aes-256-gcm';
    private const NONCE_BYTES = 12;
    private const TAG_BYTES = 16;

    /** Generate once, store in a secret manager or environment variable. */
    public static function generateKey(): string
    {
        return base64_encode(random_bytes(32));
    }

    /** Encrypt a UTF-8 string. $key must be a base64-encoded 32-byte key. */
    public static function encrypt(string $plaintext, string $key): string
    {
        $rawKey = self::key($key);
        $nonce = random_bytes(self::NONCE_BYTES);
        $tag = '';
        $ciphertext = openssl_encrypt($plaintext, self::CIPHER, $rawKey, OPENSSL_RAW_DATA, $nonce, $tag, '', self::TAG_BYTES);
        if ($ciphertext === false || strlen($tag) !== self::TAG_BYTES) {
            throw new RuntimeException('UltraCrypt: encryption failed.');
        }
        return self::PREFIX.'.'.self::b64url($nonce).'.'.self::b64url($ciphertext).'.'.self::b64url($tag);
    }

    /** Decrypt a UC01 value. Throws if the input is altered or the key is wrong. */
    public static function decrypt(string $container, string $key): string
    {
        $parts = explode('.', $container);
        if (count($parts) !== 4 || $parts[0] !== self::PREFIX) {
            throw new InvalidArgumentException('UltraCrypt: invalid UC01 container.');
        }
        [$nonce, $ciphertext, $tag] = array_map([self::class, 'unb64url'], array_slice($parts, 1));
        if (strlen($nonce) !== self::NONCE_BYTES || strlen($tag) !== self::TAG_BYTES) {
            throw new InvalidArgumentException('UltraCrypt: invalid nonce or authentication tag.');
        }
        $plaintext = openssl_decrypt($ciphertext, self::CIPHER, self::key($key), OPENSSL_RAW_DATA, $nonce, $tag);
        if ($plaintext === false) {
            throw new RuntimeException('UltraCrypt: authentication failed (wrong key or modified data).');
        }
        return $plaintext;
    }

    private static function key(string $key): string
    {
        $raw = base64_decode($key, true);
        if ($raw === false || strlen($raw) !== 32) {
            throw new InvalidArgumentException('UltraCrypt: key must be base64-encoded and exactly 32 bytes.');
        }
        return $raw;
    }
    private static function b64url(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); }
    private static function unb64url(string $data): string
    {
        if (!preg_match('/^[A-Za-z0-9_-]*$/', $data)) throw new InvalidArgumentException('UltraCrypt: invalid encoding.');
        $out = base64_decode(strtr($data, '-_', '+/').str_repeat('=', (4 - strlen($data) % 4) % 4), true);
        if ($out === false) throw new InvalidArgumentException('UltraCrypt: invalid encoding.');
        return $out;
    }
}
