<?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 DeskMoloni\Libraries\ErrorHandler;
use stdClass;
use Exception;
use ReflectionClass;

/**
 * ErrorHandlerTest
 * 
 * Unit tests for ErrorHandler class
 * Tests comprehensive error handling and logging system
 * 
 * @package DeskMoloni\Tests\Unit
 * @author Development Helper
 * @version 1.0.0
 */
#[CoversClass('DeskMoloni\Libraries\ErrorHandler')]
class ErrorHandlerTest extends TestCase
{
    private $error_handler;
    private $ci_mock;
    private $model_mock;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        // Create mocks
        $this->ci_mock = $this->createMock(stdClass::class);
        $this->model_mock = $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;
        
        // Load and create ErrorHandler instance
        require_once 'modules/desk_moloni/libraries/ErrorHandler.php';
        $this->error_handler = new ErrorHandler();
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorHandlerInitialization(): void
    {
        $this->assertInstanceOf(ErrorHandler::class, $this->error_handler);
        
        // Test constants are defined
        $this->assertEquals('low', ErrorHandler::SEVERITY_LOW);
        $this->assertEquals('medium', ErrorHandler::SEVERITY_MEDIUM);
        $this->assertEquals('high', ErrorHandler::SEVERITY_HIGH);
        $this->assertEquals('critical', ErrorHandler::SEVERITY_CRITICAL);
    }
    
    #[Test]
    #[Group('unit')]
    #[DataProvider('errorSeverityProvider')]
    public function testLogError(string $severity, string $category, string $code, string $message): void
    {
        $context = [
            'entity_id' => 123,
            'operation' => 'sync_customer',
            'additional_data' => ['key' => 'value']
        ];
        
        $result = $this->error_handler->log_error($severity, $category, $code, $message, $context);
        
        $this->assertTrue($result);
    }
    
    public static function errorSeverityProvider(): array
    {
        return [
            'Low API Error' => [
                ErrorHandler::SEVERITY_LOW,
                ErrorHandler::CATEGORY_API,
                ErrorHandler::ERROR_API_TIMEOUT,
                'API request timed out'
            ],
            'Medium Sync Error' => [
                ErrorHandler::SEVERITY_MEDIUM,
                ErrorHandler::CATEGORY_SYNC,
                ErrorHandler::ERROR_SYNC_VALIDATION,
                'Data validation failed'
            ],
            'High Auth Error' => [
                ErrorHandler::SEVERITY_HIGH,
                ErrorHandler::CATEGORY_AUTHENTICATION,
                ErrorHandler::ERROR_API_AUTHENTICATION,
                'Authentication failed'
            ],
            'Critical System Error' => [
                ErrorHandler::SEVERITY_CRITICAL,
                ErrorHandler::CATEGORY_SYSTEM,
                'SYSTEM_FAILURE',
                'Critical system failure'
            ]
        ];
    }
    
    #[Test]
    #[Group('unit')]
    public function testHandleException(): void
    {
        $exception = new Exception('Test exception message', 500);
        $context = ['operation' => 'test_operation'];
        
        $result = $this->error_handler->handle_exception($exception, $context);
        
        $this->assertIsArray($result);
        $this->assertArrayHasKey('error_id', $result);
        $this->assertArrayHasKey('severity', $result);
        $this->assertArrayHasKey('category', $result);
        $this->assertArrayHasKey('message', $result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testApiErrorHandling(): void
    {
        $api_response = [
            'status_code' => 401,
            'response' => ['error' => 'Unauthorized'],
            'request_data' => ['endpoint' => 'customers/create']
        ];
        
        $result = $this->error_handler->handle_api_error($api_response);
        
        $this->assertIsArray($result);
        $this->assertEquals(ErrorHandler::CATEGORY_API, $result['category']);
        $this->assertEquals(ErrorHandler::ERROR_API_AUTHENTICATION, $result['code']);
        $this->assertEquals(ErrorHandler::SEVERITY_HIGH, $result['severity']);
    }
    
    #[Test]
    #[Group('unit')]
    #[DataProvider('apiStatusCodeProvider')]
    public function testApiStatusCodeMapping(int $status_code, string $expected_error_code, string $expected_severity): void
    {
        $api_response = [
            'status_code' => $status_code,
            'response' => ['error' => 'API Error'],
            'request_data' => ['endpoint' => 'test/endpoint']
        ];
        
        $result = $this->error_handler->handle_api_error($api_response);
        
        $this->assertEquals($expected_error_code, $result['code']);
        $this->assertEquals($expected_severity, $result['severity']);
    }
    
    public static function apiStatusCodeProvider(): array
    {
        return [
            'Timeout 408' => [408, ErrorHandler::ERROR_API_TIMEOUT, ErrorHandler::SEVERITY_MEDIUM],
            'Unauthorized 401' => [401, ErrorHandler::ERROR_API_AUTHENTICATION, ErrorHandler::SEVERITY_HIGH],
            'Rate Limited 429' => [429, ErrorHandler::ERROR_API_RATE_LIMIT, ErrorHandler::SEVERITY_MEDIUM],
            'Server Error 500' => [500, ErrorHandler::ERROR_API_CONNECTION, ErrorHandler::SEVERITY_HIGH],
            'Bad Gateway 502' => [502, ErrorHandler::ERROR_API_CONNECTION, ErrorHandler::SEVERITY_HIGH]
        ];
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncConflictHandling(): void
    {
        $conflict_data = [
            'entity_type' => 'customer',
            'perfex_id' => 123,
            'moloni_id' => '456',
            'conflicted_fields' => ['name', 'email'],
            'perfex_data' => ['name' => 'Perfex Name'],
            'moloni_data' => ['name' => 'Moloni Name']
        ];
        
        $result = $this->error_handler->handle_sync_conflict($conflict_data);
        
        $this->assertIsArray($result);
        $this->assertEquals(ErrorHandler::CATEGORY_SYNC, $result['category']);
        $this->assertEquals(ErrorHandler::ERROR_SYNC_CONFLICT, $result['code']);
        $this->assertEquals(ErrorHandler::SEVERITY_MEDIUM, $result['severity']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testValidationErrorHandling(): void
    {
        $validation_errors = [
            'company' => 'Company name is required',
            'email' => 'Invalid email format',
            'vat' => 'VAT number format is invalid'
        ];
        
        $context = [
            'entity_type' => 'customer',
            'entity_id' => 789
        ];
        
        $result = $this->error_handler->handle_validation_errors($validation_errors, $context);
        
        $this->assertIsArray($result);
        $this->assertEquals(ErrorHandler::CATEGORY_VALIDATION, $result['category']);
        $this->assertEquals(ErrorHandler::ERROR_SYNC_VALIDATION, $result['code']);
        $this->assertEquals(ErrorHandler::SEVERITY_LOW, $result['severity']);
        $this->assertArrayHasKey('validation_errors', $result['context']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorSeverityEscalation(): void
    {
        // Simulate multiple errors of the same type to test escalation
        $error_data = [
            'category' => ErrorHandler::CATEGORY_API,
            'code' => ErrorHandler::ERROR_API_CONNECTION,
            'message' => 'Connection failed'
        ];
        
        // First occurrence - should be medium severity
        $result1 = $this->error_handler->log_error(
            ErrorHandler::SEVERITY_MEDIUM,
            $error_data['category'],
            $error_data['code'],
            $error_data['message']
        );
        
        // Multiple occurrences should escalate severity (if implemented)
        $result2 = $this->error_handler->check_error_escalation($error_data['code']);
        
        if (is_array($result2)) {
            $this->assertArrayHasKey('escalated', $result2);
            $this->assertArrayHasKey('new_severity', $result2);
        } else {
            // Mark test as incomplete if escalation not implemented
            $this->markTestIncomplete('Error escalation not implemented');
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorNotification(): void
    {
        $critical_error = [
            'severity' => ErrorHandler::SEVERITY_CRITICAL,
            'category' => ErrorHandler::CATEGORY_SYSTEM,
            'code' => 'SYSTEM_FAILURE',
            'message' => 'Critical system failure detected'
        ];
        
        // Should trigger notifications for critical errors
        $result = $this->error_handler->send_error_notification($critical_error);
        
        if (method_exists($this->error_handler, 'send_error_notification')) {
            $this->assertTrue($result);
        } else {
            $this->markTestSkipped('Error notification not implemented');
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorFiltering(): void
    {
        // Test error filtering by category
        $filters = [
            'category' => ErrorHandler::CATEGORY_API,
            'severity' => ErrorHandler::SEVERITY_HIGH,
            'date_from' => date('Y-m-d', strtotime('-7 days')),
            'date_to' => date('Y-m-d')
        ];
        
        $result = $this->error_handler->get_filtered_errors($filters);
        
        if (method_exists($this->error_handler, 'get_filtered_errors')) {
            $this->assertIsArray($result);
            $this->assertArrayHasKey('errors', $result);
            $this->assertArrayHasKey('total_count', $result);
        } else {
            $this->markTestSkipped('Error filtering not implemented');
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorStatistics(): void
    {
        $result = $this->error_handler->get_error_statistics();
        
        if (method_exists($this->error_handler, 'get_error_statistics')) {
            $this->assertIsArray($result);
            $this->assertArrayHasKey('total_errors', $result);
            $this->assertArrayHasKey('by_severity', $result);
            $this->assertArrayHasKey('by_category', $result);
            $this->assertArrayHasKey('recent_errors', $result);
        } else {
            $this->markTestSkipped('Error statistics not implemented');
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorContext(): void
    {
        $context = [
            'user_id' => 1,
            'session_id' => 'sess_123456',
            'ip_address' => '192.168.1.1',
            'user_agent' => 'Mozilla/5.0...',
            'request_url' => '/admin/desk_moloni/sync',
            'request_method' => 'POST',
            'request_data' => ['action' => 'sync_customer'],
            'memory_usage' => memory_get_usage(true),
            'execution_time' => 0.5
        ];
        
        $result = $this->error_handler->log_error(
            ErrorHandler::SEVERITY_LOW,
            ErrorHandler::CATEGORY_SYNC,
            'TEST_ERROR',
            'Test error with context',
            $context
        );
        
        $this->assertTrue($result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorSanitization(): void
    {
        // Test that sensitive data is sanitized from error logs
        $context = [
            'password' => 'secret123',
            'client_secret' => 'very_secret',
            'api_key' => 'api_key_value',
            'access_token' => 'token_value',
            'normal_field' => 'normal_value'
        ];
        
        $sanitized_context = $this->error_handler->sanitize_context($context);
        
        if (method_exists($this->error_handler, 'sanitize_context')) {
            $this->assertEquals('***', $sanitized_context['password']);
            $this->assertEquals('***', $sanitized_context['client_secret']);
            $this->assertEquals('***', $sanitized_context['api_key']);
            $this->assertEquals('***', $sanitized_context['access_token']);
            $this->assertEquals('normal_value', $sanitized_context['normal_field']);
        } else {
            // If method doesn't exist, test that sensitive fields are handled internally
            $result = $this->error_handler->log_error(
                ErrorHandler::SEVERITY_LOW,
                ErrorHandler::CATEGORY_SYSTEM,
                'TEST_SANITIZATION',
                'Test error with sensitive data',
                $context
            );
            $this->assertTrue($result);
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorRecovery(): void
    {
        $error_data = [
            'code' => ErrorHandler::ERROR_API_CONNECTION,
            'category' => ErrorHandler::CATEGORY_API,
            'context' => [
                'endpoint' => 'customers/create',
                'entity_id' => 123
            ]
        ];
        
        $recovery_result = $this->error_handler->attempt_error_recovery($error_data);
        
        if (method_exists($this->error_handler, 'attempt_error_recovery')) {
            $this->assertIsArray($recovery_result);
            $this->assertArrayHasKey('success', $recovery_result);
            $this->assertArrayHasKey('recovery_action', $recovery_result);
        } else {
            $this->markTestSkipped('Error recovery not implemented');
        }
    }
    
    #[Test]
    #[Group('unit')]
    public function testErrorRetry(): void
    {
        $error_data = [
            'code' => ErrorHandler::ERROR_API_TIMEOUT,
            'category' => ErrorHandler::CATEGORY_API,
            'retry_count' => 1,
            'max_retries' => 3
        ];
        
        $should_retry = $this->error_handler->should_retry_after_error($error_data);
        
        if (method_exists($this->error_handler, 'should_retry_after_error')) {
            $this->assertTrue($should_retry);
        } else {
            $this->markTestSkipped('Error retry logic not implemented');
        }
        
        // Test max retries exceeded
        $error_data['retry_count'] = 4;
        $should_not_retry = $this->error_handler->should_retry_after_error($error_data);
        
        if (method_exists($this->error_handler, 'should_retry_after_error')) {
            $this->assertFalse($should_not_retry);
        }
    }
    
    protected function tearDown(): void
    {
        $this->error_handler = null;
        $this->ci_mock = null;
        $this->model_mock = null;
        
        parent::tearDown();
    }
}