<?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\Jobs\JobStatus;
use App\Framework\Queue\Workers\WorkerRegistry;
use App\Framework\Queue\Workers\WorkerStatus;
use PHPUnit\Framework\TestCase;

final class DatabasePerformanceTest 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 testWorkerQueryPerformance(): void
    {
        // Create workers with different statuses and capacities
        $workers = array_merge(
            $this->createWorkers(50, 20, WorkerStatus::AVAILABLE),
            $this->createWorkers(20, 15, WorkerStatus::BUSY),
            $this->createWorkers(10, 25, WorkerStatus::FAILED)
        );

        $this->registerWorkers($workers);

        echo "\nWorker Query Performance Test:\n";
        echo "Total workers: " . count($workers) . "\n";

        // Test worker lookup by ID
        $workerLookupTimes = [];
        for ($i = 0; $i < 100; $i++) {
            $randomWorker = $workers[array_rand($workers)];
            $workerId = $randomWorker->id->toString();

            $time = PerformanceTestHelper::measureTime(function () use ($workerId) {
                return $this->workerRegistry->getWorker($workerId);
            });

            $workerLookupTimes[] = $time;
        }

        $lookupStats = PerformanceTestHelper::calculateStatistics($workerLookupTimes);
        echo "Worker lookup by ID: " . PerformanceTestHelper::formatStatistics($lookupStats) . "\n";

        // Test getting available workers
        $availableWorkerTimes = [];
        for ($i = 0; $i < 100; $i++) {
            $time = PerformanceTestHelper::measureTime(function () {
                return $this->workerRegistry->getAvailableWorkers();
            });
            $availableWorkerTimes[] = $time;
        }

        $availableStats = PerformanceTestHelper::calculateStatistics($availableWorkerTimes);
        echo "Get available workers: " . PerformanceTestHelper::formatStatistics($availableStats) . "\n";

        // Test worker status updates
        $updateTimes = [];
        for ($i = 0; $i < 100; $i++) {
            $randomWorker = $workers[array_rand($workers)];
            $workerId = $randomWorker->id->toString();

            $time = PerformanceTestHelper::measureTime(function () use ($workerId) {
                $this->updateWorkerStatus($workerId, WorkerStatus::BUSY);
                $this->updateWorkerStatus($workerId, WorkerStatus::AVAILABLE);
            });

            $updateTimes[] = $time;
        }

        $updateStats = PerformanceTestHelper::calculateStatistics($updateTimes);
        echo "Worker status updates: " . PerformanceTestHelper::formatStatistics($updateStats) . "\n";

        // Validate performance benchmarks
        $this->assertLessThan(1.0, $lookupStats['avg'], 'Worker lookup average time exceeds 1ms');
        $this->assertLessThan(2.0, $availableStats['avg'], 'Available workers query average time exceeds 2ms');
        $this->assertLessThan(5.0, $updateStats['avg'], 'Worker update average time exceeds 5ms');

        PerformanceTestHelper::assertPerformance($workerLookupTimes, 1.0, 2.0, 'Worker lookup');
        PerformanceTestHelper::assertPerformance($availableWorkerTimes, 2.0, 5.0, 'Available workers query');
    }

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

        // Create jobs with different statuses and priorities
        $jobs = array_merge(
            PerformanceTestHelper::createBulkJobs(500),
            PerformanceTestHelper::createBulkJobs(300, \App\Framework\Queue\Jobs\JobPriority::HIGH),
            PerformanceTestHelper::createBulkJobs(200, \App\Framework\Queue\Jobs\JobPriority::CRITICAL)
        );

        // Distribute and update job statuses
        foreach ($jobs as $index => $job) {
            $this->distributionService->distributeJob($job);

            // Simulate some completed jobs
            if ($index % 3 === 0) {
                $this->updateJobStatus($job->id, JobStatus::COMPLETED);
            } elseif ($index % 5 === 0) {
                $this->updateJobStatus($job->id, JobStatus::FAILED);
            }
        }

        echo "\nJob Query Performance Test:\n";
        echo "Total jobs: " . count($jobs) . "\n";

        // Test job lookup by ID
        $jobLookupTimes = [];
        for ($i = 0; $i < 100; $i++) {
            $randomJob = $jobs[array_rand($jobs)];
            $jobId = $randomJob->id;

            $time = PerformanceTestHelper::measureTime(function () use ($jobId) {
                return $this->getJobById($jobId);
            });

            $jobLookupTimes[] = $time;
        }

        $lookupStats = PerformanceTestHelper::calculateStatistics($jobLookupTimes);
        echo "Job lookup by ID: " . PerformanceTestHelper::formatStatistics($lookupStats) . "\n";

        // Test getting jobs by status
        $statusQueryTimes = [];
        $statuses = [JobStatus::PENDING, JobStatus::PROCESSING, JobStatus::COMPLETED, JobStatus::FAILED];

        foreach ($statuses as $status) {
            for ($i = 0; $i < 25; $i++) {
                $time = PerformanceTestHelper::measureTime(function () use ($status) {
                    return $this->getJobsByStatus($status);
                });
                $statusQueryTimes[] = $time;
            }
        }

        $statusStats = PerformanceTestHelper::calculateStatistics($statusQueryTimes);
        echo "Jobs by status query: " . PerformanceTestHelper::formatStatistics($statusStats) . "\n";

        // Test getting jobs by worker
        $workerJobTimes = [];
        for ($i = 0; $i < 50; $i++) {
            $randomWorker = $workers[array_rand($workers)];
            $workerId = $randomWorker->id->toString();

            $time = PerformanceTestHelper::measureTime(function () use ($workerId) {
                return $this->getJobsByWorker($workerId);
            });

            $workerJobTimes[] = $time;
        }

        $workerJobStats = PerformanceTestHelper::calculateStatistics($workerJobTimes);
        echo "Jobs by worker query: " . PerformanceTestHelper::formatStatistics($workerJobStats) . "\n";

        // Validate performance benchmarks
        $this->assertLessThan(1.0, $lookupStats['avg'], 'Job lookup average time exceeds 1ms');
        $this->assertLessThan(5.0, $statusStats['avg'], 'Job status query average time exceeds 5ms');
        $this->assertLessThan(3.0, $workerJobStats['avg'], 'Jobs by worker query average time exceeds 3ms');

        PerformanceTestHelper::assertPerformance($jobLookupTimes, 1.0, 2.0, 'Job lookup');
        PerformanceTestHelper::assertPerformance($statusQueryTimes, 5.0, 10.0, 'Job status queries');
    }

    public function testBatchOperationPerformance(): void
    {
        echo "\nBatch Operation Performance Test:\n";

        $batchSizes = [10, 50, 100, 500, 1000];

        foreach ($batchSizes as $batchSize) {
            echo "Testing batch size: {$batchSize}\n";

            // Test batch worker registration
            $workers = $this->createWorkers($batchSize, 20);

            $batchRegisterTime = PerformanceTestHelper::measureTime(function () use ($workers) {
                $this->registerWorkers($workers);
            });

            // Test batch job distribution
            $jobs = PerformanceTestHelper::createBulkJobs($batchSize);

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

            // Test batch job status updates
            $batchUpdateTime = PerformanceTestHelper::measureTime(function () use ($jobs) {
                foreach ($jobs as $job) {
                    $this->updateJobStatus($job->id, JobStatus::COMPLETED);
                }
            });

            $perItemRegister = $batchRegisterTime / $batchSize;
            $perItemDistribute = $batchDistributeTime / $batchSize;
            $perItemUpdate = $batchUpdateTime / $batchSize;

            echo sprintf(
                "  Register: %6.1fms total (%4.2fms/item), Distribute: %6.1fms (%4.2fms/item), Update: %6.1fms (%4.2fms/item)\n",
                $batchRegisterTime,
                $perItemRegister,
                $batchDistributeTime,
                $perItemDistribute,
                $batchUpdateTime,
                $perItemUpdate
            );

            // Batch operations should be efficient
            $this->assertLessThan(2.0, $perItemRegister, "Worker registration too slow for batch size {$batchSize}");
            $this->assertLessThan(5.0, $perItemDistribute, "Job distribution too slow for batch size {$batchSize}");
            $this->assertLessThan(1.0, $perItemUpdate, "Job update too slow for batch size {$batchSize}");

            $this->cleanupTestData();
        }
    }

    public function testIndexEfficiency(): void
    {
        echo "\nIndex Efficiency Test:\n";

        // Create large dataset to test index effectiveness
        $workerCount = 1000;
        $jobCount = 5000;

        $workers = $this->createWorkers($workerCount, 20);
        $this->registerWorkers($workers);

        $jobs = PerformanceTestHelper::createBulkJobs($jobCount);
        foreach ($jobs as $job) {
            $this->distributionService->distributeJob($job);
        }

        echo "Dataset: {$workerCount} workers, {$jobCount} jobs\n";

        // Test indexed queries performance
        $indexTests = [
            'worker_by_id' => function () use ($workers) {
                $randomWorker = $workers[array_rand($workers)];

                return $this->workerRegistry->getWorker($randomWorker->id->toString());
            },
            'workers_by_status' => function () {
                return $this->workerRegistry->getWorkersByStatus(WorkerStatus::AVAILABLE);
            },
            'jobs_by_status' => function () {
                return $this->getJobsByStatus(JobStatus::PENDING);
            },
            'jobs_by_priority' => function () {
                return $this->getJobsByPriority(\App\Framework\Queue\Jobs\JobPriority::HIGH);
            },
            'jobs_by_queue' => function () {
                return $this->getJobsByQueue('test_queue');
            },
        ];

        foreach ($indexTests as $testName => $testFunction) {
            $times = [];

            for ($i = 0; $i < 100; $i++) {
                $time = PerformanceTestHelper::measureTime($testFunction);
                $times[] = $time;
            }

            $stats = PerformanceTestHelper::calculateStatistics($times);
            echo "{$testName}: " . PerformanceTestHelper::formatStatistics($stats) . "\n";

            // All indexed queries should be fast even with large dataset
            $this->assertLessThan(10.0, $stats['avg'], "Index query {$testName} too slow with large dataset");
            $this->assertLessThan(25.0, $stats['p95'], "Index query {$testName} P95 too slow with large dataset");
        }
    }

    public function testConnectionPoolingPerformance(): void
    {
        echo "\nConnection Pooling Performance Test:\n";

        // Simulate multiple concurrent database operations
        $operationTypes = [
            'worker_lookup' => function () {
                $workerId = 'worker_' . rand(1, 100);

                return $this->workerRegistry->getWorker($workerId);
            },
            'job_insertion' => function () {
                $job = PerformanceTestHelper::createTestJob('pool_test_' . uniqid());

                return $this->distributionService->distributeJob($job);
            },
            'status_query' => function () {
                return $this->getJobsByStatus(JobStatus::PENDING);
            },
        ];

        $concurrencyLevels = [1, 5, 10, 20];

        foreach ($concurrencyLevels as $concurrency) {
            echo "Testing concurrency level: {$concurrency}\n";

            $operationTimes = [];
            $operationsPerType = 20;

            foreach ($operationTypes as $typeName => $operation) {
                $typeTimes = [];

                // Simulate concurrent operations (simplified for single-threaded PHP)
                for ($i = 0; $i < $operationsPerType * $concurrency; $i++) {
                    $time = PerformanceTestHelper::measureTime($operation);
                    $typeTimes[] = $time;
                    $operationTimes[] = $time;
                }

                $typeStats = PerformanceTestHelper::calculateStatistics($typeTimes);
                echo "  {$typeName}: " . PerformanceTestHelper::formatStatistics($typeStats) . "\n";
            }

            $overallStats = PerformanceTestHelper::calculateStatistics($operationTimes);
            echo "  Overall: " . PerformanceTestHelper::formatStatistics($overallStats) . "\n";

            // Performance should not degrade significantly with concurrency
            $this->assertLessThan(50.0, $overallStats['avg'], "Database operations too slow at concurrency {$concurrency}");
        }
    }

    public function testQueryOptimizationEffectiveness(): void
    {
        echo "\nQuery Optimization Effectiveness Test:\n";

        // Create test data with specific patterns to test optimization
        $workers = $this->createWorkers(500, 20);
        $this->registerWorkers($workers);

        $jobs = PerformanceTestHelper::createBulkJobs(2000);
        foreach ($jobs as $job) {
            $this->distributionService->distributeJob($job);
        }

        // Test complex queries that should benefit from optimization
        $complexQueries = [
            'workers_with_capacity_filter' => function () {
                return $this->getWorkersByCapacityRange(15, 25);
            },
            'jobs_with_multiple_filters' => function () {
                return $this->getJobsWithFilters(JobStatus::PENDING, \App\Framework\Queue\Jobs\JobPriority::NORMAL);
            },
            'job_count_aggregation' => function () {
                return $this->getJobCountsByStatus();
            },
            'worker_utilization_stats' => function () {
                return $this->getWorkerUtilizationStats();
            },
        ];

        foreach ($complexQueries as $queryName => $queryFunction) {
            $times = [];

            for ($i = 0; $i < 50; $i++) {
                $time = PerformanceTestHelper::measureTime($queryFunction);
                $times[] = $time;
            }

            $stats = PerformanceTestHelper::calculateStatistics($times);
            echo "{$queryName}: " . PerformanceTestHelper::formatStatistics($stats) . "\n";

            // Complex queries should still be reasonably fast
            $this->assertLessThan(20.0, $stats['avg'], "Complex query {$queryName} not optimized enough");
            $this->assertLessThan(50.0, $stats['p95'], "Complex query {$queryName} P95 not optimized enough");
        }
    }

    public function testTransactionPerformance(): void
    {
        echo "\nTransaction Performance Test:\n";

        $workers = $this->createWorkers(10, 20);
        $this->registerWorkers($workers);

        // Test transaction overhead
        $transactionSizes = [1, 5, 10, 50, 100];

        foreach ($transactionSizes as $size) {
            $transactionTimes = [];

            for ($iteration = 0; $iteration < 20; $iteration++) {
                $time = PerformanceTestHelper::measureTime(function () use ($size, $iteration) {
                    $pdo = $this->database->getConnection();

                    try {
                        $pdo->beginTransaction();

                        for ($i = 0; $i < $size; $i++) {
                            $job = PerformanceTestHelper::createTestJob("tx_job_{$iteration}_{$i}");
                            $this->distributionService->distributeJob($job);
                        }

                        $pdo->commit();
                    } catch (\Exception $e) {
                        $pdo->rollBack();

                        throw $e;
                    }
                });

                $transactionTimes[] = $time;
            }

            $stats = PerformanceTestHelper::calculateStatistics($transactionTimes);
            $timePerOperation = $stats['avg'] / $size;

            echo sprintf(
                "Transaction size %3d: %6.1fms total (%5.2fms/operation)\n",
                $size,
                $stats['avg'],
                $timePerOperation
            );

            // Transaction overhead should be reasonable
            $this->assertLessThan(200.0, $stats['avg'], "Transaction time too high for size {$size}");

            $this->cleanupJobs();
        }
    }

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

        return $workers;
    }

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

    private function updateWorkerStatus(string $workerId, WorkerStatus $status): void
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('UPDATE workers SET status = ? WHERE id = ?');
        $stmt->execute([$status->value, $workerId]);
    }

    private function updateJobStatus(string $jobId, JobStatus $status): void
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('UPDATE jobs SET status = ? WHERE id = ?');
        $stmt->execute([$status->value, $jobId]);
    }

    private function getJobById(string $jobId): ?array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE id = ?');
        $stmt->execute([$jobId]);

        return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
    }

    private function getJobsByStatus(JobStatus $status): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE status = ? LIMIT 100');
        $stmt->execute([$status->value]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getJobsByWorker(string $workerId): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE worker_id = ? LIMIT 100');
        $stmt->execute([$workerId]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getJobsByPriority(\App\Framework\Queue\Jobs\JobPriority $priority): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE priority = ? LIMIT 100');
        $stmt->execute([$priority->value]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getJobsByQueue(string $queueName): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE queue_name = ? LIMIT 100');
        $stmt->execute([$queueName]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getWorkersByCapacityRange(int $minCapacity, int $maxCapacity): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM workers WHERE capacity BETWEEN ? AND ?');
        $stmt->execute([$minCapacity, $maxCapacity]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getJobsWithFilters(JobStatus $status, \App\Framework\Queue\Jobs\JobPriority $priority): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->prepare('SELECT * FROM jobs WHERE status = ? AND priority = ? LIMIT 100');
        $stmt->execute([$status->value, $priority->value]);

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getJobCountsByStatus(): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->query('SELECT status, COUNT(*) as count FROM jobs GROUP BY status');

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function getWorkerUtilizationStats(): array
    {
        $pdo = $this->database->getConnection();
        $stmt = $pdo->query('
            SELECT
                w.status,
                AVG(w.capacity) as avg_capacity,
                COUNT(*) as worker_count,
                COUNT(j.id) as job_count
            FROM workers w
            LEFT JOIN jobs j ON w.id = j.worker_id
            GROUP BY w.status
        ');

        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

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

    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
            )
        ');

        // Comprehensive indexes for performance testing
        $pdo->exec('CREATE INDEX idx_workers_id ON workers(id)');
        $pdo->exec('CREATE INDEX idx_workers_status ON workers(status)');
        $pdo->exec('CREATE INDEX idx_workers_capacity ON workers(capacity)');
        $pdo->exec('CREATE INDEX idx_workers_status_capacity ON workers(status, capacity)');

        $pdo->exec('CREATE INDEX idx_jobs_id ON jobs(id)');
        $pdo->exec('CREATE INDEX idx_jobs_status ON jobs(status)');
        $pdo->exec('CREATE INDEX idx_jobs_priority ON jobs(priority)');
        $pdo->exec('CREATE INDEX idx_jobs_queue ON jobs(queue_name)');
        $pdo->exec('CREATE INDEX idx_jobs_worker ON jobs(worker_id)');
        $pdo->exec('CREATE INDEX idx_jobs_status_priority ON jobs(status, priority)');
        $pdo->exec('CREATE INDEX idx_jobs_created ON jobs(created_at)');

        return new DatabaseManager($pdo);
    }

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