<?php
declare(strict_types=1);

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

final class WorkerRecoveryManager
{
    private static ?self $instance = null;
    private WorkerRecoveryStore $store;

    private function __construct(
        private readonly string $basePath,
        private readonly WorkerRecoveryPolicy $policy = new WorkerRecoveryPolicy()
    ) {
        $this->store = new WorkerRecoveryStore($basePath);
    }

    public static function instance(?string $basePath = null): self
    {
        if (self::$instance === null) {
            $basePath ??= self::detectBasePath();
            self::$instance = new self($basePath);
        }
        return self::$instance;
    }

    public static function resetInstance(?string $basePath = null, ?WorkerRecoveryPolicy $policy = null): self
    {
        $basePath ??= self::detectBasePath();
        self::$instance = new self($basePath, $policy ?? WorkerRecoveryPolicy::default());
        return self::$instance;
    }

    public function recover(?WorkerStaleReport $staleReport = null): WorkerRecoveryReport
    {
        if ($staleReport === null) {
            $staleManager = WorkerStaleManager::instance($this->basePath);
            $staleReport = $staleManager->load();
            if ($staleReport->records() === []) {
                $staleReport = $staleManager->inspect();
            }
        }

        $actions = [];
        $processed = 0;

        foreach ($staleReport->records() as $record) {
            if ($processed >= $this->policy->maxRecoveriesPerRun()) {
                break;
            }

            $classification = (string) ($record['classification'] ?? WorkerStaleRecord::HEALTHY);
            $workerId = (string) ($record['workerId'] ?? '');

            if ($workerId === '' || !$this->policy->shouldRecover($classification)) {
                continue;
            }

            $actions[] = WorkerRecoveryAction::forClassification($workerId, $classification)->toArray();
            $processed++;
        }

        $report = new WorkerRecoveryReport($actions, $this->statistics($actions), $this->policy->toArray(), date('c'));
        $this->store->save($report);
        return $report;
    }

    public function store(): WorkerRecoveryStore { return $this->store; }
    public function load(): WorkerRecoveryReport { return $this->store->load(); }
    public function clear(): void { $this->store->reset(); }

    public function summary(): array
    {
        $report = $this->store->exists() ? $this->load() : $this->recover();
        return [
            'actions' => $report->actions(),
            'statistics' => $report->statistics(),
            'policy' => $report->policy(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

    private function statistics(array $actions): array
    {
        $stats = [
            'actions' => count($actions),
            'restart' => 0,
            'rehydrate' => 0,
            'reallocate' => 0,
            'quarantineCandidate' => 0,
            'successful' => 0,
            'failed' => 0,
        ];

        foreach ($actions as $action) {
            $type = (string) ($action['action'] ?? WorkerRecoveryAction::SKIP);

            if ($type === WorkerRecoveryAction::RESTART) {
                $stats['restart']++;
            } elseif ($type === WorkerRecoveryAction::REHYDRATE) {
                $stats['rehydrate']++;
            } elseif ($type === WorkerRecoveryAction::REALLOCATE) {
                $stats['reallocate']++;
            } elseif ($type === WorkerRecoveryAction::QUARANTINE_CANDIDATE) {
                $stats['quarantineCandidate']++;
            }

            if (($action['successful'] ?? false) === true) {
                $stats['successful']++;
            } else {
                $stats['failed']++;
            }
        }

        return $stats;
    }

    private static function detectBasePath(): string
    {
        $candidate = dirname(__DIR__, 5);
        $real = realpath($candidate);
        return $real !== false ? $real : $candidate;
    }
}
