<?php

declare(strict_types=1);

namespace App\Framework\CircuitBreaker;

use App\Framework\Cache\Cache;
use App\Framework\CircuitBreaker\Registry\ServiceRegistry;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\Logging\Logger;

/**
 * Manager für Circuit Breaker Monitoring und Management
 */
final class CircuitBreakerManager
{
    /**
     * @var array<string, CircuitBreaker>
     */
    private array $circuitBreakers;

    /**
     * @var array<string, CircuitBreakerConfig>
     */
    private array $configurations;

    public function __construct(
        private readonly Cache $cache,
        private readonly Clock $clock,
        private readonly ?Logger $logger = null,
        private readonly ?ServiceRegistry $serviceRegistry = null,
        array $configurations = []
    ) {
        $this->configurations = $configurations;
        $this->circuitBreakers = [];
    }

    /**
     * Holt oder erstellt einen Circuit Breaker für einen Service
     */
    public function getCircuitBreaker(string $namespace = 'default'): CircuitBreaker
    {
        if (! isset($this->circuitBreakers[$namespace])) {
            $this->circuitBreakers[$namespace] = new CircuitBreaker(
                cache: $this->cache,
                clock: $this->clock,
                logger: $this->logger,
                eventPublisher: null, // TODO: Add event publisher injection
                serviceRegistry: $this->serviceRegistry,
                namespace: $namespace
            );
        }

        return $this->circuitBreakers[$namespace];
    }

    /**
     * Gibt den aktuellen Status aller Services zurück
     */
    public function getAllServicesStatus(): array
    {
        $status = [];

        foreach ($this->circuitBreakers as $namespace => $circuitBreaker) {
            $status[$namespace] = [];

            // Alle Services in diesem Namespace finden
            $services = $this->getServicesInNamespace($namespace);

            foreach ($services as $service) {
                $metrics = $circuitBreaker->getMetrics($service);
                $status[$namespace][$service] = [
                    'state' => $metrics->state->value,
                    'failure_count' => $metrics->failureCount,
                    'success_count' => $metrics->successCount,
                    'half_open_attempts' => $metrics->halfOpenAttempts,
                    'last_failure_time' => $metrics->lastFailureTime?->toTimestamp(),
                    'opened_at' => $metrics->openedAt?->toTimestamp(),
                    'health_status' => $this->determineHealthStatus($metrics),
                    'configuration' => $this->getServiceConfiguration($service),
                    'summary' => $metrics->getSummary(),
                ];
            }
        }

        return $status;
    }

    /**
     * Gibt aggregierte Statistiken zurück
     */
    public function getGlobalStatistics(): array
    {
        $stats = [
            'total_services' => 0,
            'healthy_services' => 0,
            'degraded_services' => 0,
            'failed_services' => 0,
            'circuit_breakers_open' => 0,
            'circuit_breakers_half_open' => 0,
            'circuit_breakers_closed' => 0,
        ];

        $allStatus = $this->getAllServicesStatus();

        foreach ($allStatus as $namespace => $services) {
            foreach ($services as $service => $serviceStatus) {
                $stats['total_services']++;

                match ($serviceStatus['state']) {
                    'closed' => $stats['circuit_breakers_closed']++,
                    'open' => $stats['circuit_breakers_open']++,
                    'half_open' => $stats['circuit_breakers_half_open']++,
                };

                match ($serviceStatus['health_status']) {
                    'healthy' => $stats['healthy_services']++,
                    'degraded' => $stats['degraded_services']++,
                    'failed' => $stats['failed_services']++,
                };
            }
        }

        return $stats;
    }

    /**
     * Setzt alle Circuit Breaker zurück
     */
    public function resetAll(): void
    {
        foreach ($this->circuitBreakers as $circuitBreaker) {
            $circuitBreaker->resetAll();
        }

        if ($this->logger) {
            $this->logger->info('All circuit breakers have been reset by CircuitBreakerManager');
        }
    }

    /**
     * Setzt einen spezifischen Service zurück
     */
    public function resetService(string $service, string $namespace = 'default'): void
    {
        $circuitBreaker = $this->getCircuitBreaker($namespace);
        $circuitBreaker->reset($service);

        if ($this->logger) {
            $this->logger->info("Circuit breaker for service '{$service}' in namespace '{$namespace}' has been reset");
        }
    }

    /**
     * Führt Health Checks für alle konfigurierten Services durch
     */
    public function performHealthChecks(): array
    {
        $results = [];
        $allStatus = $this->getAllServicesStatus();

        foreach ($allStatus as $namespace => $services) {
            $results[$namespace] = [];

            foreach ($services as $service => $serviceStatus) {
                $healthCheckResult = $this->performServiceHealthCheck($service, $namespace);
                $results[$namespace][$service] = $healthCheckResult;

                // Logging für kritische Zustände
                if ($healthCheckResult['status'] === 'failed' && $serviceStatus['state'] === 'open') {
                    if ($this->logger) {
                        $this->logger->critical(
                            "Service '{$service}' in namespace '{$namespace}' is failing and circuit breaker is open",
                            $healthCheckResult
                        );
                    }
                }
            }
        }

        return $results;
    }

    /**
     * Exportiert Konfiguration für Monitoring-Tools
     */
    public function exportConfiguration(): array
    {
        return [
            'circuit_breakers' => array_keys($this->circuitBreakers),
            'configurations' => $this->configurations,
            'timestamp' => $this->clock->now()->format('c'),
            'version' => '1.0',
        ];
    }

    /**
     * Importiert Konfiguration
     */
    public function importConfiguration(array $config): void
    {
        if (isset($config['configurations'])) {
            foreach ($config['configurations'] as $service => $serviceConfig) {
                $this->configurations[$service] = new CircuitBreakerConfig(...$serviceConfig);
            }
        }
    }

    /**
     * Findet alle Services in einem Namespace
     */
    private function getServicesInNamespace(string $namespace): array
    {
        if ($this->serviceRegistry !== null) {
            return $this->serviceRegistry->discoverServices($namespace);
        }

        // Fallback: Returniere die konfigurierten Services
        $services = [];
        foreach ($this->configurations as $service => $config) {
            $services[] = $service;
        }

        return $services;
    }

    /**
     * Bestimmt den Health Status basierend auf Metriken
     */
    private function determineHealthStatus(CircuitBreakerMetrics $metrics): string
    {
        return match ($metrics->state) {
            CircuitState::CLOSED => 'healthy',
            CircuitState::HALF_OPEN => 'degraded',
            CircuitState::OPEN => 'failed',
        };
    }

    /**
     * Holt die Konfiguration für einen Service
     */
    private function getServiceConfiguration(string $service): ?array
    {
        $config = $this->configurations[$service] ?? null;

        if ($config === null) {
            return null;
        }

        return [
            'failure_threshold' => $config->failureThreshold,
            'recovery_timeout' => $config->recoveryTimeout->toHumanReadable(),
            'half_open_max_attempts' => $config->halfOpenMaxAttempts,
            'success_threshold' => $config->successThreshold,
            'metrics_retention_time' => $config->metricsRetentionTime->toHumanReadable(),
            'sliding_window_size' => $config->slidingWindowSize->toHumanReadable(),
        ];
    }

    /**
     * Führt Health Check für einen Service durch
     */
    private function performServiceHealthCheck(string $service, string $namespace): array
    {
        $circuitBreaker = $this->getCircuitBreaker($namespace);
        $metrics = $circuitBreaker->getMetrics($service);

        $healthCheck = [
            'service' => $service,
            'namespace' => $namespace,
            'status' => $this->determineHealthStatus($metrics),
            'checked_at' => $this->clock->now()->format('c'),
            'metrics' => $metrics->toArray(),
            'summary' => $metrics->getSummary(),
        ];

        // Zusätzliche Health Check Logik hier implementieren
        // z.B. ping external service, check database connection etc.

        return $healthCheck;
    }

    /**
     * Factory für Standard-Setup
     */
    public static function withDefaults(
        Cache $cache,
        Clock $clock,
        ?Logger $logger = null,
        ?ServiceRegistry $serviceRegistry = null
    ): self {
        $configs = [
            'database' => CircuitBreakerConfig::forDatabase(),
            'api' => new CircuitBreakerConfig(
                failureThreshold: 5,
                recoveryTimeout: Duration::fromMinutes(1),
                halfOpenMaxAttempts: 3,
                successThreshold: 2,
                metricsRetentionTime: Duration::fromHours(1),
                slidingWindowSize: Duration::fromMinutes(5)
            ),
            'external_service' => CircuitBreakerConfig::forExternalService(),
        ];

        return new self($cache, $clock, $logger, $serviceRegistry, $configs);
    }
}
