<?php

declare(strict_types=1);

namespace App\Framework\Database\Middleware;

use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;

final readonly class RetryMiddleware implements QueryMiddleware
{
    private ConnectionHealthChecker $healthChecker;

    public function __construct(
        private int $maxRetries = 3,
        private int $retryDelayMs = 100,
        private array $retryableExceptions = [
            \PDOException::class,
            DatabaseException::class,
        ]
    ) {
        $this->healthChecker = new ConnectionHealthChecker();
    }

    public function process(QueryContext $context, callable $next): mixed
    {
        // Transaktions-Commits und Rollbacks nicht wiederholen
        if (in_array($context->operation, ['commit', 'rollback'], true)) {
            return $next($context);
        }

        return $this->retryOperation($context, $next);
    }

    public function getPriority(): int
    {
        return 50; // Mittlere Priorität
    }

    private function retryOperation(QueryContext $context, callable $next): mixed
    {
        $attempt = 0;
        $lastException = null;

        while ($attempt <= $this->maxRetries) {
            try {
                return $next($context);
            } catch (\Throwable $e) {
                $lastException = $e;

                // Prüfe ob Exception retry-bar ist
                if (!$this->isRetryableException($e)) {
                    throw $e;
                }

                // Prüfe ob wir noch Versuche haben
                if ($attempt >= $this->maxRetries) {
                    break;
                }

                $attempt++;

                // Health Check vor Retry
                if (!$this->healthChecker->checkConnectionAlive($context->connection)) {
                    // Für LazyGhost: Connection ist automatisch lazy und wird bei nächstem Zugriff neu initialisiert
                    // Keine explizite Aktion nötig - LazyGhost handled das automatisch
                }

                // Exponential Backoff
                $delay = $this->retryDelayMs * (2 ** ($attempt - 1));
                usleep($delay * 1000); // usleep benötigt Mikrosekunden
            }
        }

        throw new DatabaseException(
            "Operation failed after {$this->maxRetries} retries. Last error: {$lastException->getMessage()}",
            0,
            $lastException
        );
    }

    private function isRetryableException(\Throwable $exception): bool
    {
        foreach ($this->retryableExceptions as $retryableClass) {
            if ($exception instanceof $retryableClass) {
                // Prüfe spezifische Fehlercodes die nicht retry-bar sind
                if ($exception instanceof \PDOException) {
                    return $this->isPdoExceptionRetryable($exception);
                }
                return true;
            }
        }
        return false;
    }

    private function isPdoExceptionRetryable(\PDOException $exception): bool
    {
        // Bestimmte PDO-Fehler sind nicht retry-bar (z.B. Syntax-Fehler)
        $nonRetryableCodes = [
            '42000', // Syntax Error
            '42S02', // Table doesn't exist
            '42S22', // Column doesn't exist
            '23000', // Integrity constraint violation
        ];

        $sqlState = $exception->getCode();
        return !in_array($sqlState, $nonRetryableCodes, true);
    }
}
