<?php

declare(strict_types=1);

namespace App\Framework\WebPush\Services;

use App\Framework\WebPush\ValueObjects\VapidKeyPair;

/**
 * VAPID Key Generator.
 *
 * Generiert VAPID Key Pairs für Web Push Notifications nach RFC 8292.
 * Verwendet Elliptic Curve Cryptography (P-256 / prime256v1 Kurve).
 */
final readonly class VapidKeyGenerator
{
    /**
     * Generiert ein neues VAPID Key Pair.
     *
     * @throws \RuntimeException wenn OpenSSL-Extension nicht verfügbar ist
     * @throws \RuntimeException wenn Key-Generierung fehlschlägt
     */
    public function generate(): VapidKeyPair
    {
        if (! extension_loaded('openssl')) {
            throw new \RuntimeException('OpenSSL extension is required for VAPID key generation');
        }

        // Generate EC key pair using P-256 curve (prime256v1)
        $config = [
            'curve_name' => 'prime256v1',
            'private_key_type' => OPENSSL_KEYTYPE_EC,
        ];

        $resource = openssl_pkey_new($config);

        if ($resource === false) {
            throw new \RuntimeException('Failed to generate VAPID key pair: ' . openssl_error_string());
        }

        // Extract private key
        $success = openssl_pkey_export($resource, $privateKeyPem);

        if (! $success) {
            throw new \RuntimeException('Failed to export private key: ' . openssl_error_string());
        }

        // Extract public key
        $details = openssl_pkey_get_details($resource);

        if ($details === false || ! isset($details['ec']['x']) || ! isset($details['ec']['y'])) {
            throw new \RuntimeException('Failed to extract public key details');
        }

        // Convert to uncompressed format (0x04 + X + Y)
        $publicKeyBinary = "\x04" . $details['ec']['x'] . $details['ec']['y'];

        // Convert private key PEM to binary
        $privateKeyBinary = $this->extractPrivateKeyFromPem($privateKeyPem);

        // Base64 URL-safe encode
        $publicKey = $this->base64UrlEncode($publicKeyBinary);
        $privateKey = $this->base64UrlEncode($privateKeyBinary);

        return new VapidKeyPair($publicKey, $privateKey);
    }

    /**
     * Extrahiert den Private Key aus PEM Format.
     */
    private function extractPrivateKeyFromPem(string $pem): string
    {
        $lines = explode("\n", $pem);
        $keyData = '';

        $inKey = false;

        foreach ($lines as $line) {
            if (str_contains($line, 'BEGIN')) {
                $inKey = true;

                continue;
            }

            if (str_contains($line, 'END')) {
                break;
            }

            if ($inKey) {
                $keyData .= trim($line);
            }
        }

        $decoded = base64_decode($keyData, true);

        if ($decoded === false) {
            throw new \RuntimeException('Failed to decode private key');
        }

        // EC private key is the last 32 bytes in the DER-encoded structure
        // For prime256v1, the private key is 32 bytes
        return substr($decoded, -32);
    }

    /**
     * Base64 URL-safe Encoding (RFC 4648 Section 5).
     */
    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * Base64 URL-safe Decoding (RFC 4648 Section 5).
     */
    private function base64UrlDecode(string $data): string
    {
        return base64_decode(strtr($data, '-_', '+/'), true) ?: '';
    }
}
