<?php

declare(strict_types=1);

namespace Tests\Framework\Queue\Performance;

use App\Framework\Database\DatabaseManager;
use App\Framework\Queue\Distribution\JobDistributionService;
use App\Framework\Queue\Workers\WorkerRegistry;
use PHPUnit\Framework\TestCase;

final class SystemResourcesTest extends TestCase
{
    private DatabaseManager $database;

    private WorkerRegistry $workerRegistry;

    private JobDistributionService $distributionService;

    protected function setUp(): void
    {
        $this->database = $this->createTestDatabase();
        $this->workerRegistry = new WorkerRegistry($this->database);
        $this->distributionService = new JobDistributionService(
            $this->database,
            $this->workerRegistry
        );

        $this->cleanupTestData();
        PerformanceTestHelper::warmupDatabase($this->database->getConnection());
    }

    protected function tearDown(): void
    {
        $this->cleanupTestData();
    }

    public function testMemoryUsageUnderLoad(): void
    {
        $workers = $this->createWorkers(10, 25);
        $this->registerWorkers($workers);

        $initialMemory = PerformanceTestHelper::getMemoryUsage();
        $memorySnapshots = [$initialMemory];

        echo "\nMemory Usage Under Load Test:\n";
        echo "Initial memory: {$initialMemory['current_mb']}MB (Peak: {$initialMemory['peak_mb']}MB)\n";

        // Phase 1: Moderate load
        $this->simulateJobLoad(500, 'moderate', $memorySnapshots);

        // Phase 2: High load
        $this->simulateJobLoad(1000, 'high', $memorySnapshots);

        // Phase 3: Sustained load
        $this->simulateSustainedLoad(30, 50, $memorySnapshots); // 30 seconds, 50 jobs/sec

        $finalMemory = PerformanceTestHelper::getMemoryUsage();
        $memorySnapshots[] = $finalMemory;

        echo "Final memory: {$finalMemory['current_mb']}MB (Peak: {$finalMemory['peak_mb']}MB)\n";

        // Analyze memory usage patterns
        $this->analyzeMemoryUsage($memorySnapshots);

        // Memory usage should stay within reasonable bounds
        $this->assertLessThan(
            100.0,
            $finalMemory['current_mb'],
            'Current memory usage exceeds 100MB'
        );

        $this->assertLessThan(
            200.0,
            $finalMemory['peak_mb'],
            'Peak memory usage exceeds 200MB'
        );

        // Check for potential memory leaks
        $memoryIncrease = $finalMemory['current_mb'] - $initialMemory['current_mb'];
        $this->assertLessThan(
            50.0,
            $memoryIncrease,
            'Memory increase suggests potential memory leak'
        );
    }

    public function testMemoryEfficiencyWithBulkOperations(): void
    {
        $workers = $this->createWorkers(5, 30);
        $this->registerWorkers($workers);

        echo "\nMemory Efficiency with Bulk Operations:\n";

        $testCases = [
            ['batch_size' => 10, 'batches' => 10],
            ['batch_size' => 50, 'batches' => 10],
            ['batch_size' => 100, 'batches' => 10],
            ['batch_size' => 500, 'batches' => 5],
            ['batch_size' => 1000, 'batches' => 3],
        ];

        foreach ($testCases as $case) {
            $batchSize = $case['batch_size'];
            $batchCount = $case['batches'];

            $beforeMemory = PerformanceTestHelper::getMemoryUsage();

            // Process batches
            $totalProcessingTime = 0;
            for ($batch = 0; $batch < $batchCount; $batch++) {
                $jobs = PerformanceTestHelper::createBulkJobs($batchSize);

                $batchTime = PerformanceTestHelper::measureTime(function () use ($jobs) {
                    foreach ($jobs as $job) {
                        $this->distributionService->distributeJob($job);
                    }
                });

                $totalProcessingTime += $batchTime;

                // Clean up completed jobs to simulate real processing
                if ($batch % 2 === 0) {
                    $this->cleanupCompletedJobs();
                }
            }

            $afterMemory = PerformanceTestHelper::getMemoryUsage();
            $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb'];
            $totalJobs = $batchSize * $batchCount;
            $avgTimePerJob = $totalProcessingTime / $totalJobs;

            echo sprintf(
                "Batch size: %4d, Total jobs: %4d, Memory increase: %6.2fMB, Avg time: %6.3fms/job\n",
                $batchSize,
                $totalJobs,
                $memoryIncrease,
                $avgTimePerJob
            );

            // Memory increase should not grow linearly with batch size
            $memoryPerJob = $memoryIncrease / $totalJobs;
            $this->assertLessThan(
                0.1,
                $memoryPerJob,
                "Memory usage per job too high for batch size {$batchSize}"
            );

            $this->cleanupTestData();
        }
    }

    public function testGarbageCollectionImpact(): void
    {
        $workers = $this->createWorkers(8, 20);
        $this->registerWorkers($workers);

        echo "\nGarbage Collection Impact Test:\n";

        $gcStats = [];
        $operationTimes = [];

        // Force garbage collection and measure baseline
        gc_collect_cycles();
        $initialGcStats = gc_status();

        // Perform operations that generate objects
        $iterations = 1000;
        for ($i = 0; $i < $iterations; $i++) {
            $job = PerformanceTestHelper::createTestJob("gc_test_job_{$i}");

            $operationTime = PerformanceTestHelper::measureTime(function () use ($job) {
                return $this->distributionService->distributeJob($job);
            });

            $operationTimes[] = $operationTime;

            // Collect GC stats every 100 operations
            if ($i % 100 === 0) {
                $gcStats[] = [
                    'operation' => $i,
                    'memory' => PerformanceTestHelper::getMemoryUsage(),
                    'gc_stats' => gc_status(),
                ];
            }
        }

        // Force final garbage collection
        $gcCycles = gc_collect_cycles();
        $finalGcStats = gc_status();

        echo "GC cycles collected: {$gcCycles}\n";
        echo "Initial GC runs: {$initialGcStats['runs']}\n";
        echo "Final GC runs: {$finalGcStats['runs']}\n";
        echo "GC runs during test: " . ($finalGcStats['runs'] - $initialGcStats['runs']) . "\n";

        // Analyze operation times for GC impact
        $stats = PerformanceTestHelper::calculateStatistics($operationTimes);
        echo "Operation performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";

        // GC should not cause significant performance degradation
        $this->assertLessThan(
            20.0,
            $stats['avg'],
            'Average operation time too high - possible GC impact'
        );

        // P99 should not be extremely high (indicating GC pauses)
        $this->assertLessThan(
            100.0,
            $stats['p99'],
            'P99 operation time too high - possible GC pause impact'
        );

        // Analyze memory usage patterns during GC
        foreach ($gcStats as $snapshot) {
            $memory = $snapshot['memory'];
            echo sprintf(
                "Operation %4d: Memory %6.2fMB, GC runs: %d\n",
                $snapshot['operation'],
                $memory['current_mb'],
                $snapshot['gc_stats']['runs']
            );
        }
    }

    public function testConcurrentOperationResourceUsage(): void
    {
        $workers = $this->createWorkers(15, 20);
        $this->registerWorkers($workers);

        echo "\nConcurrent Operation Resource Usage Test:\n";

        $concurrencyLevels = [1, 5, 10, 20, 50];
        $operationsPerLevel = 200;

        foreach ($concurrencyLevels as $concurrency) {
            $beforeMemory = PerformanceTestHelper::getMemoryUsage();

            // Simulate concurrent operations
            $results = $this->simulateConcurrentOperations($concurrency, $operationsPerLevel);

            $afterMemory = PerformanceTestHelper::getMemoryUsage();
            $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb'];

            $avgTime = array_sum($results['times']) / count($results['times']);
            $throughput = $results['total_operations'] / $results['duration'];

            echo sprintf(
                "Concurrency: %2d, Throughput: %6.1f ops/sec, Avg time: %6.3fms, Memory: +%6.2fMB\n",
                $concurrency,
                $throughput,
                $avgTime,
                $memoryIncrease
            );

            // Memory usage should not grow excessively with concurrency
            $this->assertLessThan(
                10.0,
                $memoryIncrease,
                "Memory increase too high for concurrency level {$concurrency}"
            );

            // Throughput should generally increase with concurrency (up to a point)
            if ($concurrency <= 10) {
                $this->assertGreaterThan(
                    $concurrency * 5, // At least 5 ops/sec per concurrent operation
                    $throughput,
                    "Throughput too low for concurrency level {$concurrency}"
                );
            }

            $this->cleanupTestData();
        }
    }

    public function testLongRunningProcessMemoryStability(): void
    {
        $workers = $this->createWorkers(6, 25);
        $this->registerWorkers($workers);

        echo "\nLong Running Process Memory Stability Test:\n";

        $duration = 120; // 2 minutes
        $operationsPerSecond = 20;
        $memorySnapshots = [];

        $startTime = microtime(true);
        $endTime = $startTime + $duration;
        $operationCount = 0;

        while (microtime(true) < $endTime) {
            $cycleStart = microtime(true);

            // Perform operations for one second
            for ($i = 0; $i < $operationsPerSecond; $i++) {
                $job = PerformanceTestHelper::createTestJob("stability_job_{$operationCount}");
                $this->distributionService->distributeJob($job);
                $operationCount++;
            }

            // Take memory snapshot every 10 seconds
            if ($operationCount % ($operationsPerSecond * 10) === 0) {
                $memory = PerformanceTestHelper::getMemoryUsage();
                $elapsed = microtime(true) - $startTime;
                $memorySnapshots[] = [
                    'time' => $elapsed,
                    'operations' => $operationCount,
                    'memory' => $memory,
                ];

                echo sprintf(
                    "Time: %3ds, Operations: %5d, Memory: %6.2fMB (Peak: %6.2fMB)\n",
                    $elapsed,
                    $operationCount,
                    $memory['current_mb'],
                    $memory['peak_mb']
                );
            }

            // Clean up periodically to simulate real-world processing
            if ($operationCount % ($operationsPerSecond * 5) === 0) {
                $this->cleanupCompletedJobs();
            }

            // Maintain target operations per second
            $cycleTime = microtime(true) - $cycleStart;
            $sleepTime = 1.0 - $cycleTime;
            if ($sleepTime > 0) {
                usleep($sleepTime * 1000000);
            }
        }

        $actualDuration = microtime(true) - $startTime;
        $actualThroughput = $operationCount / $actualDuration;

        echo "Total operations: {$operationCount}\n";
        echo "Actual duration: {$actualDuration} seconds\n";
        echo "Actual throughput: {$actualThroughput} ops/sec\n";

        // Analyze memory stability
        $this->analyzeMemoryStability($memorySnapshots);

        // Memory should remain stable over time
        $firstSnapshot = $memorySnapshots[0];
        $lastSnapshot = end($memorySnapshots);
        $memoryDrift = $lastSnapshot['memory']['current_mb'] - $firstSnapshot['memory']['current_mb'];

        echo "Memory drift: {$memoryDrift}MB\n";

        $this->assertLessThan(
            20.0,
            abs($memoryDrift),
            'Memory drift too high - indicates memory leak or accumulation'
        );

        // Throughput should remain stable
        $this->assertGreaterThan(
            $operationsPerSecond * 0.8,
            $actualThroughput,
            'Throughput degraded too much during long run'
        );
    }

    public function testResourceCleanupEfficiency(): void
    {
        $workers = $this->createWorkers(5, 20);
        $this->registerWorkers($workers);

        echo "\nResource Cleanup Efficiency Test:\n";

        // Create many jobs
        $jobCount = 2000;
        $jobs = PerformanceTestHelper::createBulkJobs($jobCount);

        $beforeMemory = PerformanceTestHelper::getMemoryUsage();
        echo "Memory before job creation: {$beforeMemory['current_mb']}MB\n";

        // Distribute all jobs
        foreach ($jobs as $job) {
            $this->distributionService->distributeJob($job);
        }

        $afterDistribution = PerformanceTestHelper::getMemoryUsage();
        echo "Memory after distribution: {$afterDistribution['current_mb']}MB\n";

        // Measure cleanup time
        $cleanupTime = PerformanceTestHelper::measureTime(function () {
            $this->cleanupCompletedJobs();
        });

        $afterCleanup = PerformanceTestHelper::getMemoryUsage();
        echo "Memory after cleanup: {$afterCleanup['current_mb']}MB\n";
        echo "Cleanup time: {$cleanupTime}ms\n";

        $memoryRecovered = $afterDistribution['current_mb'] - $afterCleanup['current_mb'];
        echo "Memory recovered: {$memoryRecovered}MB\n";

        // Cleanup should be efficient
        $this->assertLessThan(
            200.0,
            $cleanupTime,
            'Cleanup time too slow for 2000 jobs'
        );

        // Should recover most of the memory
        $distributionMemoryUsage = $afterDistribution['current_mb'] - $beforeMemory['current_mb'];
        $recoveryRatio = $memoryRecovered / max(1, $distributionMemoryUsage);

        echo "Memory recovery ratio: " . round($recoveryRatio * 100, 1) . "%\n";

        $this->assertGreaterThan(
            0.5,
            $recoveryRatio,
            'Should recover at least 50% of memory used during distribution'
        );
    }

    private function simulateJobLoad(int $jobCount, string $phase, array &$memorySnapshots): void
    {
        echo "Phase: {$phase} ({$jobCount} jobs)\n";

        $beforeMemory = PerformanceTestHelper::getMemoryUsage();
        $memorySnapshots[] = $beforeMemory;

        $jobs = PerformanceTestHelper::createBulkJobs($jobCount);
        $distributionTimes = [];

        foreach ($jobs as $job) {
            $time = PerformanceTestHelper::measureTime(function () use ($job) {
                return $this->distributionService->distributeJob($job);
            });
            $distributionTimes[] = $time;
        }

        $afterMemory = PerformanceTestHelper::getMemoryUsage();
        $memorySnapshots[] = $afterMemory;

        $stats = PerformanceTestHelper::calculateStatistics($distributionTimes);
        $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb'];

        echo "  Memory increase: {$memoryIncrease}MB\n";
        echo "  Distribution performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
    }

    private function simulateSustainedLoad(int $duration, int $jobsPerSecond, array &$memorySnapshots): void
    {
        echo "Sustained load: {$jobsPerSecond} jobs/sec for {$duration} seconds\n";

        $startTime = microtime(true);
        $endTime = $startTime + $duration;
        $jobCount = 0;

        $snapshotInterval = 5; // Take snapshot every 5 seconds
        $nextSnapshotTime = $startTime + $snapshotInterval;

        while (microtime(true) < $endTime) {
            $job = PerformanceTestHelper::createTestJob("sustained_job_{$jobCount}");
            $this->distributionService->distributeJob($job);
            $jobCount++;

            // Take memory snapshot
            if (microtime(true) >= $nextSnapshotTime) {
                $memory = PerformanceTestHelper::getMemoryUsage();
                $memorySnapshots[] = $memory;
                $elapsed = microtime(true) - $startTime;
                echo "  {$elapsed}s: {$memory['current_mb']}MB\n";
                $nextSnapshotTime += $snapshotInterval;
            }

            // Maintain target rate
            usleep(1000000 / $jobsPerSecond); // Convert to microseconds
        }

        echo "  Total jobs: {$jobCount}\n";
    }

    private function simulateConcurrentOperations(int $concurrency, int $totalOperations): array
    {
        $times = [];
        $startTime = microtime(true);

        $operationsPerWorker = intval($totalOperations / $concurrency);
        $actualOperations = 0;

        // Simulate concurrent operations (simplified for single-threaded PHP)
        for ($worker = 0; $worker < $concurrency; $worker++) {
            for ($op = 0; $op < $operationsPerWorker; $op++) {
                $job = PerformanceTestHelper::createTestJob("concurrent_job_{$worker}_{$op}");

                $time = PerformanceTestHelper::measureTime(function () use ($job) {
                    return $this->distributionService->distributeJob($job);
                });

                $times[] = $time;
                $actualOperations++;
            }
        }

        $endTime = microtime(true);

        return [
            'times' => $times,
            'total_operations' => $actualOperations,
            'duration' => $endTime - $startTime,
        ];
    }

    private function analyzeMemoryUsage(array $memorySnapshots): void
    {
        echo "\nMemory Usage Analysis:\n";

        $memoryValues = array_column($memorySnapshots, 'current_mb');
        $peakValues = array_column($memorySnapshots, 'peak_mb');

        $memoryStats = PerformanceTestHelper::calculateStatistics($memoryValues);
        $peakStats = PerformanceTestHelper::calculateStatistics($peakValues);

        echo "Current Memory: " . PerformanceTestHelper::formatStatistics($memoryStats, 'MB') . "\n";
        echo "Peak Memory: " . PerformanceTestHelper::formatStatistics($peakStats, 'MB') . "\n";

        // Check for memory growth pattern
        $memoryTrend = end($memoryValues) - $memoryValues[0];
        echo "Memory trend: " . ($memoryTrend >= 0 ? '+' : '') . "{$memoryTrend}MB\n";
    }

    private function analyzeMemoryStability(array $memorySnapshots): void
    {
        echo "\nMemory Stability Analysis:\n";

        $memoryValues = array_column(array_column($memorySnapshots, 'memory'), 'current_mb');
        $timeValues = array_column($memorySnapshots, 'time');

        // Calculate memory growth rate
        if (count($memoryValues) >= 2) {
            $firstMemory = $memoryValues[0];
            $lastMemory = end($memoryValues);
            $timeSpan = end($timeValues) - $timeValues[0];

            $growthRate = ($lastMemory - $firstMemory) / $timeSpan; // MB per second
            echo "Memory growth rate: " . round($growthRate * 60, 3) . " MB/minute\n";

            $this->assertLessThan(
                0.1, // Less than 0.1 MB/sec = 6 MB/minute
                abs($growthRate),
                'Memory growth rate too high - indicates potential leak'
            );
        }
    }

    private function createWorkers(int $count, int $capacity): array
    {
        $workers = [];
        for ($i = 1; $i <= $count; $i++) {
            $workers[] = PerformanceTestHelper::createTestWorker(
                "resource_worker_{$i}",
                $capacity
            );
        }

        return $workers;
    }

    private function registerWorkers(array $workers): void
    {
        foreach ($workers as $worker) {
            $this->workerRegistry->registerWorker($worker);
        }
    }

    private function cleanupCompletedJobs(): void
    {
        $pdo = $this->database->getConnection();
        $pdo->exec('DELETE FROM jobs WHERE status IN ("COMPLETED", "FAILED")');
    }

    private function createTestDatabase(): DatabaseManager
    {
        $pdo = new \PDO('sqlite::memory:');
        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

        $pdo->exec('
            CREATE TABLE workers (
                id TEXT PRIMARY KEY,
                queue_names TEXT NOT NULL,
                capacity INTEGER NOT NULL,
                status TEXT NOT NULL,
                last_heartbeat TEXT NOT NULL,
                metadata TEXT
            )
        ');

        $pdo->exec('
            CREATE TABLE jobs (
                id TEXT PRIMARY KEY,
                type TEXT NOT NULL,
                payload TEXT NOT NULL,
                queue_name TEXT NOT NULL,
                priority INTEGER NOT NULL,
                status TEXT NOT NULL,
                worker_id TEXT,
                created_at TEXT NOT NULL,
                started_at TEXT,
                completed_at TEXT,
                attempts INTEGER DEFAULT 0,
                error_message TEXT
            )
        ');

        return new DatabaseManager($pdo);
    }

    private function cleanupTestData(): void
    {
        $pdo = $this->database->getConnection();
        $pdo->exec('DELETE FROM workers');
        $pdo->exec('DELETE FROM jobs');
    }
}
