<?php declare(strict_types=1);
/**
 * @package messenger
 * @author kputyr
 * @date 20.04.2026
 */

namespace IMATHUZH\Messenger\Tests;

use IMATHUZH\Messenger\AuthorizationToken;
use IMATHUZH\Messenger\TokenStorage;
use PHPUnit\Framework\TestCase;


class TokenStorageTest extends TestCase
{
    public static function tokenProvider(): \Generator
    {
        $tsep = '/';
        $csep = ',';

        yield "pub/sub single group" => [
            ["psg{$tsep}pub" => "chA{$csep}chB", "psg{$tsep}sub" => "chC"],
            ["psg" => ["pub" => ["chA","chB"], "sub" => ["chC"] ]]
        ];
        yield "pub only single group" => [
            ["grp{$tsep}pub" => "chA{$csep}chB"],
            ["grp" => ["pub" => ["chA","chB"]]],
        ];
        yield "sub only single group" => [
            ["grp{$tsep}sub" => "chA{$csep}chB"],
            ["grp" => ["sub" => ["chA","chB"]]],
        ];
        yield "controller single group" => [
            ["grp{$tsep}ctl" => "chA{$csep}chB"],
            ["grp" => ["ctl" => ["chA","chB"]]],
        ];
        yield "multiple groups" => [
            ["grp1{$tsep}sub" => "chA{$csep}chB", "grp2{$tsep}sub" => "ch1{$csep}ch2", "grp1{$tsep}ctl" => "chX{$csep}all"],
            ["grp1" => ["sub" => ["chA","chB"], "ctl" => ["chX","all"]], "grp2" => ["sub" => ["ch1","ch2"]]],
        ];
    }

    public function testTokenExists(): void
    {
        $storage = [
            'rw' => ['rwtest1' => 'content'],
            'ro' => ['rotest1' => 'content']
        ];
        $tokenStorage = new TokenStorage($storage['rw'], $storage['ro']);

        $this->assertFalse($tokenStorage->tokenExists('rw'));
        $this->assertFalse($tokenStorage->tokenExists('ro'));
        $this->assertTrue($tokenStorage->tokenExists('rotest1'));
        $this->assertTrue($tokenStorage->tokenExists('rwtest1'));
        $this->assertFalse($tokenStorage->tokenExists('rotest2'));
        $this->assertFalse($tokenStorage->tokenExists('rwtest2'));

        $storage['rw']['rwtest2'] = 'content';
        $storage['ro']['rotest2'] = 'content';
        $this->assertFalse($tokenStorage->tokenExists('rotest2'));
        $this->assertTrue($tokenStorage->tokenExists('rwtest2'));
    }

    public function testDeleteToken(): void
    {
        $storage = [
            'rw' => ['rwtest' => 'content'],
            'ro' => ['rotest' => 'content']
        ];
        $tokenStorage = new TokenStorage($storage['rw'], $storage['ro']);

        // Existing token in rw store can be deleted
        $this->assertTrue($tokenStorage->deleteToken('rwtest'));
        $this->assertEmpty($storage['rw']);

        // Existing token in ro store cannot be deleted
        $this->assertFalse($tokenStorage->deleteToken('rotest'));

        // Deleting a non-existing token does not throw, but returns false
        $this->assertFalse($tokenStorage->deleteToken('rwtest'));
        $this->assertFalse($tokenStorage->deleteToken('nonexistent'));
    }

    /**
     * @dataProvider tokenProvider
     */
    public function testSaveToken(array $inStore, array $channels): void
    {
        $storage = [];
        $tokenStorage = new TokenStorage($storage);

        $token = new AuthorizationToken($channels);
        $tokenId = $tokenStorage->saveToken($token, 'token');

        $this->assertEquals('token', $tokenId);
        $this->assertArrayHasKey('token', $storage);
        parse_str($storage['token'], $persisted);
        $this->assertEquals($inStore, $persisted);

        $tokenId = $tokenStorage->saveToken($token);
        $this->assertNotEquals('token', $tokenId);
        $this->assertArrayHasKey($tokenId, $storage);
        $this->assertEquals($storage['token'], $storage[$tokenId]);
    }

    /**
     * @dataProvider tokenProvider
     */
    public function testLoadToken(array $toStore, array $expanded): void
    {
        $storage = [ 'token' => http_build_query($toStore) ];
        $tokenStorage = new TokenStorage($storage);

        $loaded = $tokenStorage->loadToken('token');
        $this->assertSame($expanded, $loaded->toArray());
    }


    public function testLoadNonexistentTokenRaises(): void
    {
        $storage = [];
        $tokenStorage = new TokenStorage($storage);

        $this->expectException(\Exception::class);
        $tokenStorage->loadToken('nonexistent');
    }

}
