<?php

declare(strict_types=1);

namespace PhpWA\Crypto;

use InvalidArgumentException;

final class PreKeyFactory
{
    /** @var RandomSourceInterface */ private $random;
    /** @var XEdDsaInterface */ private $xeddsa;

    public function __construct(?RandomSourceInterface $random = null, ?XEdDsaInterface $xeddsa = null)
    {
        $this->random = $random ?? new SystemRandomSource();
        $this->xeddsa = $xeddsa ?? new SodiumCompatXEdDsa($this->random);
    }

    public function registrationId(): int
    {
        $bytes = $this->random->bytes(2);
        if (strlen($bytes) !== 2) { throw new InvalidArgumentException('Random source returned invalid registration ID bytes.'); }
        return (((ord($bytes[0]) << 8) | ord($bytes[1])) & 0x3fff);
    }

    public function preKey(int $id): PreKey
    {
        return new PreKey($id, X25519::generate($this->random));
    }

    public function signedPreKey(KeyPair $identity, int $id, ?KeyPair $keyPair = null): SignedPreKey
    {
        $keyPair = $keyPair ?? X25519::generate($this->random);
        $signature = $this->xeddsa->sign($identity->privateKey(), self::signalPublicKey($keyPair->publicKey()));
        return new SignedPreKey($id, $keyPair, $signature);
    }

    /** @return list<PreKey> */
    public function preKeys(int $startId, int $count): array
    {
        if ($count < 0 || $count > 1000 || $startId < 0 || $startId + $count > 16777216) {
            throw new InvalidArgumentException('Signal pre-key batch is out of range.');
        }
        $keys = [];
        for ($id = $startId; $id < $startId + $count; $id++) { $keys[] = $this->preKey($id); }
        return $keys;
    }

    public static function signalPublicKey(string $publicKey): string
    {
        if (strlen($publicKey) !== 32) { throw new InvalidArgumentException('Signal Curve25519 public key must contain 32 bytes.'); }
        return "\x05" . $publicKey;
    }
}
