<?php

declare(strict_types=1);

namespace App\Framework\CircuitBreaker;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\Next;
use App\Framework\Http\Request;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Status;
use Throwable;

/**
 * Middleware zur Integration von Circuit Breaker Pattern in HTTP-Requests
 */
final readonly class CircuitBreakerMiddleware implements HttpMiddleware
{
    /**
     * Services die durch Circuit Breaker geschützt werden sollen
     * @var array<string, CircuitBreakerConfig>
     */
    private array $protectedServices;

    public function __construct(
        private CircuitBreaker $circuitBreaker,
        array $protectedServices = []
    ) {
        $this->protectedServices = $protectedServices;
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        $request = $context->request;
        $route = $stateManager->get('route_name') ?? $request->path;

        // Prüfen ob Route durch Circuit Breaker geschützt werden soll
        $service = $this->getServiceForRoute($route);
        if ($service === null) {
            return $next($context);
        }

        $config = $this->protectedServices[$service] ?? null;

        try {
            // Circuit Breaker prüfen bevor Request verarbeitet wird
            $this->circuitBreaker->check($service, $config);

            // Request normal verarbeiten
            $resultContext = $next($context);

            // Erfolg nur bei 2xx Status Codes prüfen wenn Response vorhanden
            if ($resultContext->hasResponse()) {
                $response = $resultContext->response;
                $statusCode = $response?->status->value;

                if ($statusCode >= 200 && $statusCode < 300) {
                    $this->circuitBreaker->recordSuccess($service, $config);
                } elseif ($statusCode >= 500) {
                    // 5xx Fehler als Circuit Breaker Fehler behandeln
                    $exception = new \Exception("HTTP {$statusCode} response");
                    $this->circuitBreaker->recordFailure($service, $exception, $config);
                }
            }

            return $resultContext;

        } catch (CircuitBreakerException $e) {
            // Circuit Breaker ist offen - Service Unavailable Response
            $response = $this->createCircuitOpenResponse($e);

            return $context->withResponse($response);

        } catch (Throwable $e) {
            // Andere Exceptions als Circuit Breaker Fehler behandeln
            $this->circuitBreaker->recordFailure($service, $e, $config);

            throw $e;
        }
    }

    /**
     * Bestimmt welcher Service für eine Route zuständig ist
     */
    private function getServiceForRoute(string $route): ?string
    {
        // Einfache Route-zu-Service Mapping
        // Kann durch komplexere Logik ersetzt werden

        if (str_starts_with($route, '/api/')) {
            return 'api';
        }

        if (str_starts_with($route, '/admin/')) {
            return 'admin';
        }

        // Weitere Service-Mappings...
        foreach ($this->protectedServices as $service => $config) {
            if (str_contains($route, $service)) {
                return $service;
            }
        }

        return null;
    }

    /**
     * Erstellt Response wenn Circuit Breaker offen ist
     */
    private function createCircuitOpenResponse(CircuitBreakerException $e): HttpResponse
    {
        $body = json_encode([
            'error' => true,
            'code' => 503,
            'message' => 'Service temporarily unavailable. Please try again later.',
            'service' => $e->service,
            'retryAfter' => $e->retryAfterSeconds,
            'timestamp' => date('c'),
        ], JSON_PRETTY_PRINT);

        return new HttpResponse(
            status: Status::SERVICE_UNAVAILABLE,
            headers: new Headers(
                [
                    'Content-Type' => 'application/json; charset=utf-8',
                    'Retry-After' => (string) $e->retryAfterSeconds,
                    'X-Circuit-Breaker' => 'open',
                    'X-Service' => $e->service,
                ],
            ),
            body: $body
        );
    }

    /**
     * Factory method für Standard-API-Schutz
     */
    public static function forApi(CircuitBreaker $circuitBreaker): self
    {
        return new self($circuitBreaker, [
            'api' => CircuitBreakerConfig::forExternalService(
                failureThreshold: 10,
                recoveryTimeout: Duration::fromSeconds(30)
            ),
        ]);
    }

    /**
     * Factory method für Admin-Bereich-Schutz
     */
    public static function forAdmin(CircuitBreaker $circuitBreaker): self
    {
        return new self($circuitBreaker, [
            'admin' => new CircuitBreakerConfig(
                failureThreshold: 5,
                recoveryTimeout: Duration::fromSeconds(60)
            ),
        ]);
    }
}
