<?php
declare(strict_types=1);

namespace Celena\Core\Plugin;

use RuntimeException;

/**
 * Ed25519-подпись артефактов маркетплейса (supply-chain).
 *
 * Модель доверия:
 *   - Реестр (marketplace.celena.io) держит ПРИВАТНЫЙ ключ (env MARKETPLACE_SIGN_SECRET)
 *     и подписывает каждую версию пакета при публикации.
 *   - Каждый клиент Celena хранит ПУБЛИЧНЫЙ ключ реестра вне пакета
 *     (config marketplace.pubkey ← env MARKETPLACE_PUBKEY) и проверяет подпись
 *     скачанного архива перед установкой.
 *
 * Подписывается канонический payload `slug|version|sha256(zip)` — он привязывает
 * подпись к конкретному содержимому конкретной версии конкретного пакета, поэтому
 * подменить архив на CDN/в транспорте или переиспользовать чужую подпись нельзя
 * (без приватного ключа подпись не подделать). Ключ берётся НЕ из манифеста —
 * иначе злоумышленник просто переподписал бы пакет своим ключом.
 */
final class PackageSigner
{
    public static function available(): bool
    {
        return function_exists('sodium_crypto_sign_detached');
    }

    public static function payload(string $slug, string $version, string $sha256): string
    {
        return $slug . '|' . $version . '|' . strtolower(trim($sha256));
    }

    /**
     * Сгенерировать пару ключей.
     * @return array{public:string, secret:string} base64
     */
    public static function keypair(): array
    {
        if (!self::available()) {
            throw new RuntimeException('libsodium недоступен — невозможно сгенерировать ключи.');
        }
        $kp = sodium_crypto_sign_keypair();
        return [
            'public' => base64_encode(sodium_crypto_sign_publickey($kp)),
            'secret' => base64_encode(sodium_crypto_sign_secretkey($kp)),
        ];
    }

    /** Подписать (сервер реестра). Возвращает base64 подписи. */
    public static function sign(string $secretB64, string $slug, string $version, string $sha256): string
    {
        if (!self::available()) {
            throw new RuntimeException('libsodium недоступен — невозможно подписать пакет.');
        }
        $sk = base64_decode(trim($secretB64), true);
        if ($sk === false || strlen($sk) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
            throw new RuntimeException('Невалидный приватный ключ подписи (MARKETPLACE_SIGN_SECRET).');
        }
        return base64_encode(sodium_crypto_sign_detached(self::payload($slug, $version, $sha256), $sk));
    }

    /** Проверить подпись скачанного артефакта (клиент). Любая ошибка/несоответствие → false. */
    public static function verify(string $pubB64, string $slug, string $version, string $sha256, string $sigB64): bool
    {
        if (!self::available() || $pubB64 === '' || $sigB64 === '') {
            return false;
        }
        $pk = base64_decode(trim($pubB64), true);
        $sig = base64_decode(trim($sigB64), true);
        if ($pk === false || strlen($pk) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) {
            return false;
        }
        if ($sig === false || strlen($sig) !== SODIUM_CRYPTO_SIGN_BYTES) {
            return false;
        }
        return sodium_crypto_sign_verify_detached($sig, self::payload($slug, $version, $sha256), $pk);
    }
}
