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

namespace IMATHUZH\Messenger\Tests;

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

class AuthorizationTokenTest extends TestCase
{
    public function testSanitizeChannels(): void
    {
        $inputString = " ch1, ch2 ,ch1, invalid!channel, group.channel, group.* ";
        $inputArray = [" ch1 ", "ch2", "ch1", "invalid!channel", "group.channel", "group.*"];
        $expected = ["ch1", "ch2", "group.channel", "group.*"];

        $this->assertEquals($expected, AuthorizationToken::sanitizeChannels($inputString));
        $this->assertEquals($expected, AuthorizationToken::sanitizeChannels($inputArray));
    }

    public function testCreateControllerToken(): void
    {
        $token = AuthorizationToken::createControllerToken("grp1", "ch1,ch2");
        $expected = ["grp1" => ["ctl" => ["ch1", "ch2"]]];
        $this->assertEquals($expected, $token->toArray());
    }

    public function testCreatePublisherToken(): void
    {
        $token = AuthorizationToken::createPublisherToken("grp1", "ch1,ch2");
        $expected = ["grp1" => ["pub" => ["ch1", "ch2"]]];
        $this->assertEquals($expected, $token->toArray());
    }

    public function testCreateListenerToken(): void
    {
        $token = AuthorizationToken::createListenerToken("grp1", "ch1,ch2");
        $expected = ["grp1" => ["sub" => ["ch1", "ch2"]]];
        $this->assertEquals($expected, $token->toArray());
    }

    public function testCreateMessengerToken(): void
    {
        $token = AuthorizationToken::createMessengerToken("grp1", "pub1,pub2", "sub1,sub2");
        $expected = ["grp1" => [
            "pub" => ["pub1", "pub2"],
            "sub" => ["sub1", "sub2"]
        ]];
        $this->assertEquals($expected, $token->toArray());
    }

    public function testMergeTokens(): void
    {
        $t1 = AuthorizationToken::createPublisherToken("grp1", "ch1,ch2");
        $t2 = AuthorizationToken::createPublisherToken("grp1", "ch2,ch3");
        $t3 = AuthorizationToken::createListenerToken("grp1", "sub1");
        $t4 = AuthorizationToken::createPublisherToken("grp2", "other");

        $merged = AuthorizationToken::mergeTokens($t1, $t2, $t3, $t4);
        $expected = [
            "grp1" => [
                "pub" => ["ch1", "ch2", "ch3"],
                "sub" => ["sub1"]
            ],
            "grp2" => [
                "pub" => ["other"]
            ]
        ];
        $this->assertEquals($expected, $merged->toArray());
    }

    public function testGetIterator(): void
    {
        $data = ["grp1" => ["pub" => ["ch1"]]];
        $token = new AuthorizationToken($data);
        $result = iterator_to_array($token);
        $this->assertEquals($data, $result);
    }

    public function testGetChannelsWithFlags(): void
    {
        $data = ["grp1" => [
            "pub" => ["pch1", "pgroup.ch2", "ppattern*", "pgroup.*"],
            "sub" => ["sch1", "sgroup.ch2", "spattern*", "sgroup.*"]
        ]];
        $token = new AuthorizationToken($data);

        // Default / KEEP_NONE (only alphanumeric and hyphens)
        $this->assertEquals(["pch1"], $token->getPubChannels("grp1"));
        $this->assertEquals(["sch1"], $token->getSubChannels("grp1"));
        
        // KEEP_SPECIAL (alphanumeric, hyphens and one dot)
        $this->assertEquals(["pch1", "pgroup.ch2"], $token->getPubChannels("grp1", TokenInterface::KEEP_SPECIAL));
        $this->assertEquals(["sch1", "sgroup.ch2"], $token->getSubChannels("grp1", TokenInterface::KEEP_SPECIAL));

        // KEEP_PATTERNS (alphanumeric, hyphens and asterisks)
        $this->assertEquals(["pch1", "ppattern*"], $token->getPubChannels("grp1", TokenInterface::KEEP_PATTERNS));
        $this->assertEquals(["sch1", "spattern*"], $token->getSubChannels("grp1", TokenInterface::KEEP_PATTERNS));

        // KEEP_ALL
        $this->assertEquals(["pch1", "pgroup.ch2", "ppattern*", "pgroup.*"], $token->getPubChannels("grp1", TokenInterface::KEEP_ALL));
        $this->assertEquals(["sch1", "sgroup.ch2", "spattern*", "sgroup.*"], $token->getSubChannels("grp1", TokenInterface::KEEP_ALL));
        
        // Non-existent group
        $this->assertNull($token->getPubChannels("missing"));
        $this->assertNull($token->getSubChannels("missing"));
    }

    public function testAuthorizesPublisher(): void
    {
        $token = AuthorizationToken::createPublisherToken("grp1", "ch1,pattern*,group.*");

        $this->assertTrue($token->authorizesPublisher("grp1", ["ch1"]));
        $this->assertTrue($token->authorizesPublisher("grp1", ["pattern-match"]));
        $this->assertTrue($token->authorizesPublisher("grp1", ["group.sub"]));
        $this->assertTrue($token->authorizesPublisher("grp1", ["ch1", "pattern-123"]));
        
        $this->assertFalse($token->authorizesPublisher("grp1", ["ch2"]));
        $this->assertFalse($token->authorizesPublisher("grp1", ["ch1", "unauthorized"]));
        $this->assertFalse($token->authorizesPublisher("missing", ["ch1"]));
    }

    public function testAuthorizesListener(): void
    {
        $token = AuthorizationToken::createListenerToken("grp1", "ch1,pattern*,group.*");

        $this->assertTrue($token->authorizesListener("grp1", ["ch1"]));
        $this->assertTrue($token->authorizesListener("grp1", ["pattern-match"]));
        $this->assertTrue($token->authorizesListener("grp1", ["group.sub"]));
        $this->assertTrue($token->authorizesListener("grp1", ["ch1", "pattern-123"]));
        
        $this->assertFalse($token->authorizesListener("grp1", ["ch2"]));
        $this->assertFalse($token->authorizesListener("grp1", ["ch1", "unauthorized"]));
        $this->assertFalse($token->authorizesListener("missing", ["ch1"]));
    }

    public function testAuthorizesController(): void
    {
        $token = AuthorizationToken::createControllerToken("grp1", "ch1,pattern*,group.*");

        $this->assertTrue($token->authorizesController("grp1", ["ch1"]));
        $this->assertTrue($token->authorizesController("grp1", ["pattern-match"]));
        $this->assertTrue($token->authorizesController("grp1", ["group.sub"]));
        $this->assertTrue($token->authorizesController("grp1", ["ch1", "pattern-123"]));
        
        $this->assertFalse($token->authorizesController("grp1", ["ch2"]));
        $this->assertFalse($token->authorizesController("grp1", ["ch1", "unauthorized"]));
        $this->assertFalse($token->authorizesController("missing", ["ch1"]));
    }
}
