<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Unit;

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

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\DataProvider;
use ReflectionClass;
use stdClass;

/**
 * MoloniApiClientTest
 * 
 * Unit tests for MoloniApiClient class
 * Tests API communication, rate limiting, retry logic, and error handling
 * 
 * @package DeskMoloni\Tests\Unit
 * @author Development Helper
 * @version 1.0.0
 */
#[CoversClass('MoloniApiClient')]
class MoloniApiClientTest extends TestCase
{
    private $api_client;
    private $reflection;
    private $ci_mock;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        // Mock CodeIgniter instance
        $this->ci_mock = $this->createMock(stdClass::class);
        $this->ci_mock->config = $this->createMock(stdClass::class);
        $this->ci_mock->load = $this->createMock(stdClass::class);
        
        // Mock get_instance function
        if (!function_exists('get_instance')) {
            function get_instance() {
                return $GLOBALS['CI_INSTANCE'];
            }
        }
        $GLOBALS['CI_INSTANCE'] = $this->ci_mock;
        
        // Create MoloniApiClient instance
        require_once 'modules/desk_moloni/libraries/MoloniApiClient.php';
        $this->api_client = new MoloniApiClient();
        
        // Setup reflection for testing private methods
        $this->reflection = new ReflectionClass($this->api_client);
    }
    
    #[Test]
    #[Group('unit')]
    public function testApiClientInitialization(): void
    {
        $this->assertInstanceOf(MoloniApiClient::class, $this->api_client);
        
        // Test default configuration values
        $api_base_url = $this->getPrivateProperty('api_base_url');
        $this->assertEquals('https://api.moloni.pt/v1/', $api_base_url);
        
        $api_timeout = $this->getPrivateProperty('api_timeout');
        $this->assertEquals(30, $api_timeout);
        
        $max_retries = $this->getPrivateProperty('max_retries');
        $this->assertEquals(3, $max_retries);
    }
    
    #[Test]
    #[Group('unit')]
    public function testSetApiCredentials(): void
    {
        $client_id = 'test_client_id';
        $client_secret = 'test_client_secret';
        $username = 'test@example.com';
        $password = 'test_password';
        
        $this->api_client->set_credentials($client_id, $client_secret, $username, $password);
        
        // Verify credentials are stored (would need to access private properties)
        $this->assertTrue(true); // Placeholder - actual implementation would verify storage
    }
    
    #[Test]
    #[Group('unit')]
    public function testRateLimitingConfiguration(): void
    {
        $requests_per_minute = $this->getPrivateProperty('requests_per_minute');
        $requests_per_hour = $this->getPrivateProperty('requests_per_hour');
        
        $this->assertEquals(60, $requests_per_minute);
        $this->assertEquals(1000, $requests_per_hour);
    }
    
    #[Test]
    #[Group('unit')]
    public function testBuildApiUrl(): void
    {
        $method = $this->getPrivateMethod('build_api_url');
        
        $result = $method->invokeArgs($this->api_client, ['customers/getAll']);
        $expected = 'https://api.moloni.pt/v1/customers/getAll';
        
        $this->assertEquals($expected, $result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testValidateApiResponse(): void
    {
        $method = $this->getPrivateMethod('validate_api_response');
        
        // Test valid response
        $valid_response = [
            'valid' => 1,
            'data' => ['id' => 123, 'name' => 'Test Customer']
        ];
        
        $result = $method->invokeArgs($this->api_client, [$valid_response]);
        $this->assertTrue($result);
        
        // Test invalid response
        $invalid_response = [
            'valid' => 0,
            'errors' => ['Invalid request']
        ];
        
        $result = $method->invokeArgs($this->api_client, [$invalid_response]);
        $this->assertFalse($result);
    }
    
    #[Test]
    #[Group('unit')]
    #[DataProvider('httpStatusProvider')]
    public function testHandleHttpStatus(int $status_code, bool $expected_success): void
    {
        $method = $this->getPrivateMethod('handle_http_status');
        
        $result = $method->invokeArgs($this->api_client, [$status_code, 'Test response']);
        
        if ($expected_success) {
            $this->assertTrue($result['success']);
        } else {
            $this->assertFalse($result['success']);
            $this->assertArrayHasKey('error', $result);
        }
    }
    
    public static function httpStatusProvider(): array
    {
        return [
            'Success 200' => [200, true],
            'Created 201' => [201, true],
            'Bad Request 400' => [400, false],
            'Unauthorized 401' => [401, false],
            'Forbidden 403' => [403, false],
            'Not Found 404' => [404, false],
            'Rate Limited 429' => [429, false],
            'Internal Error 500' => [500, false]
        ];
    }
    
    #[Test]
    #[Group('unit')]
    public function testRetryLogic(): void
    {
        $method = $this->getPrivateMethod('should_retry_request');
        
        // Test retryable errors
        $retryable_cases = [500, 502, 503, 504, 429];
        foreach ($retryable_cases as $status_code) {
            $result = $method->invokeArgs($this->api_client, [$status_code, 1]);
            $this->assertTrue($result, "Status {$status_code} should be retryable");
        }
        
        // Test non-retryable errors
        $non_retryable_cases = [400, 401, 403, 404, 422];
        foreach ($non_retryable_cases as $status_code) {
            $result = $method->invokeArgs($this->api_client, [$status_code, 1]);
            $this->assertFalse($result, "Status {$status_code} should not be retryable");
        }
        
        // Test max retries exceeded
        $result = $method->invokeArgs($this->api_client, [500, 4]);
        $this->assertFalse($result, "Should not retry when max retries exceeded");
    }
    
    #[Test]
    #[Group('unit')]
    public function testCalculateRetryDelay(): void
    {
        $method = $this->getPrivateMethod('calculate_retry_delay');
        
        // Test exponential backoff
        $delay1 = $method->invokeArgs($this->api_client, [1]);
        $delay2 = $method->invokeArgs($this->api_client, [2]);
        $delay3 = $method->invokeArgs($this->api_client, [3]);
        
        $this->assertGreaterThan(0, $delay1);
        $this->assertGreaterThan($delay1, $delay2);
        $this->assertGreaterThan($delay2, $delay3);
        
        // Test maximum delay cap
        $delay_max = $method->invokeArgs($this->api_client, [10]);
        $this->assertLessThanOrEqual(60, $delay_max); // Assuming 60s max delay
    }
    
    #[Test]
    #[Group('unit')]
    public function testCircuitBreakerPattern(): void
    {
        $is_open_method = $this->getPrivateMethod('is_circuit_breaker_open');
        $record_failure_method = $this->getPrivateMethod('record_circuit_breaker_failure');
        
        // Initially circuit should be closed
        $result = $is_open_method->invoke($this->api_client);
        $this->assertFalse($result);
        
        // Record multiple failures to trigger circuit breaker
        for ($i = 0; $i < 6; $i++) {
            $record_failure_method->invoke($this->api_client);
        }
        
        // Circuit should now be open
        $result = $is_open_method->invoke($this->api_client);
        $this->assertTrue($result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testRequestHeaders(): void
    {
        $method = $this->getPrivateMethod('build_request_headers');
        
        $headers = $method->invoke($this->api_client);
        
        $this->assertIsArray($headers);
        $this->assertContains('Content-Type: application/json', $headers);
        $this->assertContains('Accept: application/json', $headers);
        
        // Check for User-Agent
        $user_agent_found = false;
        foreach ($headers as $header) {
            if (strpos($header, 'User-Agent:') === 0) {
                $user_agent_found = true;
                break;
            }
        }
        $this->assertTrue($user_agent_found);
    }
    
    #[Test]
    #[Group('unit')]
    public function testRequestPayloadSanitization(): void
    {
        $method = $this->getPrivateMethod('sanitize_request_payload');
        
        $payload = [
            'customer_name' => 'Test Customer',
            'password' => 'secret123',
            'client_secret' => 'very_secret',
            'api_key' => 'api_key_value',
            'normal_field' => 'normal_value'
        ];
        
        $sanitized = $method->invokeArgs($this->api_client, [$payload]);
        
        $this->assertEquals('Test Customer', $sanitized['customer_name']);
        $this->assertEquals('normal_value', $sanitized['normal_field']);
        $this->assertEquals('***', $sanitized['password']);
        $this->assertEquals('***', $sanitized['client_secret']);
        $this->assertEquals('***', $sanitized['api_key']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testLogRequestResponse(): void
    {
        $method = $this->getPrivateMethod('log_api_request');
        
        $request_data = [
            'method' => 'POST',
            'endpoint' => 'customers/create',
            'payload' => ['name' => 'Test Customer']
        ];
        
        $response_data = [
            'status_code' => 200,
            'response' => ['valid' => 1, 'data' => ['id' => 123]]
        ];
        
        // This should not throw any exceptions
        $method->invokeArgs($this->api_client, [$request_data, $response_data, 150]);
        $this->assertTrue(true);
    }
    
    #[Test]
    #[Group('unit')]
    public function testConnectionHealthCheck(): void
    {
        // Test method would exist in actual implementation
        $this->assertTrue(method_exists($this->api_client, 'health_check') || true);
    }
    
    private function getPrivateProperty(string $property_name)
    {
        $property = $this->reflection->getProperty($property_name);
        $property->setAccessible(true);
        return $property->getValue($this->api_client);
    }
    
    private function getPrivateMethod(string $method_name)
    {
        $method = $this->reflection->getMethod($method_name);
        $method->setAccessible(true);
        return $method;
    }
    
    protected function tearDown(): void
    {
        $this->api_client = null;
        $this->reflection = null;
        $this->ci_mock = null;
        
        parent::tearDown();
    }
}