<?php

declare(strict_types=1);

namespace Origin\Engine;

use RuntimeException;
use InvalidArgumentException;

class EngineHealth
{
    private EngineClient $client;
    private array $config;
    private array $metrics = [];
    private int $consecutiveFailures = 0;
    private int $consecutiveSuccesses = 0;
    private bool $healthy = false;
    private ?float $lastCheckTime = null;
    private array $failureLog = [];

    public function __construct(EngineClient $client, array $config)
    {
        $this->client = $client;
        $this->config = array_merge([
            'check_interval_seconds' => 30,
            'failure_threshold' => 3,
            'recovery_threshold' => 2,
            'metrics_retention_hours' => 24,
        ], $config);
    }

    public function check(): bool
    {
        $start = microtime(true);
        $this->lastCheckTime = $start;

        try {
            $health = $this->client->healthCheck();
            $latency = (microtime(true) - $start) * 1000;

            if ($health) {
                $this->recordSuccess($latency);
            } else {
                $this->recordFailure('Health check returned unhealthy status', $latency);
            }

            return $health;
        } catch (RuntimeException $e) {
            $latency = (microtime(true) - $start) * 1000;
            $this->recordFailure($e->getMessage(), $latency);
            return false;
        }
    }

    public function getStatus(): array
    {
        return [
            'healthy' => $this->healthy,
            'last_check' => $this->lastCheckTime !== null
                ? date('c', (int)$this->lastCheckTime)
                : null,
            'consecutive_failures' => $this->consecutiveFailures,
            'consecutive_successes' => $this->consecutiveSuccesses,
            'total_checks' => count($this->metrics),
            'recent_latency_ms' => $this->getRecentLatency(),
            'uptime_percentage' => $this->calculateUptime(),
            'last_failure_reason' => $this->failureLog[0]['reason'] ?? null,
        ];
    }

    public function getDetailedStatus(): array
    {
        try {
            $engineStatus = $this->client->getEngineStatus();
        } catch (RuntimeException $e) {
            $engineStatus = ['error' => $e->getMessage()];
        }

        return [
            'health' => $this->getStatus(),
            'engine' => $engineStatus,
            'metrics' => [
                'latency_p50_ms' => $this->getLatencyPercentile(50),
                'latency_p95_ms' => $this->getLatencyPercentile(95),
                'latency_p99_ms' => $this->getLatencyPercentile(99),
                'checks_last_hour' => $this->getChecksInWindow(3600),
                'failures_last_hour' => $this->getFailuresInWindow(3600),
            ],
            'failure_log' => array_slice($this->failureLog, 0, 10),
        ];
    }

    public function isHealthy(): bool
    {
        if ($this->lastCheckTime === null) {
            $this->check();
        }

        return $this->healthy;
    }

    public function waitForRecovery(int $timeoutSeconds = 60, int $pollIntervalMs = 1000): bool
    {
        $deadline = microtime(true) + $timeoutSeconds;

        while (microtime(true) < $deadline) {
            if ($this->check() && $this->healthy) {
                return true;
            }
            usleep($pollIntervalMs * 1000);
        }

        return false;
    }

    public function runPeriodicChecks(callable $onStateChange, int $maxIterations = 0): void
    {
        $iterations = 0;

        while ($maxIterations === 0 || $iterations < $maxIterations) {
            $previousState = $this->healthy;
            $this->check();

            if ($previousState !== $this->healthy) {
                $onStateChange($this->healthy, $this->getStatus());
            }

            $iterations++;
            sleep($this->config['check_interval_seconds']);
        }
    }

    public function getMetricsSnapshot(): array
    {
        $cutoff = strtotime('-' . ($this->config['metrics_retention_hours'] ?? 24) . ' hours');
        $recent = array_filter(
            $this->metrics,
            fn(array $m): bool => $m['timestamp'] >= $cutoff
        );

        $latencies = array_column($recent, 'latency_ms');
        sort($latencies);

        $total = count($recent);
        $successes = count(array_filter($recent, fn(array $m): bool => $m['success']));

        return [
            'total_checks' => $total,
            'success_count' => $successes,
            'failure_count' => $total - $successes,
            'uptime_pct' => $total > 0 ? round(($successes / $total) * 100, 2) : 0,
            'latency_min_ms' => $latencies ? round($latencies[0], 2) : 0,
            'latency_max_ms' => $latencies ? round($latencies[count($latencies) - 1], 2) : 0,
            'latency_avg_ms' => $latencies ? round(array_sum($latencies) / count($latencies), 2) : 0,
            'latency_p95_ms' => $this->getLatencyPercentile(95),
            'latency_p99_ms' => $this->getLatencyPercentile(99),
            'period_hours' => $this->config['metrics_retention_hours'],
        ];
    }

    public function resetMetrics(): void
    {
        $this->metrics = [];
        $this->consecutiveFailures = 0;
        $this->consecutiveSuccesses = 0;
        $this->failureLog = [];
    }

    private function recordSuccess(float $latencyMs): void
    {
        $this->consecutiveFailures = 0;
        $this->consecutiveSuccesses++;

        $this->metrics[] = [
            'timestamp' => time(),
            'success' => true,
            'latency_ms' => round($latencyMs, 2),
        ];

        if ($this->consecutiveSuccesses >= ($this->config['recovery_threshold'] ?? 2)) {
            $this->healthy = true;
        }

        $this->pruneOldMetrics();
    }

    private function recordFailure(string $reason, float $latencyMs): void
    {
        $this->consecutiveSuccesses = 0;
        $this->consecutiveFailures++;

        $this->metrics[] = [
            'timestamp' => time(),
            'success' => false,
            'latency_ms' => round($latencyMs, 2),
        ];

        $this->failureLog[] = [
            'timestamp' => date('c'),
            'reason' => $reason,
            'latency_ms' => round($latencyMs, 2),
        ];

        if ($this->consecutiveFailures >= ($this->config['failure_threshold'] ?? 3)) {
            $this->healthy = false;
        }

        $this->pruneOldMetrics();
    }

    private function pruneOldMetrics(): void
    {
        $cutoff = strtotime('-' . ($this->config['metrics_retention_hours'] ?? 24) . ' hours');
        $this->metrics = array_filter(
            $this->metrics,
            fn(array $m): bool => $m['timestamp'] >= $cutoff
        );
    }

    private function getRecentLatency(): ?float
    {
        if (empty($this->metrics)) {
            return null;
        }

        $latest = end($this->metrics);
        return $latest['latency_ms'] ?? null;
    }

    private function calculateUptime(): float
    {
        if (empty($this->metrics)) {
            return 0.0;
        }

        $successes = count(array_filter($this->metrics, fn(array $m): bool => $m['success']));
        return round(($successes / count($this->metrics)) * 100, 2);
    }

    private function getLatencyPercentile(int $percentile): float
    {
        $latencies = array_filter(array_column($this->metrics, 'latency_ms'));

        if (empty($latencies)) {
            return 0.0;
        }

        sort($latencies);
        $index = (int)ceil(($percentile / 100) * count($latencies)) - 1;
        $index = max(0, $index);

        return round($latencies[$index] ?? 0, 2);
    }

    private function getChecksInWindow(int $seconds): int
    {
        $cutoff = time() - $seconds;
        return count(array_filter($this->metrics, fn(array $m): bool => $m['timestamp'] >= $cutoff));
    }

    private function getFailuresInWindow(int $seconds): int
    {
        $cutoff = time() - $seconds;
        return count(array_filter(
            $this->metrics,
            fn(array $m): bool => $m['timestamp'] >= $cutoff && !$m['success']
        ));
    }
}
