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

<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Performance;

use PHPUnit\Framework\TestCase;
use DeskMoloni\Tests\TestHelpers;

/**
 * Performance Test: Queue Processing and API Rate Limiting
 * 
 * This test MUST FAIL initially as part of TDD methodology.
 * Tests performance requirements and benchmarks.
 * 
 * @group performance
 * @group queue
 */
class QueuePerformanceTest extends TestCase
{
    private array $testConfig;
    private \PDO $pdo;
    private \DeskMoloni\QueueProcessor $queueProcessor;

    protected function setUp(): void
    {
        global $testConfig;
        $this->testConfig = $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]
        );
        
        // This will fail initially until QueueProcessor is implemented
        $this->queueProcessor = new \DeskMoloni\QueueProcessor($testConfig);
        
        // Clean test data
        TestHelpers::clearTestData();
    }

    /**
     * Test queue processing performance requirements
     * Requirement: Process 50 tasks in under 30 seconds
     */
    public function testQueueProcessingPerformance(): void
    {
        $taskCount = 50;
        $maxExecutionTime = 30; // seconds
        
        // Create test tasks
        $tasks = $this->createTestTasks($taskCount);
        $this->insertTasksIntoQueue($tasks);
        
        // Measure processing time
        $startTime = microtime(true);
        
        $result = $this->queueProcessor->processBatch($taskCount);
        
        $endTime = microtime(true);
        $executionTime = $endTime - $startTime;
        
        // Performance assertions
        $this->assertLessThan($maxExecutionTime, $executionTime, "Queue should process {$taskCount} tasks in under {$maxExecutionTime} seconds");
        
        $this->assertIsArray($result);
        $this->assertArrayHasKey('processed_count', $result);
        $this->assertArrayHasKey('successful_count', $result);
        $this->assertArrayHasKey('failed_count', $result);
        $this->assertArrayHasKey('average_task_time', $result);
        
        $this->assertEquals($taskCount, $result['processed_count']);
        $this->assertGreaterThan(0, $result['successful_count']);
        $this->assertLessThan(1000, $result['average_task_time'], 'Average task time should be under 1 second');
        
        // Verify tasks were processed
        $stmt = $this->pdo->query("SELECT COUNT(*) as count FROM tbl_desk_moloni_sync_queue WHERE status = 'completed'");
        $completedCount = $stmt->fetch();
        
        $this->assertGreaterThan(0, $completedCount['count'], 'Some tasks should be completed');
    }

    /**
     * Test concurrent queue processing
     */
    public function testConcurrentQueueProcessing(): void
    {
        $taskCount = 100;
        $workerCount = 4;
        
        // Create test tasks
        $tasks = $this->createTestTasks($taskCount);
        $this->insertTasksIntoQueue($tasks);
        
        $startTime = microtime(true);
        
        // Simulate concurrent workers
        $workers = [];
        for ($i = 0; $i < $workerCount; $i++) {
            $workers[$i] = $this->queueProcessor->createWorker("worker_{$i}");
        }
        
        // Process tasks concurrently (simulated)
        $results = [];
        foreach ($workers as $workerId => $worker) {
            $results[$workerId] = $worker->processBatch($taskCount / $workerCount);
        }
        
        $endTime = microtime(true);
        $executionTime = $endTime - $startTime;
        
        // Should be faster than sequential processing
        $this->assertLessThan(20, $executionTime, 'Concurrent processing should be faster');
        
        // Verify no task conflicts
        $stmt = $this->pdo->query("SELECT COUNT(*) as count FROM tbl_desk_moloni_sync_queue WHERE status = 'processing'");
        $processingCount = $stmt->fetch();
        
        $this->assertEquals(0, $processingCount['count'], 'No tasks should be stuck in processing state');
        
        // Verify all tasks processed exactly once
        $stmt = $this->pdo->query("SELECT COUNT(*) as count FROM tbl_desk_moloni_sync_queue WHERE status IN ('completed', 'failed')");
        $processedCount = $stmt->fetch();
        
        $this->assertEquals($taskCount, $processedCount['count'], 'All tasks should be processed exactly once');
    }

    /**
     * Test API rate limiting performance
     * Requirement: Respect Moloni rate limits without excessive delays
     */
    public function testApiRateLimitingPerformance(): void
    {
        $rateLimiter = new \DeskMoloni\ApiRateLimiter($this->testConfig);
        
        $requestCount = 50;
        $maxTotalTime = 60; // seconds - should not take too long due to rate limiting
        
        $startTime = microtime(true);
        $successfulRequests = 0;
        $rateLimitedRequests = 0;
        
        for ($i = 0; $i < $requestCount; $i++) {
            $requestStart = microtime(true);
            
            $allowed = $rateLimiter->allowRequest('test_endpoint');
            
            if ($allowed) {
                $successfulRequests++;
                
                // Simulate API call
                usleep(100000); // 100ms
            } else {
                $rateLimitedRequests++;
                
                // Test wait time calculation
                $waitTime = $rateLimiter->getWaitTime('test_endpoint');
                $this->assertIsFloat($waitTime);
                $this->assertGreaterThanOrEqual(0, $waitTime);
                $this->assertLessThan(60, $waitTime, 'Wait time should not exceed 60 seconds');
            }
            
            $requestTime = microtime(true) - $requestStart;
            $this->assertLessThan(5, $requestTime, 'Individual request processing should be under 5 seconds');
        }
        
        $endTime = microtime(true);
        $totalTime = $endTime - $startTime;
        
        $this->assertLessThan($maxTotalTime, $totalTime, "Rate limited requests should complete in under {$maxTotalTime} seconds");
        $this->assertGreaterThan(0, $successfulRequests, 'Some requests should be allowed');
        
        // Verify rate limiting data
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_rate_limits WHERE api_endpoint = ?");
        $stmt->execute(['test_endpoint']);
        $rateLimitData = $stmt->fetch();
        
        $this->assertNotFalse($rateLimitData, 'Rate limit data should be recorded');
        $this->assertGreaterThan(0, $rateLimitData['calls_made']);
        $this->assertLessThanOrEqual($rateLimitData['limit_per_window'], $rateLimitData['calls_made']);
    }

    /**
     * Test memory usage during bulk operations
     */
    public function testMemoryUsageDuringBulkOperations(): void
    {
        $initialMemory = memory_get_usage(true);
        $maxAllowedMemory = 128 * 1024 * 1024; // 128MB
        
        // Create large batch of tasks
        $largeBatchSize = 1000;
        $tasks = $this->createTestTasks($largeBatchSize);
        
        $memoryAfterCreation = memory_get_usage(true);
        $creationMemoryIncrease = $memoryAfterCreation - $initialMemory;
        
        $this->assertLessThan($maxAllowedMemory / 4, $creationMemoryIncrease, 'Task creation should not use excessive memory');
        
        // Insert tasks
        $this->insertTasksIntoQueue($tasks);
        
        $memoryAfterInsert = memory_get_usage(true);
        $insertMemoryIncrease = $memoryAfterInsert - $memoryAfterCreation;
        
        $this->assertLessThan($maxAllowedMemory / 4, $insertMemoryIncrease, 'Task insertion should not use excessive memory');
        
        // Process tasks in chunks to test memory management
        $chunkSize = 100;
        $chunksProcessed = 0;
        
        while ($chunksProcessed * $chunkSize < $largeBatchSize) {
            $chunkStartMemory = memory_get_usage(true);
            
            $result = $this->queueProcessor->processBatch($chunkSize);
            
            $chunkEndMemory = memory_get_usage(true);
            $chunkMemoryIncrease = $chunkEndMemory - $chunkStartMemory;
            
            $this->assertLessThan($maxAllowedMemory / 8, $chunkMemoryIncrease, "Chunk processing should not leak memory (chunk {$chunksProcessed})");
            
            $chunksProcessed++;
            
            // Force garbage collection
            gc_collect_cycles();
        }
        
        $finalMemory = memory_get_usage(true);
        $totalMemoryIncrease = $finalMemory - $initialMemory;
        
        $this->assertLessThan($maxAllowedMemory, $totalMemoryIncrease, 'Total memory usage should stay within limits');
    }

    /**
     * Test database query performance
     */
    public function testDatabaseQueryPerformance(): void
    {
        // Create test data for performance testing
        $testDataCount = 10000;
        $this->createLargeTestDataset($testDataCount);
        
        // Test queue selection performance
        $startTime = microtime(true);
        
        $stmt = $this->pdo->query("
            SELECT * FROM tbl_desk_moloni_sync_queue 
            WHERE status = 'pending' 
            ORDER BY priority ASC, scheduled_at ASC 
            LIMIT 100
        ");
        $tasks = $stmt->fetchAll();
        
        $queryTime = microtime(true) - $startTime;
        
        $this->assertLessThan(0.5, $queryTime, 'Queue selection query should complete in under 500ms');
        $this->assertLessThanOrEqual(100, count($tasks));
        
        // Test mapping lookup performance
        $startTime = microtime(true);
        
        $stmt = $this->pdo->query("
            SELECT m.*, l.execution_time_ms 
            FROM tbl_desk_moloni_mapping m
            LEFT JOIN tbl_desk_moloni_sync_log l ON l.perfex_id = m.perfex_id AND l.entity_type = m.entity_type
            WHERE m.entity_type = 'client'
            ORDER BY m.last_sync_at DESC
            LIMIT 100
        ");
        $mappings = $stmt->fetchAll();
        
        $queryTime = microtime(true) - $startTime;
        
        $this->assertLessThan(0.3, $queryTime, 'Mapping lookup query should complete in under 300ms');
        
        // Test log aggregation performance
        $startTime = microtime(true);
        
        $stmt = $this->pdo->query("
            SELECT 
                entity_type,
                status,
                COUNT(*) as count,
                AVG(execution_time_ms) as avg_time,
                MAX(created_at) as latest
            FROM tbl_desk_moloni_sync_log
            WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
            GROUP BY entity_type, status
        ");
        $stats = $stmt->fetchAll();
        
        $queryTime = microtime(true) - $startTime;
        
        $this->assertLessThan(1.0, $queryTime, 'Log aggregation query should complete in under 1 second');
        $this->assertIsArray($stats);
    }

    /**
     * Test Redis cache performance
     */
    public function testRedisCachePerformance(): void
    {
        if (!isset($GLOBALS['test_redis'])) {
            $this->markTestSkipped('Redis not available for testing');
        }
        
        $redis = $GLOBALS['test_redis'];
        $cacheManager = new \DeskMoloni\CacheManager($redis);
        
        $testDataSize = 1000;
        $testData = [];
        
        // Generate test data
        for ($i = 0; $i < $testDataSize; $i++) {
            $testData["test_key_{$i}"] = [
                'id' => $i,
                'name' => "Test Item {$i}",
                'data' => str_repeat('x', 100) // 100 byte payload
            ];
        }
        
        // Test write performance
        $startTime = microtime(true);
        
        foreach ($testData as $key => $data) {
            $cacheManager->set($key, $data, 300); // 5 minute TTL
        }
        
        $writeTime = microtime(true) - $startTime;
        $writeOpsPerSecond = $testDataSize / $writeTime;
        
        $this->assertGreaterThan(500, $writeOpsPerSecond, 'Cache should handle at least 500 writes per second');
        
        // Test read performance
        $startTime = microtime(true);
        
        foreach (array_keys($testData) as $key) {
            $cached = $cacheManager->get($key);
            $this->assertNotNull($cached, "Cached data should exist for key {$key}");
        }
        
        $readTime = microtime(true) - $startTime;
        $readOpsPerSecond = $testDataSize / $readTime;
        
        $this->assertGreaterThan(1000, $readOpsPerSecond, 'Cache should handle at least 1000 reads per second');
        
        // Test batch operations
        $batchKeys = array_slice(array_keys($testData), 0, 100);
        
        $startTime = microtime(true);
        $batchResult = $cacheManager->multiGet($batchKeys);
        $batchTime = microtime(true) - $startTime;
        
        $this->assertLessThan(0.1, $batchTime, 'Batch get should complete in under 100ms');
        $this->assertCount(100, $batchResult);
    }

    /**
     * Test sync operation performance benchmarks
     */
    public function testSyncOperationPerformanceBenchmarks(): void
    {
        $syncService = new \DeskMoloni\ClientSyncService();
        
        // Benchmark single client sync
        $testClient = TestHelpers::createTestClient([
            'userid' => 99999,
            'company' => 'Performance Test Company',
            'vat' => '999999999'
        ]);
        
        $syncTimes = [];
        $iterations = 10;
        
        for ($i = 0; $i < $iterations; $i++) {
            $startTime = microtime(true);
            
            $result = $syncService->syncPerfexToMoloni($testClient);
            
            $syncTime = microtime(true) - $startTime;
            $syncTimes[] = $syncTime;
            
            // Clean up for next iteration
            if ($result['success'] ?? false) {
                $this->pdo->exec("DELETE FROM tbl_desk_moloni_mapping WHERE perfex_id = 99999");
                $this->pdo->exec("DELETE FROM tbl_desk_moloni_sync_log WHERE perfex_id = 99999");
            }
        }
        
        $avgSyncTime = array_sum($syncTimes) / count($syncTimes);
        $maxSyncTime = max($syncTimes);
        $minSyncTime = min($syncTimes);
        
        $this->assertLessThan(5.0, $avgSyncTime, 'Average sync time should be under 5 seconds');
        $this->assertLessThan(10.0, $maxSyncTime, 'Maximum sync time should be under 10 seconds');
        $this->assertGreaterThan(0.1, $minSyncTime, 'Minimum sync time should be realistic (over 100ms)');
        
        // Calculate performance metrics
        $standardDeviation = sqrt(array_sum(array_map(function($x) use ($avgSyncTime) {
            return pow($x - $avgSyncTime, 2);
        }, $syncTimes)) / count($syncTimes));
        
        $this->assertLessThan($avgSyncTime * 0.5, $standardDeviation, 'Sync times should be consistent (low standard deviation)');
    }

    private function createTestTasks(int $count): array
    {
        $tasks = [];
        
        for ($i = 1; $i <= $count; $i++) {
            $tasks[] = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => 1000 + $i,
                'priority' => rand(1, 9),
                'payload' => json_encode([
                    'client_data' => [
                        'id' => 1000 + $i,
                        'name' => "Test Client {$i}",
                        'email' => "test{$i}@example.com"
                    ]
                ]),
                'scheduled_at' => date('Y-m-d H:i:s')
            ];
        }
        
        return $tasks;
    }

    private function insertTasksIntoQueue(array $tasks): void
    {
        $stmt = $this->pdo->prepare("
            INSERT INTO tbl_desk_moloni_sync_queue 
            (task_type, entity_type, entity_id, priority, payload, scheduled_at) 
            VALUES (?, ?, ?, ?, ?, ?)
        ");
        
        foreach ($tasks as $task) {
            $stmt->execute([
                $task['task_type'],
                $task['entity_type'],
                $task['entity_id'],
                $task['priority'],
                $task['payload'],
                $task['scheduled_at']
            ]);
        }
    }

    private function createLargeTestDataset(int $count): void
    {
        // Create test mappings
        $stmt = $this->pdo->prepare("
            INSERT INTO tbl_desk_moloni_mapping 
            (entity_type, perfex_id, moloni_id, sync_direction, last_sync_at) 
            VALUES (?, ?, ?, ?, ?)
        ");
        
        for ($i = 1; $i <= $count / 4; $i++) {
            $stmt->execute([
                'client',
                10000 + $i,
                20000 + $i,
                'bidirectional',
                date('Y-m-d H:i:s', strtotime("-{$i} minutes"))
            ]);
        }
        
        // Create test logs
        $stmt = $this->pdo->prepare("
            INSERT INTO tbl_desk_moloni_sync_log 
            (operation_type, entity_type, perfex_id, moloni_id, direction, status, execution_time_ms, created_at) 
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ");
        
        for ($i = 1; $i <= $count; $i++) {
            $stmt->execute([
                'create',
                'client',
                10000 + ($i % ($count / 4)),
                20000 + ($i % ($count / 4)),
                'perfex_to_moloni',
                rand(0, 10) < 9 ? 'success' : 'error', // 90% success rate
                rand(100, 5000), // Random execution time
                date('Y-m-d H:i:s', strtotime("-{$i} seconds"))
            ]);
        }
    }

    protected function tearDown(): void
    {
        // Clean up performance test data
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_sync_queue WHERE entity_id >= 1000");
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_mapping WHERE perfex_id >= 10000");
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_sync_log WHERE perfex_id >= 10000");
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_rate_limits WHERE api_endpoint = 'test_endpoint'");
        
        if (isset($GLOBALS['test_redis'])) {
            $GLOBALS['test_redis']->flushdb();
        }
    }
}