<?php

declare(strict_types=1);

namespace PhpWA\Transport\Noise;

use PhpWA\Crypto\CryptoPrimitives;
use PhpWA\Exception\CryptoException;

final class NoiseTransportState
{
    /** @var string */ private $encryptKey;
    /** @var string */ private $decryptKey;
    /** @var int */ private $readCounter = 0;
    /** @var int */ private $writeCounter = 0;
    /** @var bool */ private $poisoned = false;

    public function __construct(string $encryptKey, string $decryptKey)
    {
        if (strlen($encryptKey) !== 32 || strlen($decryptKey) !== 32) {
            throw new CryptoException('Noise transport keys must contain exactly 32 bytes.');
        }
        $this->encryptKey = $encryptKey;
        $this->decryptKey = $decryptKey;
    }

    public function encrypt(string $plaintext): string
    {
        $this->requireUsable($this->writeCounter);
        try {
            $ciphertext = CryptoPrimitives::aesGcmEncrypt($this->encryptKey, $plaintext, NoiseHandler::iv($this->writeCounter));
            $this->writeCounter++;
            return $ciphertext;
        } catch (\Throwable $error) {
            $this->poisoned = true;
            throw $error;
        }
    }

    public function decrypt(string $ciphertext): string
    {
        $this->requireUsable($this->readCounter);
        try {
            $plaintext = CryptoPrimitives::aesGcmDecrypt($this->decryptKey, $ciphertext, NoiseHandler::iv($this->readCounter));
            $this->readCounter++;
            return $plaintext;
        } catch (\Throwable $error) {
            $this->poisoned = true;
            throw $error;
        }
    }

    public function readCounter(): int { return $this->readCounter; }
    public function writeCounter(): int { return $this->writeCounter; }
    public function isPoisoned(): bool { return $this->poisoned; }

    private function requireUsable(int $counter): void
    {
        if ($this->poisoned) {
            throw new CryptoException('Noise transport state is unusable after an authentication failure.');
        }
        if ($counter > 4294967295) {
            $this->poisoned = true;
            throw new CryptoException('Noise nonce counter exhausted; reconnect is required.');
        }
    }
}
