<?php

declare(strict_types=1);

namespace PhpWA\Media;

use PhpWA\Client\MessageIdGeneratorInterface;
use PhpWA\Client\QueryExecutorInterface;
use PhpWA\Client\RandomMessageIdGenerator;
use PhpWA\Exception\ProtocolException;
use PhpWA\Protocol\Binary\BinaryNode;
use PhpWA\Protocol\Jid\ServerJid;
use PhpWA\Runtime\Deadline;

final class MediaConnectionDirectory
{
    /** @var QueryExecutorInterface */ private $queries;
    /** @var MessageIdGeneratorInterface */ private $ids;
    /** @var MediaConnection|null */ private $cached;
    /** @var int */ private $cachedAt = 0;
    /** @var callable */ private $clock;
    public function __construct(QueryExecutorInterface $queries, ?MessageIdGeneratorInterface $ids = null, ?callable $clock = null)
    {
        $this->queries = $queries; $this->ids = $ids ?? new RandomMessageIdGenerator();
        $this->clock = $clock ?? static function (): int { return time(); };
    }
    public function get(Deadline $deadline, bool $force = false): MediaConnection
    {
        $now = ($this->clock)(); if (!is_int($now) || $now < 0) { throw new ProtocolException('Media connection clock is invalid.'); }
        if (!$force && $this->cached instanceof MediaConnection && $now < $this->cachedAt + $this->cached->ttl()) { return $this->cached; }
        $request = BinaryNode::children('iq', [
            'to' => ServerJid::WHATSAPP, 'type' => 'set', 'xmlns' => 'w:m', 'id' => $this->ids->generate(),
        ], [new BinaryNode('media_conn')]);
        $response = $this->queries->query($request, $deadline);
        if ($response->tag() !== 'iq' || $response->attribute('type') !== 'result') { throw new ProtocolException('Media connection query returned an invalid IQ result.'); }
        $node = $response->child('media_conn');
        if (!$node instanceof BinaryNode) { throw new ProtocolException('Media connection result is missing.'); }
        $auth = $node->attribute('auth'); $ttl = $this->decimal($node->attribute('ttl'), 'TTL', 86400);
        if ($auth === null) { throw new ProtocolException('Media connection auth token is missing.'); }
        $hosts = [];
        foreach ($node->childrenNamed('host') as $host) {
            $hostname = $host->attribute('hostname'); if ($hostname === null) { throw new ProtocolException('Media connection host is missing.'); }
            $hosts[] = $hostname;
        }
        try { $this->cached = new MediaConnection($auth, $ttl, $hosts); }
        catch (\Throwable $error) { throw new ProtocolException('Media connection result is invalid.', 0, $error); }
        $this->cachedAt = $now; return $this->cached;
    }
    private function decimal(?string $value, string $field, int $maximum): int
    {
        if ($value === null || preg_match('/^(0|[1-9][0-9]{0,9})$/D', $value) !== 1 || (int) $value < 1 || (int) $value > $maximum) {
            throw new ProtocolException('Media connection ' . $field . ' is invalid.');
        }
        return (int) $value;
    }
}
