<?php

declare(strict_types=1);

namespace App\Services\QzTray;

use App\Repositories\QzCertificate\QzCertificateRepositoryInterface;
use Carbon\Carbon;

final class SigningService
{
    public function __construct(
        private QzCertificateRepositoryInterface $repository,
        private KeyStorageService $keyStorage
    ) {}

    /**
     * Sign a given payload with the active private key.
     *
     * @param string $payload
     * @return string Base64 encoded signature.
     * @throws \RuntimeException
     */
    public function sign(string $payload): string
    {
        $cert = $this->repository->getActive();

        if (!$cert) {
            throw new \RuntimeException('No active QZ Tray certificate found.');
        }

        if ($cert->status === 'invalid' || ($cert->expires_at && Carbon::now()->greaterThan($cert->expires_at))) {
            throw new \RuntimeException('The active QZ Tray certificate is expired or invalid.');
        }

        if (!$cert->private_key_path) {
            throw new \RuntimeException('Active certificate private key path is missing.');
        }

        // Retrieve and decrypt the private key PEM
        $privateKeyPem = $this->keyStorage->retrieveKey($cert->private_key_path);

        if (!$privateKeyPem) {
            throw new \RuntimeException('Failed to retrieve or decrypt private key.');
        }

        // Load private key resource
        $privateKeyResource = openssl_pkey_get_private($privateKeyPem);
        if ($privateKeyResource === false) {
            throw new \RuntimeException('Invalid private key structure: ' . openssl_error_string());
        }

        // Determine signing algorithm
        $algo = $this->getAlgorithmConstant();

        // Sign the payload
        $signature = '';
        $success = openssl_sign($payload, $signature, $privateKeyResource, $algo);

        if (!$success) {
            throw new \RuntimeException('Failed to generate signature: ' . openssl_error_string());
        }

        // Update the last used timestamp
        $this->repository->update($cert, [
            'last_used_at' => Carbon::now()
        ]);

        return base64_encode($signature);
    }

    /**
     * Get the OpenSSL signature algorithm constant.
     *
     * @return int
     */
    private function getAlgorithmConstant(): int
    {
        $algo = strtolower(config('qztray.signature_algorithm', 'sha256'));

        return match ($algo) {
            'sha512' => OPENSSL_ALGO_SHA512,
            'sha1' => OPENSSL_ALGO_SHA1,
            default => OPENSSL_ALGO_SHA256,
        };
    }
}
