<?php

declare(strict_types=1);

namespace PhpWA\Client;

use PhpWA\Auth\AuthenticationCredentials;
use PhpWA\Exception\ProtocolException;
use PhpWA\Protocol\Binary\BinaryNode;
use PhpWA\Storage\RetryDecision;

final class RetryReceiptBuilder
{
    public function build(IncomingMessageEnvelope $envelope, AuthenticationCredentials $credentials, RetryDecision $decision, ?RetryKeyMaterial $keys = null): BinaryNode
    {
        if (!$decision->shouldSend() || $decision->attempt() < 1) {
            throw new ProtocolException('Retry receipt requires an active retry decision.');
        }
        $source = $envelope->stanza()->attribute('from');
        if ($source === null) { throw new ProtocolException('Retry receipt source is missing.'); }
        $attrs = ['id' => $envelope->id(), 'type' => 'retry', 'to' => $source];
        foreach (['recipient', 'participant'] as $name) {
            $value = $envelope->stanza()->attribute($name);
            if ($value !== null) { $attrs[$name] = $value; }
        }
        $children = [
            new BinaryNode('retry', [
                'count' => (string) $decision->attempt(),
                'id' => $envelope->id(),
                't' => (string) $envelope->timestamp(),
                'v' => '1',
                'error' => '0',
            ]),
            BinaryNode::bytes('registration', [], self::bigEndian($credentials->registrationId(), 4)),
        ];
        if ($decision->attempt() > 1) {
            if (!$keys instanceof RetryKeyMaterial || $credentials->signedDeviceIdentity() === null) {
                throw new ProtocolException('Repeated retry receipt requires a complete key bundle.');
            }
            $preKey = $keys->preKey();
            $signed = $credentials->signedPreKey();
            $children[] = BinaryNode::children('keys', [], [
                BinaryNode::bytes('type', [], "\x05"),
                BinaryNode::bytes('identity', [], $credentials->signedIdentityKey()->publicKey()),
                BinaryNode::children('key', [], [
                    BinaryNode::bytes('id', [], self::bigEndian($preKey->id(), 3)),
                    BinaryNode::bytes('value', [], $preKey->keyPair()->publicKey()),
                ]),
                BinaryNode::children('skey', [], [
                    BinaryNode::bytes('id', [], self::bigEndian($signed->id(), 3)),
                    BinaryNode::bytes('value', [], $signed->keyPair()->publicKey()),
                    BinaryNode::bytes('signature', [], $signed->signature()),
                ]),
                BinaryNode::bytes('device-identity', [], $credentials->signedDeviceIdentity()),
            ]);
        }
        return BinaryNode::children('receipt', $attrs, $children);
    }

    private static function bigEndian(int $value, int $bytes): string
    {
        $result = '';
        for ($offset = $bytes - 1; $offset >= 0; $offset--) { $result .= chr(($value >> ($offset * 8)) & 255); }
        return $result;
    }
}
