<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Entities\JobMetricsEntry;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Queue\ValueObjects\QueueMetrics;

final readonly class JobMetricsManager
{
    public function __construct(
        private ConnectionInterface $connection,
        private Logger $logger
    ) {
    }

    public function recordJobMetrics(JobMetrics $metrics): void
    {
        $existingEntry = $this->findJobMetricsEntry($metrics->jobId);

        if ($existingEntry) {
            $updatedEntry = $existingEntry->updateWithMetrics($metrics);
            $this->entityManager->save($updatedEntry);
        } else {
            $newEntry = JobMetricsEntry::fromJobMetrics($metrics);
            $this->entityManager->save($newEntry);
        }

        $this->entityManager->flush();

        $this->logger->debug('Job metrics recorded', [
            'job_id' => $metrics->jobId,
            'queue_name' => $metrics->queueName,
            'status' => $metrics->status,
            'execution_time_ms' => $metrics->executionTimeMs,
            'memory_usage_mb' => $metrics->getMemoryUsageMB(),
        ]);
    }

    public function getJobMetrics(string $jobId): ?JobMetrics
    {
        $entry = $this->findJobMetricsEntry($jobId);

        return $entry ? $entry->getJobMetrics() : null;
    }

    public function getQueueMetrics(string $queueName, string $timeWindow = '1 hour'): QueueMetrics
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));

        $query = "SELECT * FROM job_metrics
                  WHERE queue_name = ? AND created_at >= ?
                  ORDER BY created_at DESC";

        $results = $this->connection->fetchAll($query, [$queueName, $windowStart]);

        $jobMetrics = array_map(function (array $row) {
            return $this->mapRowToJobMetrics($row);
        }, $results);

        return QueueMetrics::calculate($queueName, $jobMetrics, $timeWindow);
    }

    public function getAllQueueMetrics(string $timeWindow = '1 hour'): array
    {
        $query = "SELECT DISTINCT queue_name FROM job_metrics";
        $queueNames = $this->connection->fetchAll($query);

        $allMetrics = [];
        foreach ($queueNames as $row) {
            $queueName = $row['queue_name'];
            $allMetrics[$queueName] = $this->getQueueMetrics($queueName, $timeWindow);
        }

        return $allMetrics;
    }

    public function getJobMetricsHistory(string $jobId): array
    {
        $query = "SELECT * FROM job_metrics
                  WHERE job_id = ?
                  ORDER BY updated_at ASC";

        $results = $this->connection->fetchAll($query, [$jobId]);

        return array_map(function (array $row) {
            return $this->mapRowToJobMetrics($row);
        }, $results);
    }

    public function getTopSlowJobs(?string $queueName = null, int $limit = 10): array
    {
        $whereClause = $queueName ? "WHERE queue_name = ?" : "";
        $params = $queueName ? [$queueName] : [];

        $query = "SELECT * FROM job_metrics
                  {$whereClause}
                  ORDER BY execution_time_ms DESC
                  LIMIT ?";

        $params[] = $limit;
        $results = $this->connection->fetchAll($query, $params);

        return array_map(function (array $row) {
            return $this->mapRowToJobMetrics($row);
        }, $results);
    }

    public function getTopMemoryConsumers(?string $queueName = null, int $limit = 10): array
    {
        $whereClause = $queueName ? "WHERE queue_name = ?" : "";
        $params = $queueName ? [$queueName] : [];

        $query = "SELECT * FROM job_metrics
                  {$whereClause}
                  ORDER BY memory_usage_bytes DESC
                  LIMIT ?";

        $params[] = $limit;
        $results = $this->connection->fetchAll($query, $params);

        return array_map(function (array $row) {
            return $this->mapRowToJobMetrics($row);
        }, $results);
    }

    public function getFailedJobs(?string $queueName = null, string $timeWindow = '24 hours'): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));

        $whereClause = "WHERE status = 'failed' AND failed_at >= ?";
        $params = [$windowStart];

        if ($queueName) {
            $whereClause .= " AND queue_name = ?";
            $params[] = $queueName;
        }

        $query = "SELECT * FROM job_metrics
                  {$whereClause}
                  ORDER BY failed_at DESC";

        $results = $this->connection->fetchAll($query, $params);

        return array_map(function (array $row) {
            return $this->mapRowToJobMetrics($row);
        }, $results);
    }

    public function getPerformanceStats(?string $queueName = null, string $timeWindow = '24 hours'): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));

        $whereClause = "WHERE created_at >= ?";
        $params = [$windowStart];

        if ($queueName) {
            $whereClause .= " AND queue_name = ?";
            $params[] = $queueName;
        }

        $query = "SELECT
                    COUNT(*) as total_jobs,
                    COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_jobs,
                    COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_jobs,
                    AVG(execution_time_ms) as avg_execution_time_ms,
                    MIN(execution_time_ms) as min_execution_time_ms,
                    MAX(execution_time_ms) as max_execution_time_ms,
                    AVG(memory_usage_bytes) as avg_memory_usage_bytes,
                    MIN(memory_usage_bytes) as min_memory_usage_bytes,
                    MAX(memory_usage_bytes) as max_memory_usage_bytes,
                    AVG(attempts) as avg_attempts
                  FROM job_metrics
                  {$whereClause}";

        $result = $this->connection->fetchOne($query, $params);

        $totalJobs = (int) $result['total_jobs'];
        $completedJobs = (int) $result['completed_jobs'];
        $failedJobs = (int) $result['failed_jobs'];

        $successRate = $totalJobs > 0 ? ($completedJobs / $totalJobs) * 100 : 100;

        return [
            'time_window' => $timeWindow,
            'queue_name' => $queueName,
            'total_jobs' => $totalJobs,
            'completed_jobs' => $completedJobs,
            'failed_jobs' => $failedJobs,
            'success_rate' => $successRate,
            'failure_rate' => 100 - $successRate,
            'average_execution_time_ms' => (float) $result['avg_execution_time_ms'],
            'min_execution_time_ms' => (float) $result['min_execution_time_ms'],
            'max_execution_time_ms' => (float) $result['max_execution_time_ms'],
            'average_memory_usage_bytes' => (int) $result['avg_memory_usage_bytes'],
            'min_memory_usage_bytes' => (int) $result['min_memory_usage_bytes'],
            'max_memory_usage_bytes' => (int) $result['max_memory_usage_bytes'],
            'average_memory_usage_mb' => (int) $result['avg_memory_usage_bytes'] / (1024 * 1024),
            'average_attempts' => (float) $result['avg_attempts'],
        ];
    }

    public function getThroughputStats(?string $queueName = null, string $timeWindow = '24 hours'): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));

        $whereClause = "WHERE completed_at >= ?";
        $params = [$windowStart];

        if ($queueName) {
            $whereClause .= " AND queue_name = ?";
            $params[] = $queueName;
        }

        // Get hourly throughput
        $query = "SELECT
                    DATE_FORMAT(completed_at, '%Y-%m-%d %H:00:00') as hour,
                    COUNT(*) as jobs_completed
                  FROM job_metrics
                  {$whereClause}
                  GROUP BY hour
                  ORDER BY hour";

        $hourlyStats = $this->connection->fetchAll($query, $params);

        $totalHours = count($hourlyStats);
        $totalCompleted = array_sum(array_column($hourlyStats, 'jobs_completed'));
        $avgThroughput = $totalHours > 0 ? $totalCompleted / $totalHours : 0;

        return [
            'time_window' => $timeWindow,
            'queue_name' => $queueName,
            'total_completed' => $totalCompleted,
            'average_throughput_per_hour' => $avgThroughput,
            'hourly_breakdown' => $hourlyStats,
        ];
    }

    public function cleanupOldMetrics(int $olderThanDays = 90): int
    {
        $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$olderThanDays} days"));

        $query = "DELETE FROM job_metrics WHERE created_at < ?";

        $this->logger->info('Cleaning up old job metrics', [
            'cutoff_date' => $cutoffDate,
            'older_than_days' => $olderThanDays,
        ]);

        $affectedRows = $this->connection->execute($query, [$cutoffDate]);

        $this->logger->info('Old job metrics cleaned up', [
            'deleted_count' => $affectedRows,
        ]);

        return $affectedRows;
    }

    public function getSystemOverview(): array
    {
        $allMetrics = $this->getAllQueueMetrics('24 hours');

        $totalJobs = 0;
        $totalCompleted = 0;
        $totalFailed = 0;
        $totalPending = 0;
        $totalRunning = 0;
        $avgExecutionTime = 0;
        $avgMemoryUsage = 0;
        $healthyQueues = 0;

        foreach ($allMetrics as $queueMetrics) {
            $totalJobs += $queueMetrics->totalJobs;
            $totalCompleted += $queueMetrics->completedJobs;
            $totalFailed += $queueMetrics->failedJobs;
            $totalPending += $queueMetrics->pendingJobs;
            $totalRunning += $queueMetrics->runningJobs;
            $avgExecutionTime += $queueMetrics->averageExecutionTimeMs;
            $avgMemoryUsage += $queueMetrics->averageMemoryUsageMB;

            if ($queueMetrics->isHealthy()) {
                $healthyQueues++;
            }
        }

        $queueCount = count($allMetrics);
        $systemHealthScore = $queueCount > 0 ? ($healthyQueues / $queueCount) * 100 : 100;

        return [
            'total_queues' => $queueCount,
            'healthy_queues' => $healthyQueues,
            'system_health_score' => $systemHealthScore,
            'total_jobs' => $totalJobs,
            'completed_jobs' => $totalCompleted,
            'failed_jobs' => $totalFailed,
            'pending_jobs' => $totalPending,
            'running_jobs' => $totalRunning,
            'overall_success_rate' => $totalJobs > 0 ? ($totalCompleted / $totalJobs) * 100 : 100,
            'average_execution_time_ms' => $queueCount > 0 ? $avgExecutionTime / $queueCount : 0,
            'average_memory_usage_mb' => $queueCount > 0 ? $avgMemoryUsage / $queueCount : 0,
            'queue_metrics' => $allMetrics,
        ];
    }

    private function findJobMetricsEntry(string $jobId): ?JobMetricsEntry
    {
        $query = "SELECT * FROM job_metrics WHERE job_id = ? ORDER BY updated_at DESC LIMIT 1";
        $result = $this->connection->fetchOne($query, [$jobId]);

        if (! $result) {
            return null;
        }

        return $this->mapRowToEntry($result);
    }

    private function mapRowToEntry(array $row): JobMetricsEntry
    {
        return new JobMetricsEntry(
            id: $row['id'],
            jobId: $row['job_id'],
            queueName: $row['queue_name'],
            status: $row['status'],
            attempts: (int) $row['attempts'],
            maxAttempts: (int) $row['max_attempts'],
            executionTimeMs: (float) $row['execution_time_ms'],
            memoryUsageBytes: (int) $row['memory_usage_bytes'],
            createdAt: $row['created_at'],
            updatedAt: $row['updated_at'],
            errorMessage: $row['error_message'],
            startedAt: $row['started_at'],
            completedAt: $row['completed_at'],
            failedAt: $row['failed_at'],
            metadata: $row['metadata']
        );
    }

    private function mapRowToJobMetrics(array $row): JobMetrics
    {
        return new JobMetrics(
            jobId: $row['job_id'],
            queueName: $row['queue_name'],
            status: $row['status'],
            attempts: (int) $row['attempts'],
            maxAttempts: (int) $row['max_attempts'],
            executionTimeMs: (float) $row['execution_time_ms'],
            memoryUsageBytes: (int) $row['memory_usage_bytes'],
            errorMessage: $row['error_message'],
            createdAt: $row['created_at'],
            startedAt: $row['started_at'],
            completedAt: $row['completed_at'],
            failedAt: $row['failed_at'],
            metadata: $row['metadata'] ? json_decode($row['metadata'], true) : []
        );
    }
}
