<?php
/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

declare(strict_types=1);

namespace DeskMoloni\Tests\Database;

use PHPUnit\Framework\TestCase;
use PDO;

/**
 * Contract Test: desk_moloni_config table structure and constraints
 * 
 * This test MUST FAIL initially as part of TDD methodology.
 * Tests validate database schema contracts before implementation.
 */
class ConfigTableTest extends TestCase
{
    private PDO $pdo;
    private string $tableName = 'tbl_desk_moloni_config';

    protected function setUp(): void
    {
        global $testConfig;
        
        $this->pdo = new PDO(
            "mysql:host={$testConfig['database']['hostname']};dbname={$testConfig['database']['database']}",
            $testConfig['database']['username'],
            $testConfig['database']['password'],
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
            ]
        );
    }

    public function testTableExists(): void
    {
        $stmt = $this->pdo->query("SHOW TABLES LIKE '{$this->tableName}'");
        $result = $stmt->fetch();
        
        $this->assertNotFalse($result, "Table {$this->tableName} must exist");
    }

    public function testTableStructureContract(): void
    {
        $stmt = $this->pdo->query("DESCRIBE {$this->tableName}");
        $columns = $stmt->fetchAll();
        
        $expectedColumns = [
            'id' => ['Type' => 'int', 'Null' => 'NO', 'Key' => 'PRI', 'Extra' => 'auto_increment'],
            'setting_key' => ['Type' => 'varchar(255)', 'Null' => 'NO', 'Key' => 'UNI'],
            'setting_value' => ['Type' => 'text', 'Null' => 'YES'],
            'encrypted' => ['Type' => 'tinyint(1)', 'Null' => 'YES', 'Default' => '0'],
            'created_at' => ['Type' => 'timestamp', 'Null' => 'NO', 'Default' => 'CURRENT_TIMESTAMP'],
            'updated_at' => ['Type' => 'timestamp', 'Null' => 'NO', 'Default' => 'CURRENT_TIMESTAMP']
        ];
        
        $actualColumns = [];
        foreach ($columns as $column) {
            $actualColumns[$column['Field']] = [
                'Type' => $column['Type'],
                'Null' => $column['Null'],
                'Key' => $column['Key'],
                'Default' => $column['Default'],
                'Extra' => $column['Extra']
            ];
        }
        
        foreach ($expectedColumns as $columnName => $expectedSpec) {
            $this->assertArrayHasKey($columnName, $actualColumns, "Column {$columnName} must exist");
            
            foreach ($expectedSpec as $property => $expectedValue) {
                $this->assertEquals(
                    $expectedValue,
                    $actualColumns[$columnName][$property] ?? null,
                    "Column {$columnName} property {$property} must match contract"
                );
            }
        }
    }

    public function testUniqueConstraintOnSettingKey(): void
    {
        // Insert first record
        $stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
        $stmt->execute(['test_unique_key', 'test_value']);
        
        // Attempt to insert duplicate key should fail
        $this->expectException(\PDOException::class);
        $this->expectExceptionMessage('Duplicate entry');
        
        $stmt->execute(['test_unique_key', 'another_value']);
    }

    public function testEncryptionFlagValidation(): void
    {
        $stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value, encrypted) VALUES (?, ?, ?)");
        
        // Valid encryption flag values
        $stmt->execute(['test_encrypted_1', 'encrypted_value', 1]);
        $stmt->execute(['test_encrypted_0', 'plain_value', 0]);
        
        // Verify encryption flag is stored correctly
        $stmt = $this->pdo->query("SELECT setting_key, encrypted FROM {$this->tableName} WHERE setting_key IN ('test_encrypted_1', 'test_encrypted_0')");
        $results = $stmt->fetchAll();
        
        $this->assertCount(2, $results);
        
        foreach ($results as $result) {
            if ($result['setting_key'] === 'test_encrypted_1') {
                $this->assertEquals(1, $result['encrypted']);
            } else {
                $this->assertEquals(0, $result['encrypted']);
            }
        }
    }

    public function testTimestampAutomaticUpdates(): void
    {
        // Insert record
        $stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
        $stmt->execute(['test_timestamp', 'initial_value']);
        
        // Get initial timestamps
        $stmt = $this->pdo->query("SELECT created_at, updated_at FROM {$this->tableName} WHERE setting_key = 'test_timestamp'");
        $initial = $stmt->fetch();
        
        // Wait a moment and update
        sleep(1);
        $stmt = $this->pdo->prepare("UPDATE {$this->tableName} SET setting_value = ? WHERE setting_key = ?");
        $stmt->execute(['updated_value', 'test_timestamp']);
        
        // Get updated timestamps
        $stmt = $this->pdo->query("SELECT created_at, updated_at FROM {$this->tableName} WHERE setting_key = 'test_timestamp'");
        $updated = $stmt->fetch();
        
        // created_at should remain the same
        $this->assertEquals($initial['created_at'], $updated['created_at']);
        
        // updated_at should be different
        $this->assertNotEquals($initial['updated_at'], $updated['updated_at']);
        $this->assertGreaterThan($initial['updated_at'], $updated['updated_at']);
    }

    public function testRequiredIndexesExist(): void
    {
        $stmt = $this->pdo->query("SHOW INDEX FROM {$this->tableName}");
        $indexes = $stmt->fetchAll();
        
        $indexNames = array_column($indexes, 'Key_name');
        
        // Required indexes based on schema
        $expectedIndexes = ['PRIMARY', 'setting_key', 'idx_setting_key', 'idx_encrypted'];
        
        foreach ($expectedIndexes as $expectedIndex) {
            $this->assertContains(
                $expectedIndex,
                $indexNames,
                "Index {$expectedIndex} must exist for performance"
            );
        }
    }

    public function testSettingValueCanStoreJson(): void
    {
        $jsonData = json_encode([
            'complex' => 'data',
            'with' => ['nested', 'arrays'],
            'and' => 123,
            'numbers' => true
        ]);
        
        $stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
        $stmt->execute(['test_json', $jsonData]);
        
        $stmt = $this->pdo->query("SELECT setting_value FROM {$this->tableName} WHERE setting_key = 'test_json'");
        $result = $stmt->fetch();
        
        $this->assertEquals($jsonData, $result['setting_value']);
        $this->assertIsArray(json_decode($result['setting_value'], true));
    }

    public function testEncryptedSettingsHandling(): void
    {
        // Simulate encrypted data storage
        $plaintext = 'sensitive_api_key_value';
        $encryptedData = base64_encode(openssl_encrypt(
            $plaintext,
            'AES-256-GCM',
            'test_encryption_key_32_characters',
            0,
            'test_iv_12bytes'
        ));
        
        $stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value, encrypted) VALUES (?, ?, ?)");
        $stmt->execute(['oauth_access_token', $encryptedData, 1]);
        
        // Verify encrypted flag is set and data is stored
        $stmt = $this->pdo->query("SELECT setting_value, encrypted FROM {$this->tableName} WHERE setting_key = 'oauth_access_token'");
        $result = $stmt->fetch();
        
        $this->assertEquals(1, $result['encrypted']);
        $this->assertEquals($encryptedData, $result['setting_value']);
        $this->assertNotEquals($plaintext, $result['setting_value']);
    }

    protected function tearDown(): void
    {
        // Clean up test data
        $this->pdo->exec("DELETE FROM {$this->tableName} WHERE setting_key LIKE 'test_%'");
    }
}