<?php

declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Worker\Monitoring;

final class WorkerStaleDetector
{
    public function __construct(private readonly WorkerStalePolicy $policy = new WorkerStalePolicy()) {}

    public function inspect(WorkerHeartbeatCollection $heartbeats, array $expectedWorkerIds = []): WorkerStaleReport
    {
        $records = [];
        $seen = [];

        foreach ($heartbeats->all() as $heartbeat) {
            $seen[] = $heartbeat->workerId();
            $records[] = $this->classify($heartbeat)->toArray();
        }

        foreach ($expectedWorkerIds as $workerId) {
            if (!in_array($workerId, $seen, true)) {
                $records[] = WorkerStaleRecord::offline((string) $workerId)->toArray();
            }
        }

        return new WorkerStaleReport($records, $this->statistics($records), $this->policy->toArray(), date('c'));
    }

    public function classify(WorkerHeartbeat $heartbeat): WorkerStaleRecord
    {
        $recorded = strtotime($heartbeat->recordedAt());
        $age = max(0, time() - $recorded);
        $data = $heartbeat->toArray();

        if ($heartbeat->status() === 'failed' || $heartbeat->failed() > 0) {
            return WorkerStaleRecord::failed($heartbeat->workerId(), $age, $data);
        }

        if ($heartbeat->status() === 'running' && $age > $this->policy->runningTimeoutSeconds()) {
            return WorkerStaleRecord::hung($heartbeat->workerId(), $age, $data);
        }

        if ($age > $this->policy->heartbeatTimeoutSeconds()) {
            return WorkerStaleRecord::stale($heartbeat->workerId(), $age, $data);
        }

        return WorkerStaleRecord::healthy($heartbeat->workerId(), $age, $data);
    }

    private function statistics(array $records): array
    {
        $stats = [
            'workers' => count($records),
            'healthy' => 0,
            'stale' => 0,
            'hung' => 0,
            'offline' => 0,
            'failed' => 0,
            'unhealthy' => 0,
        ];

        foreach ($records as $record) {
            $class = (string) ($record['classification'] ?? WorkerStaleRecord::HEALTHY);

            switch ($class) {
                case WorkerStaleRecord::HEALTHY:
                    $stats['healthy']++;
                    break;

                case WorkerStaleRecord::STALE:
                    $stats['stale']++;
                    $stats['unhealthy']++;
                    break;

                case WorkerStaleRecord::HUNG:
                    $stats['hung']++;
                    $stats['unhealthy']++;
                    break;

                case WorkerStaleRecord::OFFLINE:
                    $stats['offline']++;
                    $stats['unhealthy']++;
                    break;

                case WorkerStaleRecord::FAILED:
                    $stats['failed']++;
                    $stats['unhealthy']++;
                    break;

                default:
                    $stats['unhealthy']++;
                    break;
            }
        }

        return $stats;
    }
}
