<?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\Logging\ValueObjects\LogContext;
use App\Framework\Queue\Entities\Worker;
use App\Framework\Queue\ValueObjects\WorkerId;

/**
 * Worker Health Check Service für Monitoring und Überwachung
 */
final readonly class WorkerHealthCheckService
{
    private const HEALTH_CHECK_INTERVAL_SECONDS = 60;
    private const CRITICAL_CPU_THRESHOLD = 90;
    private const WARNING_CPU_THRESHOLD = 75;
    private const CRITICAL_MEMORY_THRESHOLD_GB = 2.0;
    private const WARNING_MEMORY_THRESHOLD_GB = 1.5;
    private const HEARTBEAT_TIMEOUT_SECONDS = 120;

    public function __construct(
        private WorkerRegistry $workerRegistry,
        private ConnectionInterface $connection,
        private Logger $logger
    ) {
    }

    /**
     * Health Check für alle aktiven Worker durchführen
     */
    public function performHealthCheck(): array
    {
        $this->logger->info('Starting worker health check');

        $workers = $this->workerRegistry->findActiveWorkers();
        $healthResults = [];

        foreach ($workers as $worker) {
            $health = $this->checkWorkerHealth($worker);
            $healthResults[] = $health;

            // Health Status in Datenbank speichern
            $this->recordHealthStatus($worker->id, $health);

            // Bei kritischen Problemen Worker deaktivieren
            if ($health['status'] === 'critical') {
                $this->handleCriticalWorker($worker, $health);
            }
        }

        // Cleanup inaktiver Worker
        $this->workerRegistry->cleanupInactiveWorkers(2); // 2 Minuten ohne Heartbeat

        $overallHealth = $this->calculateOverallHealth($healthResults);

        $this->logger->info('Worker health check completed', LogContext::withData([
            'total_workers' => count($workers),
            'healthy_workers' => count(array_filter($healthResults, fn ($h) => $h['status'] === 'healthy')),
            'warning_workers' => count(array_filter($healthResults, fn ($h) => $h['status'] === 'warning')),
            'critical_workers' => count(array_filter($healthResults, fn ($h) => $h['status'] === 'critical')),
            'overall_status' => $overallHealth['status'],
        ]));

        return [
            'workers' => $healthResults,
            'overall' => $overallHealth,
            'checked_at' => date('Y-m-d H:i:s'),
        ];
    }

    /**
     * Health Check für einzelnen Worker
     */
    public function checkWorkerHealth(Worker $worker): array
    {
        $issues = [];
        $warnings = [];
        $score = 100;

        // Heartbeat prüfen
        $heartbeatAge = $worker->lastHeartbeat
            ? time() - $worker->lastHeartbeat->getTimestamp()
            : PHP_INT_MAX;

        if ($heartbeatAge > self::HEARTBEAT_TIMEOUT_SECONDS) {
            $issues[] = "No heartbeat for {$heartbeatAge} seconds";
            $score -= 50;
        } elseif ($heartbeatAge > self::HEARTBEAT_TIMEOUT_SECONDS / 2) {
            $warnings[] = "Heartbeat delayed ({$heartbeatAge}s)";
            $score -= 20;
        }

        // CPU Usage prüfen
        $cpuUsage = $worker->cpuUsage->getValue();
        if ($cpuUsage > self::CRITICAL_CPU_THRESHOLD) {
            $issues[] = "Critical CPU usage: {$cpuUsage}%";
            $score -= 30;
        } elseif ($cpuUsage > self::WARNING_CPU_THRESHOLD) {
            $warnings[] = "High CPU usage: {$cpuUsage}%";
            $score -= 15;
        }

        // Memory Usage prüfen
        $memoryGB = $worker->memoryUsage->toBytes() / (1024 * 1024 * 1024);
        if ($memoryGB > self::CRITICAL_MEMORY_THRESHOLD_GB) {
            $issues[] = sprintf("Critical memory usage: %.2fGB", $memoryGB);
            $score -= 30;
        } elseif ($memoryGB > self::WARNING_MEMORY_THRESHOLD_GB) {
            $warnings[] = sprintf("High memory usage: %.2fGB", $memoryGB);
            $score -= 15;
        }

        // Job Load prüfen
        $loadPercentage = $worker->getLoadPercentage()->getValue();
        if ($loadPercentage > 95) {
            $issues[] = "Worker overloaded: {$loadPercentage}%";
            $score -= 25;
        } elseif ($loadPercentage > 80) {
            $warnings[] = "High worker load: {$loadPercentage}%";
            $score -= 10;
        }

        // Aktiv-Status prüfen
        if (! $worker->isActive) {
            $issues[] = "Worker marked as inactive";
            $score -= 100;
        }

        // Status basierend auf Score bestimmen
        $status = match (true) {
            $score <= 30 || ! empty($issues) => 'critical',
            $score <= 60 || ! empty($warnings) => 'warning',
            default => 'healthy'
        };

        return [
            'worker_id' => $worker->id->toString(),
            'hostname' => $worker->hostname,
            'process_id' => $worker->processId,
            'status' => $status,
            'score' => max(0, $score),
            'metrics' => [
                'heartbeat_age_seconds' => $heartbeatAge < PHP_INT_MAX ? $heartbeatAge : null,
                'cpu_usage_percent' => $cpuUsage,
                'memory_usage_gb' => round($memoryGB, 3),
                'job_load_percent' => $loadPercentage,
                'current_jobs' => $worker->currentJobs,
                'max_jobs' => $worker->maxJobs,
                'is_active' => $worker->isActive,
            ],
            'issues' => $issues,
            'warnings' => $warnings,
            'checked_at' => date('Y-m-d H:i:s'),
        ];
    }

    /**
     * Health Status in Datenbank speichern
     */
    private function recordHealthStatus(WorkerId $workerId, array $healthData): void
    {
        try {
            $sql = "INSERT INTO worker_health_checks (
                        worker_id, status, score, metrics, issues, warnings, checked_at
                    ) VALUES (
                        :worker_id, :status, :score, :metrics, :issues, :warnings, NOW()
                    )";

            $this->connection->execute(SqlQuery::create($sql, [
                'worker_id' => $workerId->toString(),
                'status' => $healthData['status'],
                'score' => $healthData['score'],
                'metrics' => json_encode($healthData['metrics']),
                'issues' => json_encode($healthData['issues']),
                'warnings' => json_encode($healthData['warnings']),
            ]));

        } catch (\Exception $e) {
            $this->logger->error('Failed to record worker health status', LogContext::withData([
                'worker_id' => $workerId->toString(),
                'error' => $e->getMessage(),
            ]));
        }
    }

    /**
     * Kritischen Worker behandeln
     */
    private function handleCriticalWorker(Worker $worker, array $healthData): void
    {
        $this->logger->error('Critical worker detected', LogContext::withData([
            'worker_id' => $worker->id->toString(),
            'hostname' => $worker->hostname,
            'issues' => $healthData['issues'],
            'score' => $healthData['score'],
        ]));

        // Worker als inaktiv markieren wenn zu viele kritische Issues
        $criticalIssueCount = count($healthData['issues']);
        if ($criticalIssueCount >= 2) {
            try {
                $this->workerRegistry->deregister($worker->id);

                $this->logger->warning('Worker deregistered due to critical issues', LogContext::withData([
                    'worker_id' => $worker->id->toString(),
                    'hostname' => $worker->hostname,
                    'critical_issues' => $criticalIssueCount,
                ]));

            } catch (\Exception $e) {
                $this->logger->error('Failed to deregister critical worker', LogContext::withData([
                    'worker_id' => $worker->id->toString(),
                    'error' => $e->getMessage(),
                ]));
            }
        }
    }

    /**
     * Overall Health für alle Worker berechnen
     */
    private function calculateOverallHealth(array $healthResults): array
    {
        if (empty($healthResults)) {
            return [
                'status' => 'critical',
                'score' => 0,
                'worker_count' => 0,
                'issues' => ['No workers available'],
            ];
        }

        $totalWorkers = count($healthResults);
        $healthyWorkers = count(array_filter($healthResults, fn ($h) => $h['status'] === 'healthy'));
        $warningWorkers = count(array_filter($healthResults, fn ($h) => $h['status'] === 'warning'));
        $criticalWorkers = count(array_filter($healthResults, fn ($h) => $h['status'] === 'critical'));

        // Durchschnittlicher Score
        $avgScore = array_sum(array_column($healthResults, 'score')) / $totalWorkers;

        // Percentage der gesunden Worker
        $healthyPercentage = ($healthyWorkers / $totalWorkers) * 100;

        // Overall Status bestimmen
        $status = match (true) {
            $healthyPercentage < 50 || $criticalWorkers > $totalWorkers / 2 => 'critical',
            $healthyPercentage < 80 || $warningWorkers > 0 => 'warning',
            default => 'healthy'
        };

        $overallIssues = [];
        if ($criticalWorkers > 0) {
            $overallIssues[] = "{$criticalWorkers} workers in critical state";
        }
        if ($warningWorkers > 0) {
            $overallIssues[] = "{$warningWorkers} workers with warnings";
        }
        if ($healthyPercentage < 70) {
            $overallIssues[] = "Only {$healthyPercentage}% of workers are healthy";
        }

        return [
            'status' => $status,
            'score' => round($avgScore, 1),
            'worker_count' => $totalWorkers,
            'healthy_workers' => $healthyWorkers,
            'warning_workers' => $warningWorkers,
            'critical_workers' => $criticalWorkers,
            'healthy_percentage' => round($healthyPercentage, 1),
            'issues' => $overallIssues,
        ];
    }

    /**
     * Worker Health History abrufen
     */
    public function getWorkerHealthHistory(WorkerId $workerId, ?Duration $period = null): array
    {
        $period = $period ?? Duration::fromDays(1);

        try {
            $sql = "SELECT * FROM worker_health_checks
                   WHERE worker_id = :worker_id
                   AND checked_at >= DATE_SUB(NOW(), INTERVAL :hours HOUR)
                   ORDER BY checked_at DESC
                   LIMIT 100";

            $result = $this->connection->query(SqlQuery::create($sql, [
                'worker_id' => $workerId->toString(),
                'hours' => $period->toHours(),
            ]));

            $history = [];
            foreach ($result->fetchAll() as $row) {
                $history[] = [
                    'status' => $row['status'],
                    'score' => (int) $row['score'],
                    'metrics' => json_decode($row['metrics'], true),
                    'issues' => json_decode($row['issues'], true),
                    'warnings' => json_decode($row['warnings'], true),
                    'checked_at' => $row['checked_at'],
                ];
            }

            return $history;

        } catch (\Exception $e) {
            $this->logger->error('Failed to get worker health history', LogContext::withData([
                'worker_id' => $workerId->toString(),
                'error' => $e->getMessage(),
            ]));

            throw $e;
        }
    }

    /**
     * System Health Report generieren
     */
    public function generateSystemHealthReport(): array
    {
        try {
            // Aktuelle Health Checks
            $currentHealth = $this->performHealthCheck();

            // Health Trends (letzte 24 Stunden)
            $sql = "SELECT
                       DATE(checked_at) as check_date,
                       HOUR(checked_at) as check_hour,
                       AVG(score) as avg_score,
                       COUNT(*) as total_checks,
                       SUM(CASE WHEN status = 'healthy' THEN 1 ELSE 0 END) as healthy_count,
                       SUM(CASE WHEN status = 'warning' THEN 1 ELSE 0 END) as warning_count,
                       SUM(CASE WHEN status = 'critical' THEN 1 ELSE 0 END) as critical_count
                   FROM worker_health_checks
                   WHERE checked_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
                   GROUP BY DATE(checked_at), HOUR(checked_at)
                   ORDER BY check_date DESC, check_hour DESC
                   LIMIT 24";

            $result = $this->connection->query(SqlQuery::create($sql));
            $trends = $result->fetchAll();

            // Top Issues der letzten 24 Stunden
            $issuesSql = "SELECT
                             JSON_UNQUOTE(JSON_EXTRACT(issues, '$[*]')) as issue_list,
                             COUNT(*) as occurrence_count
                         FROM worker_health_checks
                         WHERE checked_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
                         AND JSON_LENGTH(issues) > 0
                         GROUP BY JSON_EXTRACT(issues, '$[*]')
                         ORDER BY occurrence_count DESC
                         LIMIT 10";

            $result = $this->connection->query(SqlQuery::create($issuesSql));
            $topIssues = $result->fetchAll();

            return [
                'current_health' => $currentHealth,
                'trends_24h' => $trends,
                'top_issues_24h' => $topIssues,
                'generated_at' => date('Y-m-d H:i:s'),
            ];

        } catch (\Exception $e) {
            $this->logger->error('Failed to generate system health report', LogContext::withData([
                'error' => $e->getMessage(),
            ]));

            throw $e;
        }
    }

    /**
     * Health Check Cleanup (alte Einträge löschen)
     */
    public function cleanupHealthChecks(?Duration $retentionPeriod = null): int
    {
        $retentionPeriod = $retentionPeriod ?? Duration::fromDays(30);

        try {
            $sql = "DELETE FROM worker_health_checks
                   WHERE checked_at < DATE_SUB(NOW(), INTERVAL :hours HOUR)";

            $deletedCount = $this->connection->execute(SqlQuery::create($sql, [
                'hours' => $retentionPeriod->toHours(),
            ]));

            $this->logger->info('Health check cleanup completed', LogContext::withData([
                'deleted_records' => $deletedCount,
                'retention_days' => $retentionPeriod->toHours() / 24,
            ]));

            return $deletedCount;

        } catch (\Exception $e) {
            $this->logger->error('Failed to cleanup health checks', LogContext::withData([
                'error' => $e->getMessage(),
            ]));

            throw $e;
        }
    }
}
