<?php

declare(strict_types=1);

namespace PhpWA\Client;

use PhpWA\Crypto\Signal\PreKeyBundle;
use PhpWA\Crypto\Signal\SignalCurve;
use PhpWA\Exception\ProtocolException;
use PhpWA\Protocol\Binary\BinaryNode;

/** Strict parser shared by encrypt IQ responses and incoming retry receipts. */
final class PreKeyBundleParser
{
    public function parse(BinaryNode $root): PreKeyBundle
    {
        $registration = $this->bytes($root->child('registration'), 'registration', 4);
        $unpacked = unpack('Nvalue', $registration);
        if (!is_array($unpacked) || !isset($unpacked['value'])) { throw new ProtocolException('Pre-key registration ID is invalid.'); }
        $keys = $root->child('keys');
        if (!$keys instanceof BinaryNode) { $keys = $root; }
        $identity = SignalCurve::prefix($this->bytes($keys->child('identity'), 'identity key', 32));
        $signed = $this->key($keys->child('skey'), true);
        $oneTimeNode = $keys->child('key');
        $oneTime = $oneTimeNode instanceof BinaryNode ? $this->key($oneTimeNode, false) : null;
        return new PreKeyBundle(
            $identity, (int) $unpacked['value'], $signed['id'], SignalCurve::prefix($signed['key']), (string) $signed['signature'],
            $oneTime === null ? null : $oneTime['id'], $oneTime === null ? null : SignalCurve::prefix($oneTime['key'])
        );
    }

    /** @return array{id:int,key:string,signature:string|null} */
    private function key(?BinaryNode $node, bool $signed): array
    {
        if (!$node instanceof BinaryNode || $node->tag() !== ($signed ? 'skey' : 'key')) {
            throw new ProtocolException('Pre-key result is missing required key material.');
        }
        $idBytes = $this->bytes($node->child('id'), 'key ID', 3);
        $id = (ord($idBytes[0]) << 16) | (ord($idBytes[1]) << 8) | ord($idBytes[2]);
        $key = $this->bytes($node->child('value'), 'public key', 32);
        $signature = $signed ? $this->bytes($node->child('signature'), 'signed pre-key signature', 64) : null;
        return ['id' => $id, 'key' => $key, 'signature' => $signature];
    }

    private function bytes(?BinaryNode $node, string $field, int $length): string
    {
        $value = $node instanceof BinaryNode ? $node->bytesContent() : null;
        if (!is_string($value) || strlen($value) !== $length) { throw new ProtocolException('Pre-key ' . $field . ' has an invalid length.'); }
        return $value;
    }
}
