<?php
// DbConfigTest.php
// Created: 2023-10-20
// Updated: 2025-04-02

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Config\{GetValueInfoCommon,GetValuesCommon,MutableConfigCommon,ScopedConfigValueInfo};
use Index\Config\Db\{DbConfig,DbConfigValueInfo};
use Index\Db\{DbBackends,DbConnection};

#[CoversClass(DbConfig::class)]
#[CoversClass(DbConfigValueInfo::class)]
#[CoversClass(MutableConfigCommon::class)]
#[CoversClass(GetValueInfoCommon::class)]
#[CoversClass(GetValuesCommon::class)]
final class DbConfigTest extends TestCase {
    private DbConnection $dbConn; // @phpstan-ignore property.uninitialized
    private DbConfig $config; // @phpstan-ignore property.uninitialized

    private const VALUES = [
        'private.allow_password_reset' => 'b:1;',
        'private.enable' => 'b:0;',
        'private.msg' => 's:71:"Things are happening. Check back later for something new... eventually.";',
        'private.perm.cat' => 's:4:"user";',
        'private.perm.val' => 'i:1;',
        'site.desc' => 's:38:"The internet\'s last convenience store.";',
        'site.ext_logo' => 's:51:"https://static.flash.moe/images/flashii-logo-v3.png";',
        'site.name' => 's:5:"Edgii";',
        'site.social.bsky' => 's:36:"https://bsky.app/profile/flashii.net";',
        'site.url' => 's:18:"https://edgii.net/";',
        'test.array' => 'a:5:{i:0;i:1234;i:1;d:56.789;i:2;s:6:"Mewow!";i:3;b:1;i:4;s:4:"jeff";}',
        'test.bool' => 'b:1;',
        'test.float' => 'd:9876.4321;',
        'test.int' => 'i:243230;',
    ];

    private const USER_VALUES = [
        'mobile.left_handed' => ['b:0;', 'b:1;'],
        'profile.allow_indexing' => ['b:1;', 'b:0;'],
    ];

    protected function setUp(): void {
        $this->dbConn = DbBackends::create('sqlite::memory:');
        $this->dbConn->execute('CREATE TABLE skh_config (config_name TEXT NOT NULL COLLATE NOCASE, config_value BLOB NOT NULL, PRIMARY KEY (config_name))');
        $this->dbConn->execute('CREATE TABLE skh_user_settings (user_id INTEGER NOT NULL, setting_name TEXT NOT NULL COLLATE NOCASE, setting_value BLOB NOT NULL, PRIMARY KEY (user_id, setting_name))');

        $stmt = $this->dbConn->prepare('INSERT INTO skh_config (config_name, config_value) VALUES (?, ?)');
        foreach(self::VALUES as $name => $value) {
            $stmt->addParameter(1, $name);
            $stmt->addParameter(2, $value);
            $stmt->execute();
        }

        $stmt = $this->dbConn->prepare('INSERT INTO skh_user_settings (user_id, setting_name, setting_value) VALUES (?, ?, ?)');
        for($i = 1; $i <= 10; ++$i)
            foreach(self::USER_VALUES as $name => $value) {
                $stmt->addParameter(1, $i);
                $stmt->addParameter(2, $name);
                $stmt->addParameter(3, $value[$i % 2]);
                $stmt->execute();
            }

        $this->config = new DbConfig($this->dbConn, 'skh_config');
    }

    public function testScoping(): void {
        $this->assertEquals('user', $this->config->getString('private.perm.cat'));
        $this->assertEquals('Edgii', $this->config->getString('site.name'));

        $scoped = $this->config->scopeTo('private', 'perm');
        $this->assertEquals('user', $scoped->getString('cat'));
    }

    public function testHasValues(): void {
        // hasValues should always return true when the list is empty
        $this->assertTrue($this->config->hasValues([]));
        $this->assertFalse($this->config->hasValues('meow'));
        $this->assertTrue($this->config->hasValues('site.desc'));
        $this->assertTrue($this->config->hasValues(['site.ext_logo', 'site.url']));
        $this->assertFalse($this->config->hasValues(['site.ext_logo', 'site.url', 'site.gun']));
    }

    public function testGetAllValueInfos(): void {
        $all = $this->config->getAllValueInfos();
        $expected = array_keys(self::VALUES);
        $values = [];

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

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

        $subset = $this->config->getAllValueInfos(2, 3);
        $expected = [
            'private.perm.cat',
            'private.perm.val',
        ];
        $values = [];

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

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

    public function testGetValues(): void {
        $this->assertNull($this->config->getValueInfo('doesnotexist'));

        $scoped = $this->config->scopeTo('private', 'perm');

        $expected = ['private.perm.cat' => 'user', 'private.perm.val' => 1];
        $values = [];
        $valueInfos = $scoped->getValueInfos(['cat', 'val', 'poop']);
        foreach($valueInfos as $valueInfo) {
            $this->assertInstanceOf(ScopedConfigValueInfo::class, $valueInfo);
            $values[$valueInfo->realName] = $valueInfo->value;
        }

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

        $scoped = $this->config->scopeTo('site')->scopeTo('social');

        $expected = [
            'bsky' => 'https://bsky.app/profile/flashii.net',
            'bsky_show' => true,
            'twitter' => '',
            'twitterShow' => false,
        ];
        $values = $scoped->getValues([
            'bsky',
            ['bsky_show:b', true],
            'twitter:s',
            ['twitter_show:b', false, 'twitterShow'],
        ]);

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

        $this->assertEquals('', $this->config->getString('none.string'));
        $this->assertEquals('test', $this->config->getString('none.string', 'test'));
        $this->assertEquals('https://edgii.net/', $this->config->getString('site.url'));
        $this->assertEquals(0, $this->config->getInteger('site.url'));

        $this->assertEquals(0, $this->config->getInteger('none.int'));
        $this->assertEquals(10, $this->config->getInteger('none.int', 10));
        $this->assertEquals(243230, $this->config->getInteger('test.int'));
        $this->assertEquals('', $this->config->getString('test.int'));

        $this->assertEquals(0, $this->config->getFloat('none.float'));
        $this->assertEquals(0.1, $this->config->getFloat('none.float', 0.1));
        $this->assertEquals(9876.4321, $this->config->getFloat('test.float'));
        $this->assertEmpty($this->config->getArray('test.float'));

        $this->assertEquals(false, $this->config->getBoolean('none.bool'));
        $this->assertEquals(true, $this->config->getBoolean('none.bool', true));
        $this->assertEquals(true, $this->config->getBoolean('test.bool'));
        $this->assertEquals(false, $this->config->getBoolean('private.msg'));
        $this->assertEquals(0, $this->config->getFloat('test.bool'));

        $this->assertEmpty($this->config->getArray('none.array'));
        $this->assertEquals(['de', 'het', 'een'], $this->config->getArray('none.array', ['de', 'het', 'een']));
        $this->assertEquals([1234, 56.789, 'Mewow!', true, 'jeff'], $this->config->getArray('test.array'));
        $this->assertEquals(false, $this->config->getBoolean('test.array'));
    }

    public function testNameValidation(): void {
        $this->assertTrue(DbConfig::validateName('th1s.iS.vAL1d'));
        $this->assertFalse(DbConfig::validateName(''));
        $this->assertFalse(DbConfig::validateName('this..is.not.valid'));
        $this->assertFalse(DbConfig::validateName('this..is.not.valid'));
        $this->assertFalse(DbConfig::validateName('First.may.Not.be.uppercase'));
    }

    public function testUserSettings(): void {
        for($i = 1; $i <= 10; ++$i) {
            $config = new DbConfig($this->dbConn, 'skh_user_settings', 'setting_name', 'setting_value', ['user_id' => $i]);

            foreach(self::USER_VALUES as $name => $value)
                $this->assertEquals(unserialize($value[$i % 2]), $config->getBoolean($name));
        }
    }
}