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

namespace IMATHUZH\Messenger\Test;

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


class AuthorizationTokenTest extends TestCase
{
    public static function channelsProvider(): \Generator
    {
        // $tokenChannels, $authenticated, $notAuthenticated

        yield 'regular channels' => [
            ['ch1', 'ch2'],
            [ ['ch1','ch2'], ['ch2', 'ch1'], ['ch1'] ],
            [ ['ch1','ch3'], ['ch3'] ]
        ];
        yield 'special channels' => [
            ['ch1', 'ch.xyz'],
            [ ['ch1','ch.xyz'], ['ch.xyz', 'ch1'], ['ch1'], ['ch.xyz'] ],
            [ ['ch2','ch.abc'], ['ch.abc'], ['ch'] ]
        ];
        yield 'pattern' => [
            ['ch*', '*hc', 'l*r', '*mid*'],
            [
                ['ch', 'hc', 'lr', 'mid', 'ch1', '2hc', 'l111r', 'xmidy'],
                ['ch-1-', '-2-hc', 'l-1-1-r', '-x-mid-y-'],
                ['ch_1_', '_2_hc', 'l_1_1_r', '_x_mid_y_']
            ],
            [
                [ 'ch.abc' ], [ 'xyz.hc' ], [ 'lex.11r' ], [ 'x:amidb' ]
            ]
        ];
        yield 'pattern for special' => [
            ['x.ch*', 'y.*hc', 'z.l*r', 'u.*mid*'],
            [
                ['x.ch', 'y.hc', 'z.lr', 'u.mid', 'x.ch1', 'y.2hc', 'z.l111r', 'u.xmidy'],
                ['x.ch-1-', 'y.-2-hc', 'z.l-1-1-r', 'u.-x-mid-y-'],
                ['x.ch_1_', 'y._2_hc', 'z.l_1_1_r', 'u._x_mid_y_']
            ],
            []
        ];
        yield 'match all regular' => [
            ['*'],
            [ ['ch1', 'ch2'], ['whatever'] ],
            [ ['x.abc'] ]
        ];
        yield 'match all prefixed' => [
            ['*.*'],
            [ ['x.abc'], ['y.cde'] ],
            [ ['regular'] ]
        ];
        yield 'match all' => [
            ['*', '*.*'],
            [ ['channel'], ['x.abv'] ],
            []
        ];
    }

    public static function authenticationChannelsProvider(): \Generator
    {
        foreach (self::channelsProvider() as $label => list($token, $accepted, $rejected)) {
            $counter = 0;
            foreach ($accepted as $channels) {
                $counter++;
                yield "$label [accepted $counter]" => [$token, $channels, true];
            }

            $counter = 0;
            foreach ($rejected as $channels) {
                $counter++;
                yield "$label [rejected $counter]" => [$token, $channels, false];
            }
        }
    }


    public static function filteredChannelsProvider(): \Generator
    {
        function channels(string $name, string $prefix='') {
            if ($prefix) $prefix .= '.';
            return ["$prefix$name", "$prefix-$name-", "$prefix$name-$name-$name"];
        }

        $regular = [
            'grp1' => [
                'pub' => channels('abc'),
                'sub' => channels('efg'),
                'ctl' => channels('mn')
            ],
            'grp2' => [
                'pub' => channels('xyz'),
                'sub' => channels('tu'),
                'ctl' => channels('wv')
            ],
            'empty' => []
        ];

        $special = [
            'grp1' => [
                'pub' => channels('abc','x'),
                'sub' => channels('efg','x'),
                'ctl' => channels('mn' ,'x')
            ],
            'grp2' => [
                'pub' => channels('xyz','z'),
                'sub' => channels('tu' ,'z'),
                'ctl' => channels('wv' ,'z')
            ]
        ];

        $patterns = [
            'grp1' => [
                'pub' => ['ch*', '*ch', '*', 'ch*ch*ch'],
                'sub' => ['hc*', '*hc', '*', 'hc*hc*hc'],
                'ctl' => ['ctl*', '*ctl']
            ]
        ];

        $specialPatterns = [
            'grp1' => [
                'pub' => ['x*.ch*', '*x.*ch', '*.*'],
                'sub' => ['x*y.hc*', '*x*.*hc', '*.*'],
                'ctl' => ['x*.ctl*', '*x.*ctl']
            ]
        ];

        $fullData = array_merge_recursive($regular, $special, $patterns, $specialPatterns);
        $keepSpecial = array_merge_recursive($regular, $special);
        $keepPatterns = array_merge_recursive($regular, $patterns);

        foreach (['grp1', 'grp2', 'empty', 'non-existent'] as $group) {
            yield "$group: all channels" => [$fullData, $group, $fullData, AuthorizationToken::KEEP_ALL];
            yield "$group: with specials"  => [$fullData, $group, $keepSpecial, AuthorizationToken::KEEP_SPECIAL];
            yield "$group: with patterns"   => [$fullData, $group, $keepPatterns, AuthorizationToken::KEEP_PATTERNS];
            yield "$group: only regular" => [$fullData, $group, $regular, 0];
        }

    }

    /**
     * @dataProvider filteredChannelsProvider
     */
    public function testAccessTokenData(array $fullData, string $group, array $filtered, int $filter): void
    {
        $token = new AuthorizationToken($fullData);
        $this->assertEquals($fullData, $token->toArray());

        $this->assertEquals($filtered[$group]['pub'] ?? null, $token->getPubChannels($group, $filter));
        $this->assertEquals($filtered[$group]['sub'] ?? null, $token->getSubChannels($group, $filter));
        $this->assertEquals($filtered[$group]['ctl'] ?? null, $token->getCtlChannels($group, $filter));
    }

    public function testSanitizeChannels(): void
    {
        $channels = [
            ' ch1', 'ch-2 ', ' c-h-2 ', "\tch_2\n", 'c_h_2', '-ch-', '_ch_',
            ' prefixed.ch2 ', '-pre-fixed.-ch-2-', '_pre_fixed._ch_2_',
            '\tch1', ' ch-2 ',
            ' pattern.*ch ', 'pa*rn.*', '*c*h*',
            'in.va.lid', 'in\\val\\id', 'in/valid', 'in$valid', 'in+valid',
            'invalid.', 'in*valid.', ' in va lid '
        ];
        $sanitized = [
            'ch1', 'ch-2', 'c-h-2', 'ch_2', 'c_h_2', '-ch-', '_ch_',
            'prefixed.ch2', '-pre-fixed.-ch-2-', '_pre_fixed._ch_2_',
            'pattern.*ch', 'pa*rn.*', '*c*h*'
        ];

        $this->assertEquals($sanitized, AuthorizationToken::sanitizeChannels($channels));
    }

    public function testCreateToken(): void
    {
        $channels1 = [' ch1 ', "\tch2\n", 'in.va.lid', 'pat*tern' ];
        $channels2 = [' _ch_3_ ', '-ch-4-', 'x.23', 'in va lid' ];

        $sanitized1 = ['ch1', 'ch2', 'pat*tern' ];
        $sanitized2 = ['_ch_3_', '-ch-4-', 'x.23' ];

        $ctlToken = AuthorizationToken::createControllerToken('group', $channels1);
        $pubToken = AuthorizationToken::createPublisherToken('group', $channels1);
        $subToken = AuthorizationToken::createListenerToken('group', $channels1);
        $msgToken = AuthorizationToken::createMessengerToken('group', $channels1, $channels2);

        $this->assertEquals([ 'group' => ['ctl' => $sanitized1] ], $ctlToken->toArray());
        $this->assertEquals([ 'group' => ['pub' => $sanitized1] ], $pubToken->toArray());
        $this->assertEquals([ 'group' => ['sub' => $sanitized1] ], $subToken->toArray());
        $this->assertEquals(
            [ 'group' => [ 'pub' => $sanitized1, 'sub' => $sanitized2 ] ],
            $msgToken->toArray()
        );
    }

    public function testIteratorAggregate(): void
    {
        $data = [
            'grp1' => [ 'pub' => ['ch1'] ],
            'grp2' => [ 'pub' => ['ch2'] ],
            'grp3' => [ 'sub' => ['ch3'] ],
        ];

        $token = new AuthorizationToken($data);

        $groups = [];
        foreach($token as $group => $channels) {
            $groups[] = $group;
            $this->assertEquals($data[$group] ?? null, $channels);
        }
        $this->assertEquals(array_keys($data), $groups);
    }

    public function testMergeTokens(): void
    {
        $token1 = new AuthorizationToken(['grp1' => ['pub' => ['ch1']]]);
        $token2 = new AuthorizationToken(['grp1' => ['pub' => ['ch2'], 'sub' => ['ch3']], 'grp2' => ['sub' => ['ch1','ch2']]]);
        $token3 = new AuthorizationToken(['grp4' => ['ctl' => ['ch1','ch2']], 'grp2' => ['sub' => ['ch1','ch3']]]);

        $expectedData = [
            'grp1' => ['pub' => ['ch1','ch2'], 'sub' => ['ch3']],
            'grp2' => ['sub' => ['ch1','ch2','ch3']],
            'grp4' => ['ctl' => ['ch1','ch2']]
        ];

        $merged = AuthorizationToken::mergeTokens($token1, $token2, $token3);

        $this->assertEquals($expectedData, $merged->toArray());
    }

    /**
     * @dataProvider authenticationChannelsProvider
     */
    public function testAuthorizesChannels(array $tokenChannels, array $requested, bool $accepted): void
    {
        $token1 = AuthorizationToken::createPublisherToken('test', $tokenChannels);
        $token2 = AuthorizationToken::createListenerToken('test', $tokenChannels);
        $token3 = AuthorizationToken::createControllerToken('test', $tokenChannels);
        if ($accepted) {
            $this->assertTrue($token1->authorizesPublisher('test', $requested));
            $this->assertTrue($token2->authorizesListener('test', $requested));
            $this->assertTrue($token3->authorizesController('test', $requested));
            $this->assertFalse($token1->authorizesPublisher('wrong-group', $requested));
            $this->assertFalse($token2->authorizesListener('wrong-group', $requested));
            $this->assertFalse($token3->authorizesController('wrong-group', $requested));
        } else {
            $this->assertFalse($token1->authorizesPublisher('test', $requested));
            $this->assertFalse($token2->authorizesListener('test', $requested));
            $this->assertFalse($token3->authorizesController('test', $requested));
        }
    }
}
