<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\Logging\Logger;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Queue\ValueObjects\QueueMetrics;

/**
 * Production implementation of JobMetricsManager with full database integration
 */
final readonly class ProductionJobMetricsManager implements JobMetricsManagerInterface
{
    public function __construct(
        private ConnectionInterface $connection,
        private Logger $logger
    ) {
    }

    public function recordJobMetrics(JobMetrics $metrics): void
    {
        try {
            $this->connection->beginTransaction();

            $existingEntry = $this->findJobMetricsEntry($metrics->jobId);

            if ($existingEntry) {
                $this->updateJobMetricsEntry($metrics);
            } else {
                $this->insertJobMetricsEntry($metrics);
            }

            $this->connection->commit();

            $this->logger->debug('Job metrics recorded', [
                'job_id' => $metrics->jobId,
                'queue_name' => $metrics->queueName,
                'status' => $metrics->status,
                'execution_time_ms' => $metrics->executionTimeMs,
                'memory_usage_bytes' => $metrics->memoryUsageBytes,
            ]);
        } catch (\Exception $e) {
            $this->connection->rollback();

            $this->logger->error('Failed to record job metrics', [
                'job_id' => $metrics->jobId,
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    public function getJobMetrics(string $jobId): ?JobMetrics
    {
        $query = "SELECT * FROM job_metrics WHERE job_id = ? LIMIT 1";
        $result = $this->connection->queryOne(
            SqlQuery::create($query, [$jobId])
        );

        if (! $result) {
            return null;
        }

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

    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->query(
            SqlQuery::create($query, [$queueName, $windowStart])
        )->fetchAll();

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

        return $this->calculateQueueMetrics($queueName, $jobMetrics, $timeWindow);
    }

    /**
     * Get metrics for all queues in the time window
     * Optimized to avoid N+1 query problem
     *
     * @return array<QueueMetrics>
     */
    public function getAllQueueMetrics(string $timeWindow = '1 hour'): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));

        // Get all metrics in a single query and aggregate in PHP
        $query = "SELECT * FROM job_metrics
                  WHERE created_at >= ?
                  ORDER BY queue_name, created_at DESC";

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

        // Group by queue name
        $metricsByQueue = [];
        foreach ($results as $row) {
            $queueName = $row['queue_name'];
            if (!isset($metricsByQueue[$queueName])) {
                $metricsByQueue[$queueName] = [];
            }
            $metricsByQueue[$queueName][] = $this->mapRowToJobMetrics($row);
        }

        // Calculate metrics for each queue
        $allMetrics = [];
        foreach ($metricsByQueue as $queueName => $jobMetrics) {
            $allMetrics[] = $this->calculateQueueMetrics($queueName, $jobMetrics, $timeWindow);
        }

        return $allMetrics;
    }

    public function getSystemOverview(): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime('-24 hours'));

        // Get overall statistics
        $overallStats = $this->connection->queryOne(
            SqlQuery::create("
                SELECT
                    COUNT(*) as total_jobs,
                    SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_jobs,
                    SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_jobs,
                    SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_jobs,
                    SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) as running_jobs,
                    AVG(execution_time_ms) as avg_execution_time,
                    AVG(memory_usage_bytes / 1024 / 1024) as avg_memory_mb
                FROM job_metrics
                WHERE created_at >= ?
            ", [$windowStart])
        );

        $totalJobs = (int) $overallStats['total_jobs'];
        $completedJobs = (int) $overallStats['completed_jobs'];
        $successRate = $totalJobs > 0 ? ($completedJobs / $totalJobs) * 100 : 100.0;

        // Get queue names
        $queueNames = $this->connection->query(
            SqlQuery::create("
                SELECT DISTINCT queue_name
                FROM job_metrics
                WHERE created_at >= ?
            ", [$windowStart])
        )->fetchAll();

        $queueMetrics = [];
        $healthyQueues = 0;

        foreach ($queueNames as $row) {
            $queueName = $row['queue_name'];
            $metrics = $this->getQueueMetrics($queueName, '24 hours');
            $queueMetrics[$queueName] = $metrics;

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

        $systemHealthScore = $this->calculateSystemHealthScore($successRate, count($queueNames), $healthyQueues);

        return [
            'system_health_score' => $systemHealthScore,
            'total_queues' => count($queueNames),
            'healthy_queues' => $healthyQueues,
            'total_jobs' => $totalJobs,
            'completed_jobs' => $completedJobs,
            'failed_jobs' => (int) $overallStats['failed_jobs'],
            'pending_jobs' => (int) $overallStats['pending_jobs'],
            'running_jobs' => (int) $overallStats['running_jobs'],
            'overall_success_rate' => round($successRate, 1),
            'average_execution_time_ms' => round((float) $overallStats['avg_execution_time'], 1),
            'average_memory_usage_mb' => round((float) $overallStats['avg_memory_mb'], 1),
            'queue_metrics' => $queueMetrics,
        ];
    }

    public function getTopSlowJobs(?string $queueName, int $limit): array
    {
        $params = [];
        $whereClause = '';

        if ($queueName) {
            $whereClause = 'WHERE queue_name = ?';
            $params[] = $queueName;
        }

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

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

        return array_map([$this, 'mapRowToJobMetrics'], $results);
    }

    public function getTopMemoryConsumers(?string $queueName, int $limit): array
    {
        $params = [];
        $whereClause = '';

        if ($queueName) {
            $whereClause = 'WHERE queue_name = ?';
            $params[] = $queueName;
        }

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

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

        return array_map([$this, 'mapRowToJobMetrics'], $results);
    }

    public function getFailedJobs(?string $queueName, string $timeWindow): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));
        $params = [$windowStart];
        $whereClause = 'WHERE status = "failed" AND created_at >= ?';

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

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

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

        return array_map([$this, 'mapRowToJobMetrics'], $results);
    }

    public function getPerformanceStats(?string $queueName, string $timeWindow): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));
        $params = [$windowStart];
        $whereClause = 'WHERE created_at >= ?';

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

        $query = "SELECT
                    COUNT(*) as total_jobs,
                    SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_jobs,
                    SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_jobs,
                    AVG(execution_time_ms) as avg_execution_time,
                    MIN(execution_time_ms) as min_execution_time,
                    MAX(execution_time_ms) as max_execution_time,
                    AVG(memory_usage_bytes / 1024 / 1024) as avg_memory_mb,
                    MIN(memory_usage_bytes) as min_memory_bytes,
                    MAX(memory_usage_bytes) as max_memory_bytes,
                    AVG(attempts) as avg_attempts
                  FROM job_metrics
                  {$whereClause}";

        $stats = $this->connection->queryOne(
            SqlQuery::create($query, $params)
        );

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

        return [
            'total_jobs' => $totalJobs,
            'completed_jobs' => $completedJobs,
            'failed_jobs' => $failedJobs,
            'success_rate' => $totalJobs > 0 ? ($completedJobs / $totalJobs) * 100 : 100,
            'failure_rate' => $totalJobs > 0 ? ($failedJobs / $totalJobs) * 100 : 0,
            'average_execution_time_ms' => (float) $stats['avg_execution_time'],
            'min_execution_time_ms' => (float) $stats['min_execution_time'],
            'max_execution_time_ms' => (float) $stats['max_execution_time'],
            'average_memory_usage_mb' => (float) $stats['avg_memory_mb'],
            'min_memory_usage_bytes' => (int) $stats['min_memory_bytes'],
            'max_memory_usage_bytes' => (int) $stats['max_memory_bytes'],
            'average_attempts' => (float) $stats['avg_attempts'],
        ];
    }

    public function getThroughputStats(?string $queueName, string $timeWindow): array
    {
        $windowStart = date('Y-m-d H:i:s', strtotime("-{$timeWindow}"));
        $params = [$windowStart];
        $whereClause = 'WHERE status = "completed" AND created_at >= ?';

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

        // Overall throughput
        $overallQuery = "SELECT COUNT(*) as total_completed
                        FROM job_metrics
                        {$whereClause}";

        $totalCompleted = (int) $this->connection->queryOne(
            SqlQuery::create($overallQuery, $params)
        )['total_completed'];

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

        $hourlyStats = $this->connection->query(
            SqlQuery::create($hourlyQuery, $params)
        )->fetchAll();

        // Calculate average per hour
        $hours = $this->getHoursInTimeWindow($timeWindow);
        $averageThroughput = $hours > 0 ? $totalCompleted / $hours : 0;

        return [
            'total_completed' => $totalCompleted,
            'average_throughput_per_hour' => $averageThroughput,
            'hourly_breakdown' => $hourlyStats,
        ];
    }

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

        $query = "DELETE FROM job_metrics WHERE created_at < ?";
        $affectedRows = $this->connection->execute(
            SqlQuery::create($query, [$cutoffDate])
        );

        $this->logger->info('Job metrics cleanup completed', [
            'cutoff_date' => $cutoffDate,
            'deleted_rows' => $affectedRows,
        ]);

        return $affectedRows;
    }

    private function insertJobMetricsEntry(JobMetrics $metrics): void
    {
        $query = "INSERT INTO job_metrics (
                    job_id, queue_name, status, attempts, max_attempts,
                    execution_time_ms, memory_usage_bytes,
                    created_at, started_at, completed_at, failed_at,
                    error_message, metadata
                  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

        $this->connection->execute(
            SqlQuery::create($query, [
                $metrics->jobId,
                $metrics->queueName,
                $metrics->status,
                $metrics->attempts,
                $metrics->maxAttempts,
                $metrics->executionTimeMs,
                $metrics->memoryUsageBytes,
                $metrics->createdAt,
                $metrics->startedAt,
                $metrics->completedAt,
                $metrics->failedAt,
                $metrics->errorMessage,
                $metrics->metadata ? json_encode($metrics->metadata) : null,
            ])
        );
    }

    private function updateJobMetricsEntry(JobMetrics $metrics): void
    {
        $query = "UPDATE job_metrics SET
                    status = ?, attempts = ?, execution_time_ms = ?,
                    memory_usage_bytes = ?, started_at = ?, completed_at = ?,
                    failed_at = ?, error_message = ?, metadata = ?
                  WHERE job_id = ?";

        $this->connection->execute(
            SqlQuery::create($query, [
                $metrics->status,
                $metrics->attempts,
                $metrics->executionTimeMs,
                $metrics->memoryUsageBytes,
                $metrics->startedAt,
                $metrics->completedAt,
                $metrics->failedAt,
                $metrics->errorMessage,
                $metrics->metadata ? json_encode($metrics->metadata) : null,
                $metrics->jobId,
            ])
        );
    }

    private function findJobMetricsEntry(string $jobId): ?array
    {
        $query = "SELECT * FROM job_metrics WHERE job_id = ? LIMIT 1";
        $result = $this->connection->queryOne(
            SqlQuery::create($query, [$jobId])
        );

        return $result ?: null;
    }

    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: (int) $row['execution_time_ms'],
            memoryUsageBytes: (int) $row['memory_usage_bytes'],
            createdAt: $row['created_at'],
            startedAt: $row['started_at'],
            completedAt: $row['completed_at'],
            failedAt: $row['failed_at'],
            errorMessage: $row['error_message'],
            metadata: $row['metadata'] ? json_decode($row['metadata'], true) : []
        );
    }

    private function calculateQueueMetrics(string $queueName, array $jobMetrics, string $timeWindow): QueueMetrics
    {
        $totalJobs = count($jobMetrics);

        if ($totalJobs === 0) {
            return new QueueMetrics(
                queueName: $queueName,
                totalJobs: 0,
                completedJobs: 0,
                failedJobs: 0,
                pendingJobs: 0,
                runningJobs: 0,
                deadLetterJobs: 0,
                successRate: new Percentage(100.0),
                averageExecutionTimeMs: 0,
                averageMemoryUsageMB: 0.0,
                throughputJobsPerHour: 0.0,
                measuredAt: date('Y-m-d H:i:s')
            );
        }

        $completed = $failed = $pending = $running = $deadLetter = 0;
        $totalExecutionTime = $totalMemory = 0;

        foreach ($jobMetrics as $metrics) {
            switch ($metrics->status) {
                case 'completed':
                    $completed++;

                    break;
                case 'failed':
                    $failed++;

                    break;
                case 'pending':
                    $pending++;

                    break;
                case 'running':
                    $running++;

                    break;
                case 'dead_letter':
                    $deadLetter++;

                    break;
            }

            $totalExecutionTime += $metrics->executionTimeMs;
            $totalMemory += $metrics->memoryUsageBytes;
        }

        $successRate = new Percentage($totalJobs > 0 ? ($completed / $totalJobs) * 100 : 100);
        $avgExecutionTime = $totalJobs > 0 ? $totalExecutionTime / $totalJobs : 0;
        $avgMemoryMB = $totalJobs > 0 ? ($totalMemory / $totalJobs) / (1024 * 1024) : 0;

        // Calculate throughput (jobs per hour)
        $hours = $this->getHoursInTimeWindow($timeWindow);
        $throughput = $hours > 0 ? $completed / $hours : 0;

        return new QueueMetrics(
            queueName: $queueName,
            totalJobs: $totalJobs,
            completedJobs: $completed,
            failedJobs: $failed,
            pendingJobs: $pending,
            runningJobs: $running,
            deadLetterJobs: $deadLetter,
            successRate: $successRate,
            averageExecutionTimeMs: $avgExecutionTime,
            averageMemoryUsageMB: $avgMemoryMB,
            throughputJobsPerHour: $throughput,
            measuredAt: date('Y-m-d H:i:s')
        );
    }

    private function calculateSystemHealthScore(float $successRate, int $totalQueues, int $healthyQueues): int
    {
        $successWeight = 60; // 60% weight for success rate
        $healthWeight = 40;  // 40% weight for queue health

        $successScore = $successRate;
        $healthScore = $totalQueues > 0 ? ($healthyQueues / $totalQueues) * 100 : 100;

        $weightedScore = ($successScore * $successWeight + $healthScore * $healthWeight) / 100;

        return (int) round($weightedScore);
    }

    private function getHoursInTimeWindow(string $timeWindow): float
    {
        try {
            // Use framework's Duration class instead of manual parsing
            $duration = Duration::parse($timeWindow);

            return $duration->toHours();
        } catch (\Exception) {
            // Fallback to 1 hour if parsing fails
            return 1.0;
        }
    }
}
