<?php

namespace DeskMoloni\Tests\Unit;

/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 * 
 * Unit Test for Config_model
 * 
 * This test MUST FAIL until the Config_model is properly implemented
 * Following TDD RED-GREEN-REFACTOR cycle
 * 
 * @package DeskMoloni\Tests\Unit
 */

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;

class ConfigModelTest extends TestCase
{
    private $CI;
    private $config_model;

    protected function setUp(): void
    {
        // Initialize CodeIgniter instance
        $this->CI = &get_instance();
        
        // Ensure we're in test environment
        if (ENVIRONMENT !== 'testing') {
            $this->markTestSkipped('Unit tests should only run in testing environment');
        }
        
        // This will FAIL until Config_model is implemented
        $this->CI->load->model('desk_moloni/config_model');
        $this->config_model = $this->CI->config_model;
    }

    /**
     * Contract: Config model must be loadable and inherit from CI_Model
     */
    #[Test]
    public function config_model_exists_and_is_valid()
    {
        // ASSERT: Model must be loaded successfully
        $this->assertNotNull($this->config_model, 'Config_model must be loadable');
        $this->assertInstanceOf('CI_Model', $this->config_model, 'Config_model must inherit from CI_Model');
    }

    /**
     * Contract: Config model must provide method to get configuration value
     */
    #[Test]
    public function config_model_can_get_configuration_values()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'get'), 'Config_model must have get() method');
        
        // ACT: Try to get a configuration value
        $result = $this->config_model->get('module_version');
        
        // ASSERT: Method must return value or null
        $this->assertTrue(is_string($result) || is_null($result), 'get() method must return string or null');
    }

    /**
     * @test
     * Contract: Config model must provide method to set configuration value
     */
    public function config_model_can_set_configuration_values()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'set'), 'Config_model must have set() method');
        
        // ACT: Try to set a configuration value
        $test_key = 'test_config_key';
        $test_value = 'test_config_value';
        $result = $this->config_model->set($test_key, $test_value);
        
        // ASSERT: Method must return boolean success indicator
        $this->assertIsBool($result, 'set() method must return boolean');
        $this->assertTrue($result, 'set() method must return true on success');
        
        // ASSERT: Value must be retrievable
        $retrieved_value = $this->config_model->get($test_key);
        $this->assertEquals($test_value, $retrieved_value, 'Set value must be retrievable');
    }

    /**
     * @test
     * Contract: Config model must support encrypted configuration storage
     */
    public function config_model_supports_encrypted_storage()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'set_encrypted'), 'Config_model must have set_encrypted() method');
        $this->assertTrue(method_exists($this->config_model, 'get_encrypted'), 'Config_model must have get_encrypted() method');
        
        // ACT: Set encrypted value
        $test_key = 'test_encrypted_key';
        $test_value = 'sensitive_data_123';
        $set_result = $this->config_model->set_encrypted($test_key, $test_value);
        
        // ASSERT: Encrypted set must succeed
        $this->assertTrue($set_result, 'set_encrypted() must return true on success');
        
        // ACT: Get encrypted value
        $retrieved_value = $this->config_model->get_encrypted($test_key);
        
        // ASSERT: Encrypted value must be retrievable and match
        $this->assertEquals($test_value, $retrieved_value, 'Encrypted value must be retrievable and decrypted correctly');
        
        // ASSERT: Raw stored value must be different (encrypted)
        $raw_value = $this->config_model->get($test_key);
        $this->assertNotEquals($test_value, $raw_value, 'Raw stored value must be encrypted');
    }

    /**
     * @test
     * Contract: Config model must support OAuth token storage with expiration
     */
    public function config_model_supports_oauth_token_storage()
    {
        // ARRANGE: Ensure methods exist
        $this->assertTrue(method_exists($this->config_model, 'set_oauth_token'), 'Config_model must have set_oauth_token() method');
        $this->assertTrue(method_exists($this->config_model, 'get_oauth_token'), 'Config_model must have get_oauth_token() method');
        $this->assertTrue(method_exists($this->config_model, 'is_oauth_token_valid'), 'Config_model must have is_oauth_token_valid() method');
        
        // ACT: Set OAuth token with expiration
        $token = 'test_oauth_token_123';
        $expires_at = time() + 3600; // 1 hour from now
        $set_result = $this->config_model->set_oauth_token($token, $expires_at);
        
        // ASSERT: Token set must succeed
        $this->assertTrue($set_result, 'set_oauth_token() must return true on success');
        
        // ACT: Get OAuth token
        $token_data = $this->config_model->get_oauth_token();
        
        // ASSERT: Token data must be valid array
        $this->assertIsArray($token_data, 'get_oauth_token() must return array');
        $this->assertArrayHasKey('token', $token_data, 'Token data must have token key');
        $this->assertArrayHasKey('expires_at', $token_data, 'Token data must have expires_at key');
        $this->assertEquals($token, $token_data['token'], 'Token must match stored value');
        
        // ACT: Check token validity
        $is_valid = $this->config_model->is_oauth_token_valid();
        
        // ASSERT: Token must be valid (not expired)
        $this->assertTrue($is_valid, 'OAuth token must be valid when not expired');
    }

    /**
     * @test
     * Contract: Config model must handle expired OAuth tokens
     */
    public function config_model_handles_expired_oauth_tokens()
    {
        // ARRANGE: Set expired token
        $token = 'expired_token_123';
        $expires_at = time() - 3600; // 1 hour ago (expired)
        $this->config_model->set_oauth_token($token, $expires_at);
        
        // ACT: Check token validity
        $is_valid = $this->config_model->is_oauth_token_valid();
        
        // ASSERT: Expired token must be invalid
        $this->assertFalse($is_valid, 'Expired OAuth token must be invalid');
    }

    /**
     * @test
     * Contract: Config model must provide method to get all configuration
     */
    public function config_model_can_get_all_configuration()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'get_all'), 'Config_model must have get_all() method');
        
        // ACT: Get all configuration
        $all_config = $this->config_model->get_all();
        
        // ASSERT: Must return array
        $this->assertIsArray($all_config, 'get_all() must return array');
        
        // ASSERT: Must contain default configuration values
        $this->assertArrayHasKey('module_version', $all_config, 'Configuration must contain module_version');
        $this->assertEquals('3.0.0', $all_config['module_version'], 'Module version must be 3.0.0');
    }

    /**
     * @test
     * Contract: Config model must support configuration deletion
     */
    public function config_model_can_delete_configuration()
    {
        // ARRANGE: Set test configuration
        $test_key = 'test_delete_key';
        $test_value = 'test_delete_value';
        $this->config_model->set($test_key, $test_value);
        
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'delete'), 'Config_model must have delete() method');
        
        // ACT: Delete configuration
        $delete_result = $this->config_model->delete($test_key);
        
        // ASSERT: Delete must succeed
        $this->assertTrue($delete_result, 'delete() must return true on success');
        
        // ASSERT: Value must no longer exist
        $retrieved_value = $this->config_model->get($test_key);
        $this->assertNull($retrieved_value, 'Deleted configuration must return null');
    }

    /**
     * @test
     * Contract: Config model must validate configuration keys
     */
    public function config_model_validates_configuration_keys()
    {
        // ACT & ASSERT: Empty key must be invalid
        $this->expectException(\InvalidArgumentException::class);
        $this->config_model->set('', 'test_value');
    }

    /**
     * @test
     * Contract: Config model must handle database errors gracefully
     */
    public function config_model_handles_database_errors_gracefully()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'get'), 'Config_model must have get() method');
        
        // ACT: Try to get non-existent configuration
        $result = $this->config_model->get('non_existent_key_12345');
        
        // ASSERT: Must return null for non-existent keys
        $this->assertNull($result, 'Non-existent configuration must return null');
    }

    /**
     * @test
     * Contract: Config model must support batch operations
     */
    public function config_model_supports_batch_operations()
    {
        // ARRANGE: Ensure method exists
        $this->assertTrue(method_exists($this->config_model, 'set_batch'), 'Config_model must have set_batch() method');
        
        // ACT: Set multiple configurations
        $batch_config = [
            'batch_test_1' => 'value_1',
            'batch_test_2' => 'value_2',
            'batch_test_3' => 'value_3',
        ];
        
        $batch_result = $this->config_model->set_batch($batch_config);
        
        // ASSERT: Batch set must succeed
        $this->assertTrue($batch_result, 'set_batch() must return true on success');
        
        // ASSERT: All values must be retrievable
        foreach ($batch_config as $key => $expected_value) {
            $actual_value = $this->config_model->get($key);
            $this->assertEquals($expected_value, $actual_value, "Batch set value for '{$key}' must be retrievable");
        }
    }

    protected function tearDown(): void
    {
        // Clean up test configuration data
        if ($this->config_model) {
            $test_keys = [
                'test_config_key',
                'test_encrypted_key', 
                'test_delete_key',
                'batch_test_1',
                'batch_test_2',
                'batch_test_3'
            ];
            
            foreach ($test_keys as $key) {
                try {
                    $this->config_model->delete($key);
                } catch (Exception $e) {
                    // Ignore cleanup errors
                }
            }
        }
    }
}