<?php

declare(strict_types=1);

namespace PhpWA\AppState;

use PhpWA\Crypto\CryptoPrimitives;
use PhpWA\Exception\ProtocolException;
use PhpWA\Exception\MissingAppStateKeyException;
use PhpWA\Protocol\Proto\Message\AppStateSyncKeyData;
use PhpWA\Protocol\Proto\Message\AppStateSyncKeyShare;
use PhpWA\Storage\AccountTransactionInterface;

final class AppStateKeyStore
{
    public function storeShare(AccountTransactionInterface $transaction, AppStateSyncKeyShare $share): int
    {
        if (count($share->getKeys()) > 1000) { throw new ProtocolException('App-state key share contains too many keys.'); }
        $stored = 0;
        foreach ($share->getKeys() as $key) {
            $id = $key->getKeyId() === null ? '' : $key->getKeyId()->getKeyId();
            $data = $key->getKeyData();
            if ($id === '' || strlen($id) > 64 || $data === null || strlen($data->getKeyData()) !== 32) {
                throw new ProtocolException('App-state key share contains an invalid key.');
            }
            try { $encoded = $data->serializeToString(); }
            catch (\Throwable $error) { throw new ProtocolException('Unable to serialize an app-state key.', 0, $error); }
            if ($encoded === '' || strlen($encoded) > 8192) { throw new ProtocolException('Serialized app-state key is empty or too large.'); }
            $stateKey = bin2hex($id); $known = $transaction->get('app_state_key', $stateKey);
            if ($known === null) {
                $transaction->put('app_state_key', $stateKey, $encoded, 0); $stored++;
            } elseif (!hash_equals($known->value(), $encoded)) {
                throw new ProtocolException('App-state key ID conflicts with pinned key material.');
            }
        }
        return $stored;
    }

    public function load(AccountTransactionInterface $transaction, string $keyId): AppStateSyncKeyData
    {
        if ($keyId === '' || strlen($keyId) > 64) { throw new ProtocolException('App-state key ID is invalid.'); }
        $stored = $transaction->get('app_state_key', bin2hex($keyId));
        if ($stored === null) { throw new MissingAppStateKeyException('Required app-state key is not available: ' . bin2hex($keyId)); }
        $data = new AppStateSyncKeyData();
        try { $data->mergeFromString($stored->value()); }
        catch (\Throwable $error) { throw new ProtocolException('Stored app-state key protobuf is malformed.', 0, $error); }
        if (strlen($data->getKeyData()) !== 32) { throw new ProtocolException('Stored app-state key material has an invalid length.'); }
        return $data;
    }

    public function expanded(AccountTransactionInterface $transaction, string $keyId): AppStateExpandedKeys
    {
        $data = $this->load($transaction, $keyId)->getKeyData();
        return new AppStateExpandedKeys(CryptoPrimitives::hkdf($data, 160, '', 'WhatsApp Mutation Keys'));
    }
}
