<?php

declare(strict_types=1);

namespace PhpWA\Client;

use InvalidArgumentException;
use PhpWA\Crypto\Signal\PreKeyBundle;
use PhpWA\Exception\ProtocolException;
use PhpWA\Protocol\Binary\BinaryNode;
use PhpWA\Protocol\Jid\Jid;
use PhpWA\Protocol\Jid\ServerJid;
use PhpWA\Runtime\Deadline;

final class WhatsAppPreKeyDirectory implements PreKeyDirectoryInterface
{
    /** @var QueryExecutorInterface */ private $queries;
    /** @var MessageIdGeneratorInterface */ private $ids;
    /** @var int */ private $chunkSize;
    /** @var PreKeyBundleParser */ private $parser;

    public function __construct(QueryExecutorInterface $queries, ?MessageIdGeneratorInterface $ids = null, int $chunkSize = 100)
    {
        if ($chunkSize < 1 || $chunkSize > 500) { throw new InvalidArgumentException('Pre-key chunk size is invalid.'); }
        $this->queries = $queries; $this->ids = $ids ?? new RandomMessageIdGenerator(); $this->chunkSize = $chunkSize;
        $this->parser = new PreKeyBundleParser();
    }

    public function fetch(array $deviceJids, Deadline $deadline): array
    {
        if ($deviceJids === [] || count($deviceJids) > 10000) { throw new InvalidArgumentException('Pre-key device list is empty or too large.'); }
        $devices = [];
        foreach ($deviceJids as $value) {
            $jid = is_string($value) ? Jid::parse($value) : null;
            if (!$jid instanceof Jid || (!$jid->isPhoneNumber() && !$jid->isLid()
                && !$jid->isHostedPhoneNumber() && !$jid->isHostedLid())) {
                throw new InvalidArgumentException('Pre-key request contains an invalid device JID.');
            }
            $devices[(string) $jid] = true;
        }
        $bundles = [];
        foreach (array_chunk(array_keys($devices), $this->chunkSize) as $chunk) {
            $requested = array_fill_keys($chunk, true);
            $users = [];
            foreach ($chunk as $jid) { $users[] = new BinaryNode('user', ['jid' => $jid, 'reason' => 'identity']); }
            $request = BinaryNode::children('iq', [
                'to' => ServerJid::WHATSAPP, 'type' => 'get', 'xmlns' => 'encrypt', 'id' => $this->ids->generate(),
            ], [BinaryNode::children('key', [], $users)]);
            $response = $this->queries->query($request, $deadline);
            if ($response->tag() !== 'iq' || $response->attribute('type') !== 'result') {
                throw new ProtocolException('Pre-key query returned an invalid IQ result.');
            }
            $list = $response->child('list');
            if (!$list instanceof BinaryNode) { throw new ProtocolException('Pre-key query result has no key list.'); }
            $seen = [];
            foreach ($list->childrenNamed('user') as $userNode) {
                $jid = $userNode->attribute('jid');
                if ($jid === null || !isset($requested[$jid]) || isset($seen[$jid]) || isset($bundles[$jid])) {
                    throw new ProtocolException('Pre-key result contains an unexpected or duplicate device.');
                }
                if ($userNode->child('error') instanceof BinaryNode) {
                    throw new ProtocolException('WhatsApp refused a requested pre-key bundle.');
                }
                $seen[$jid] = true;
                $bundles[$jid] = $this->parser->parse($userNode);
            }
            foreach ($chunk as $jid) {
                if (!isset($seen[$jid])) { throw new ProtocolException('Pre-key result omitted a requested device.'); }
            }
        }
        return $bundles;
    }

}
