/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

<?php

defined('BASEPATH') or exit('No direct script access allowed');

/**
 * OAuth Integration Tests
 * 
 * Comprehensive tests for OAuth 2.0 flow with Moloni API
 * 
 * @package     DeskMoloni
 * @author      Descomplicar®
 * @copyright   2025 Descomplicar
 * @version     3.0.0
 */
class OAuthIntegrationTest extends PHPUnit\Framework\TestCase
{
    private $CI;
    private $oauth;
    private $token_manager;
    private $test_client_id;
    private $test_client_secret;
    private $test_redirect_uri;
    
    protected function setUp(): void
    {
        // Get CodeIgniter instance
        $this->CI = &get_instance();
        
        // Load required libraries
        $this->CI->load->library('desk_moloni/molonioauth');
        $this->CI->load->library('desk_moloni/tokenmanager');
        
        $this->oauth = $this->CI->molonioauth;
        $this->token_manager = $this->CI->tokenmanager;
        
        // Test credentials (use environment variables or test config)
        $this->test_client_id = getenv('MOLONI_TEST_CLIENT_ID') ?: 'test_client_id';
        $this->test_client_secret = getenv('MOLONI_TEST_CLIENT_SECRET') ?: 'test_client_secret';
        $this->test_redirect_uri = 'https://test.example.com/oauth/callback';
        
        // Clear any existing tokens
        $this->token_manager->clear_tokens();
    }
    
    protected function tearDown(): void
    {
        // Clean up after tests
        $this->token_manager->clear_tokens();
        
        // Reset OAuth configuration
        update_option('desk_moloni_client_id', '');
        update_option('desk_moloni_client_secret', '');
    }
    
    /**
     * Test OAuth configuration
     */
    public function testOAuthConfiguration()
    {
        // Test initial state (not configured)
        $this->assertFalse($this->oauth->is_configured());
        
        // Test configuration
        $result = $this->oauth->configure($this->test_client_id, $this->test_client_secret, [
            'redirect_uri' => $this->test_redirect_uri,
            'use_pkce' => true
        ]);
        
        $this->assertTrue($result);
        $this->assertTrue($this->oauth->is_configured());
        
        // Test configuration persistence
        $status = $this->oauth->get_status();
        $this->assertTrue($status['configured']);
        $this->assertTrue($status['use_pkce']);
        $this->assertEquals($this->test_redirect_uri, $status['redirect_uri']);
    }
    
    /**
     * Test OAuth configuration validation
     */
    public function testOAuthConfigurationValidation()
    {
        // Test empty client ID
        $this->expectException(InvalidArgumentException::class);
        $this->oauth->configure('', $this->test_client_secret);
    }
    
    /**
     * Test OAuth configuration with invalid parameters
     */
    public function testOAuthConfigurationInvalidParameters()
    {
        // Test empty client secret
        $this->expectException(InvalidArgumentException::class);
        $this->oauth->configure($this->test_client_id, '');
    }
    
    /**
     * Test authorization URL generation
     */
    public function testAuthorizationUrlGeneration()
    {
        // Configure OAuth first
        $this->oauth->configure($this->test_client_id, $this->test_client_secret, [
            'redirect_uri' => $this->test_redirect_uri
        ]);
        
        // Generate authorization URL
        $state = 'test_state_' . time();
        $auth_url = $this->oauth->get_authorization_url($state);
        
        // Verify URL structure
        $this->assertStringContainsString('https://api.moloni.pt/v1/oauth2/authorize', $auth_url);
        $this->assertStringContainsString('client_id=' . urlencode($this->test_client_id), $auth_url);
        $this->assertStringContainsString('redirect_uri=' . urlencode($this->test_redirect_uri), $auth_url);
        $this->assertStringContainsString('state=' . $state, $auth_url);
        $this->assertStringContainsString('response_type=code', $auth_url);
        
        // Test PKCE parameters
        $this->assertStringContainsString('code_challenge=', $auth_url);
        $this->assertStringContainsString('code_challenge_method=S256', $auth_url);
    }
    
    /**
     * Test authorization URL generation without configuration
     */
    public function testAuthorizationUrlWithoutConfiguration()
    {
        $this->expectException(Exception::class);
        $this->expectExceptionMessage('OAuth not configured');
        
        $this->oauth->get_authorization_url();
    }
    
    /**
     * Test OAuth callback handling with mock data
     */
    public function testOAuthCallbackHandling()
    {
        // Configure OAuth
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        
        // Mock successful token response
        $mock_response = [
            'access_token' => 'test_access_token_' . time(),
            'refresh_token' => 'test_refresh_token_' . time(),
            'expires_in' => 3600,
            'token_type' => 'Bearer',
            'scope' => 'read write'
        ];
        
        // Save mock tokens
        $result = $this->token_manager->save_tokens($mock_response);
        $this->assertTrue($result);
        
        // Verify token storage
        $this->assertTrue($this->token_manager->are_tokens_valid());
        $this->assertEquals($mock_response['access_token'], $this->token_manager->get_access_token());
        $this->assertEquals($mock_response['refresh_token'], $this->token_manager->get_refresh_token());
    }
    
    /**
     * Test token encryption and decryption
     */
    public function testTokenEncryption()
    {
        $test_token = 'test_access_token_' . uniqid();
        
        // Test token save and retrieval
        $token_data = [
            'access_token' => $test_token,
            'refresh_token' => 'test_refresh_' . uniqid(),
            'expires_in' => 3600
        ];
        
        $result = $this->token_manager->save_tokens($token_data);
        $this->assertTrue($result);
        
        // Verify token retrieval
        $retrieved_token = $this->token_manager->get_access_token();
        $this->assertEquals($test_token, $retrieved_token);
        
        // Verify encrypted storage (tokens should not be stored in plain text)
        $stored_encrypted = get_option('desk_moloni_access_token_encrypted');
        $this->assertNotEmpty($stored_encrypted);
        $this->assertNotEquals($test_token, $stored_encrypted);
    }
    
    /**
     * Test token expiration logic
     */
    public function testTokenExpiration()
    {
        // Save token that expires in 1 second
        $token_data = [
            'access_token' => 'test_token',
            'expires_in' => 1
        ];
        
        $this->token_manager->save_tokens($token_data);
        
        // Token should be valid initially
        $this->assertTrue($this->token_manager->are_tokens_valid());
        
        // Wait for expiration
        sleep(2);
        
        // Token should be expired now
        $this->assertFalse($this->token_manager->are_tokens_valid());
    }
    
    /**
     * Test token clearing
     */
    public function testTokenClearing()
    {
        // Save some tokens
        $token_data = [
            'access_token' => 'test_token',
            'refresh_token' => 'test_refresh',
            'expires_in' => 3600
        ];
        
        $this->token_manager->save_tokens($token_data);
        $this->assertTrue($this->token_manager->are_tokens_valid());
        
        // Clear tokens
        $result = $this->token_manager->clear_tokens();
        $this->assertTrue($result);
        
        // Verify tokens are cleared
        $this->assertFalse($this->token_manager->are_tokens_valid());
        $this->assertNull($this->token_manager->get_access_token());
        $this->assertNull($this->token_manager->get_refresh_token());
    }
    
    /**
     * Test OAuth status reporting
     */
    public function testOAuthStatus()
    {
        // Test unconfigured status
        $status = $this->oauth->get_status();
        $this->assertFalse($status['configured']);
        $this->assertFalse($status['connected']);
        
        // Configure OAuth
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        
        $status = $this->oauth->get_status();
        $this->assertTrue($status['configured']);
        $this->assertFalse($status['connected']); // No tokens yet
        
        // Add tokens
        $this->token_manager->save_tokens([
            'access_token' => 'test_token',
            'expires_in' => 3600
        ]);
        
        $status = $this->oauth->get_status();
        $this->assertTrue($status['configured']);
        $this->assertTrue($status['connected']);
    }
    
    /**
     * Test OAuth configuration testing
     */
    public function testOAuthConfigurationTesting()
    {
        // Test without configuration
        $test_result = $this->oauth->test_configuration();
        $this->assertFalse($test_result['is_valid']);
        $this->assertContains('OAuth not configured', $test_result['issues']);
        
        // Configure OAuth
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        
        // Test with configuration
        $test_result = $this->oauth->test_configuration();
        
        // Should pass basic configuration tests
        $this->assertIsArray($test_result['issues']);
        $this->assertArrayHasKey('is_valid', $test_result);
        $this->assertArrayHasKey('endpoints', $test_result);
        $this->assertArrayHasKey('encryption', $test_result);
    }
    
    /**
     * Test token manager encryption validation
     */
    public function testTokenManagerEncryptionValidation()
    {
        $validation = $this->token_manager->validate_encryption();
        
        $this->assertArrayHasKey('is_valid', $validation);
        $this->assertArrayHasKey('issues', $validation);
        $this->assertArrayHasKey('cipher', $validation);
        
        // Should pass if OpenSSL is available
        if (extension_loaded('openssl')) {
            $this->assertTrue($validation['is_valid'], 'Encryption validation failed: ' . implode(', ', $validation['issues']));
        }
    }
    
    /**
     * Test token status information
     */
    public function testTokenStatus()
    {
        // Test empty status
        $status = $this->token_manager->get_token_status();
        $this->assertFalse($status['has_access_token']);
        $this->assertFalse($status['has_refresh_token']);
        $this->assertFalse($status['is_valid']);
        
        // Add tokens
        $token_data = [
            'access_token' => 'test_token',
            'refresh_token' => 'test_refresh',
            'expires_in' => 3600,
            'scope' => 'read write'
        ];
        
        $this->token_manager->save_tokens($token_data);
        
        $status = $this->token_manager->get_token_status();
        $this->assertTrue($status['has_access_token']);
        $this->assertTrue($status['has_refresh_token']);
        $this->assertTrue($status['is_valid']);
        $this->assertEquals('read write', $status['scope']);
        $this->assertGreaterThan(0, $status['expires_in']);
    }
    
    /**
     * Test PKCE implementation
     */
    public function testPKCEImplementation()
    {
        // Configure OAuth with PKCE enabled
        $this->oauth->configure($this->test_client_id, $this->test_client_secret, [
            'use_pkce' => true
        ]);
        
        // Generate authorization URL
        $auth_url = $this->oauth->get_authorization_url('test_state');
        
        // Verify PKCE parameters are included
        $this->assertStringContainsString('code_challenge=', $auth_url);
        $this->assertStringContainsString('code_challenge_method=S256', $auth_url);
        
        // Verify code verifier is stored in session (would be used in real implementation)
        $this->assertNotEmpty($this->CI->session->userdata('desk_moloni_code_verifier'));
    }
    
    /**
     * Test error handling in OAuth flow
     */
    public function testOAuthErrorHandling()
    {
        // Configure OAuth
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        
        // Test callback with error
        $this->expectException(Exception::class);
        $this->expectExceptionMessage('OAuth Error');
        
        // Simulate error callback (this would normally come from Moloni)
        $_GET['error'] = 'access_denied';
        $_GET['error_description'] = 'User denied access';
        
        $this->oauth->handle_callback('', 'test_state');
    }
    
    /**
     * Test rate limiting in OAuth requests
     */
    public function testOAuthRateLimiting()
    {
        // This test would require mocking HTTP requests
        // For now, we test that the rate limiting structure is in place
        $status = $this->oauth->get_status();
        
        $this->assertArrayHasKey('rate_limit', $status);
        $this->assertArrayHasKey('max_requests', $status['rate_limit']);
        $this->assertArrayHasKey('current_count', $status['rate_limit']);
    }
    
    /**
     * Integration test with mock HTTP responses
     */
    public function testIntegrationWithMockResponses()
    {
        // This would require a HTTP mocking library like VCR.php or Guzzle Mock
        // For demonstration, we'll test the structure is correct
        
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        
        // Verify OAuth is ready for integration
        $this->assertTrue($this->oauth->is_configured());
        
        // Verify we can generate proper authorization URLs
        $auth_url = $this->oauth->get_authorization_url();
        $this->assertStringStartsWith('https://api.moloni.pt/v1/oauth2/authorize', $auth_url);
    }
    
    /**
     * Test OAuth connection status
     */
    public function testOAuthConnectionStatus()
    {
        // Initially not connected
        $this->assertFalse($this->oauth->is_connected());
        
        // Configure OAuth
        $this->oauth->configure($this->test_client_id, $this->test_client_secret);
        $this->assertFalse($this->oauth->is_connected()); // Still no tokens
        
        // Add valid tokens
        $this->token_manager->save_tokens([
            'access_token' => 'valid_token',
            'expires_in' => 3600
        ]);
        
        $this->assertTrue($this->oauth->is_connected());
    }
    
    /**
     * Test security features
     */
    public function testSecurityFeatures()
    {
        // Test CSRF protection with state parameter
        $state1 = 'state1';
        $state2 = 'state2';
        
        $url1 = $this->oauth->get_authorization_url($state1);
        $url2 = $this->oauth->get_authorization_url($state2);
        
        $this->assertStringContainsString('state=' . $state1, $url1);
        $this->assertStringContainsString('state=' . $state2, $url2);
        
        // Test that different states produce different URLs
        $this->assertNotEquals($url1, $url2);
    }
}