<?php
// FsConfigTest.php
// Created: 2023-10-20
// Updated: 2025-01-18

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Config\{GetValueInfoCommon,GetValuesCommon,ImmutableConfigCommon,ScopedConfigValueInfo};
use Index\Config\Fs\{FsConfig,FsConfigValueInfo};

#[CoversClass(FsConfig::class)]
#[CoversClass(FsConfigValueInfo::class)]
#[CoversClass(ImmutableConfigCommon::class)]
#[CoversClass(GetValueInfoCommon::class)]
#[CoversClass(GetValuesCommon::class)]
final class FsConfigTest extends TestCase {
    public function testImmutableRemove(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test value')->removeValues('test');
    }

    public function testImmutableSetString(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test value')->setString('test', 'the');
    }

    public function testImmutableSetInteger(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test 1234')->setInteger('test', 5678);
    }

    public function testImmutableSetFloat(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test 56.78')->setFloat('test', 12.34);
    }

    public function testImmutableSetBoolean(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test true')->setBoolean('test', false);
    }

    public function testImmutableSetArray(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('test words words words')->setArray('test', ['meow', 'meow', 'meow']);
    }

    public function testImmutableSetValues(): void {
        $this->expectException(\RuntimeException::class);
        FsConfig::fromString('')->setValues([
            'stringval' => 'the',
            'intval' => 1234,
            'floatval' => 56.78,
            'boolval' => true,
            'arrval' => ['meow'],
        ]);
    }

    public function testScoping(): void {
        $config = FsConfig::fromLines([
            'test Inaccessible',
            'scoped:test Accessible',
        ]);

        $this->assertEquals('Inaccessible', $config->getString('test'));
        $this->assertEquals('Accessible', $config->getString('scoped:test'));

        $scoped = $config->scopeTo('scoped');
        $this->assertEquals('Accessible', $scoped->getString('test'));
    }

    public function testHasValues(): void {
        $config = FsConfig::fromLines([
            'test 123',
            'scoped:test true',
            'scoped:meow meow',
        ]);

        // hasValues should always return true when the list is empty
        $this->assertTrue($config->hasValues([]));
        $this->assertFalse($config->hasValues('meow'));
        $this->assertTrue($config->hasValues('test'));
        $this->assertFalse($config->hasValues(['test', 'meow']));
        $this->assertTrue($config->hasValues(['scoped:test', 'scoped:meow']));
    }

    public function testGetAllValueInfos(): void {
        $config = FsConfig::fromFile(__DIR__ . '/FsConfigTest.cfg');

        $all = $config->getAllValueInfos();
        $expected = [
            'chat:port',
            'chat:msgMaxLength',
            'chat:floodKickLength',
            'chat:channels',
            'chat:channels:lounge:name',
            'chat:channels:lounge:autoJoin',
            'chat:channels:prog:name',
            'chat:channels:games:name',
            'chat:channels:splat:name',
            'chat:channels:passwd:name',
            'chat:channels:passwd:password',
            'chat:channels:staff:name',
            'chat:channels:staff:minRank',
            'msz:secret',
            'msz:url',
            'mariadb:host',
            'mariadb:user',
            'mariadb:pass',
            'mariadb:db',
        ];
        $values = [];

        foreach($all as $info)
            $values[] = $info->name;

        $this->assertEquals($expected, $values);

        $subset = $config->getAllValueInfos(3, 6);
        $expected = [
            'chat:channels:prog:name',
            'chat:channels:games:name',
            'chat:channels:splat:name',
        ];
        $values = [];

        foreach($subset as $info)
            $values[] = $info->name;

        $this->assertEquals($expected, $values);
    }

    public function testGetValues(): void {
        $config = FsConfig::fromFile(__DIR__ . '/FsConfigTest.cfg');

        $this->assertNull($config->getValueInfo('doesnotexist'));

        $scoped = $config->scopeTo('chat')->scopeTo('channels', 'passwd');

        $expected = ['chat:channels:passwd:name' => 'Password', 'chat:channels:passwd:password' => 'meow'];
        $values = [];
        $valueInfos = $scoped->getValueInfos(['name', 'password', 'minRank']);
        foreach($valueInfos as $valueInfo) {
            $this->assertInstanceOf(ScopedConfigValueInfo::class, $valueInfo);
            $values[$valueInfo->realName] = $valueInfo->value;
        }

        $this->assertEquals($expected, $values);

        $scoped = $config->scopeTo('chat', 'channels', 'lounge');

        $expected = [
            'name' => 'Lounge',
            'auto_join' => true,
            'minRank' => 0,
        ];
        $values = $scoped->getValues([
            'name',
            ['autoJoin:b', false, 'auto_join'],
            'minRank:i',
        ]);

        $this->assertEquals($expected, $values);

        $this->assertEquals('', $config->getString('msz:url2'));
        $this->assertEquals('test', $config->getString('msz:url2', 'test'));
        $this->assertEquals('https://flashii.net/_sockchat', $config->getString('msz:url'));

        $this->assertEquals(0, $config->getInteger('chat:connMaxCount'));
        $this->assertEquals(10, $config->getInteger('chat:connMaxCount', 10));
        $this->assertEquals(30, $config->getInteger('chat:floodKickLength'));
        $this->assertEquals('30', $config->getString('chat:floodKickLength'));

        $this->assertEquals(0, $config->getFloat('boat'));
        $this->assertEquals(0.1, $config->getFloat('boat', 0.1));
        $this->assertEquals(192.168, $config->getFloat('mariadb:host'));
        $this->assertEquals('192.168.0.123', $config->getString('mariadb:host'));

        $this->assertEquals(false, $config->getBoolean('nonexist'));
        $this->assertEquals(true, $config->getBoolean('nonexist', true));
        $this->assertEquals(true, $config->getBoolean('chat:channels:lounge:autoJoin'));
        $this->assertEquals('true', $config->getString('chat:channels:lounge:autoJoin'));
        $this->assertEquals(true, $config->getBoolean('mariadb:db'));

        $this->assertEmpty($config->getArray('nonexist'));
        $this->assertEquals(['de', 'het', 'een'], $config->getArray('nonexist', ['de', 'het', 'een']));
        $this->assertEquals(['lounge', 'prog', 'games', 'splat', 'passwd', 'staff'], $config->getArray('chat:channels'));
        $this->assertEquals('lounge prog games splat passwd staff', $config->getString('chat:channels'));
        $this->assertEquals(['fake', 'secret', 'meow'], $config->getArray('msz:secret'));
        $this->assertEquals('fake secret meow', $config->getString('msz:secret'));
    }
}