<?php

declare(strict_types=1);

namespace SeoScope\Security;

use RuntimeException;

final class Crypto
{
    private const PREFIX = 'enc:v1:';

    public static function encryptForApp(string $plaintext): string
    {
        $key = self::keyFromConfig();
        return self::encrypt($plaintext, $key);
    }

    public static function decryptForApp(string $ciphertext): string
    {
        $key = self::keyFromConfig();
        return self::decrypt($ciphertext, $key);
    }

    public static function isEncrypted(string $value): bool
    {
        return str_starts_with($value, self::PREFIX);
    }

    private static function keyFromConfig(): string
    {
        $cfg = $GLOBALS['seoscope_config'] ?? null;
        $appKey = is_array($cfg) ? (string) (($cfg['app']['key'] ?? '') ?: '') : '';
        if ($appKey === '') {
            throw new RuntimeException('Missing app.key in configuration.');
        }
        // Derive a fixed-length key for AES-256-GCM.
        return hash('sha256', $appKey, true);
    }

    private static function encrypt(string $plaintext, string $key): string
    {
        if ($plaintext === '') {
            return self::PREFIX;
        }
        $iv = random_bytes(12); // GCM recommended nonce size
        $tag = '';
        $cipher = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
        if (!is_string($cipher) || $cipher === '') {
            throw new RuntimeException('Encryption failed.');
        }
        if ($tag === '') {
            throw new RuntimeException('Encryption tag missing.');
        }
        return self::PREFIX . base64_encode($iv . $tag . $cipher);
    }

    private static function decrypt(string $ciphertext, string $key): string
    {
        if (!self::isEncrypted($ciphertext)) {
            // Backwards compatible: treat as plaintext.
            return $ciphertext;
        }
        $b64 = substr($ciphertext, strlen(self::PREFIX));
        if ($b64 === '') {
            return '';
        }
        $raw = base64_decode($b64, true);
        if ($raw === false || strlen($raw) < (12 + 16 + 1)) {
            throw new RuntimeException('Invalid ciphertext payload.');
        }
        $iv = substr($raw, 0, 12);
        $tag = substr($raw, 12, 16);
        $cipher = substr($raw, 28);
        $plain = openssl_decrypt($cipher, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
        if (!is_string($plain)) {
            throw new RuntimeException('Decryption failed.');
        }
        return $plain;
    }
}

