<?php

declare(strict_types=1);

namespace App\Framework\Database\Middleware;

use App\Framework\Core\Events\EventDispatcherInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;
use App\Framework\DateTime\Clock;
use App\Framework\Retry\RetryManager;
use App\Framework\Retry\Strategies\ExponentialBackoffStrategy;

/**
 * Database Retry-Middleware basierend auf dem neuen Retry-System
 *
 * Ersetzt die alte RetryMiddleware mit einheitlicher Retry-Logik
 */
final readonly class UnifiedRetryMiddleware implements QueryMiddleware
{
    private RetryManager $retryManager;

    public function __construct(
        Clock $clock,
        ?EventDispatcherInterface $eventDispatcher = null,
        private ConnectionHealthChecker $healthChecker = new ConnectionHealthChecker()
    ) {
        $this->retryManager = RetryManager::create($clock)
            ->withStrategy(ExponentialBackoffStrategy::forDatabase())
            ->withContext(['middleware' => 'database']);

        if ($eventDispatcher !== null) {
            $this->retryManager = $this->retryManager->withEventDispatcher($eventDispatcher);
        }
    }

    public function process(QueryContext $context, callable $next): mixed
    {
        // Transaktions-Commits und Rollbacks nicht wiederholen
        if ($this->isTransactionCommand($context)) {
            return $next($context);
        }

        // Health-Check vor Retry
        if (! $this->healthChecker->isHealthy($context->connection)) {
            throw new DatabaseException('Database connection is unhealthy');
        }

        // Retry-Kontext erweitern
        $retryManager = $this->retryManager->withContext([
            'query_type' => $this->getQueryType($context),
            'connection_name' => $context->connection->getName() ?? 'default',
        ]);

        // Retry-Operation ausführen
        $result = $retryManager->execute(function () use ($context, $next) {
            return $next($context);
        });

        return $result->getResult();
    }

    private function isTransactionCommand(QueryContext $context): bool
    {
        $sql = strtoupper(trim($context->sql));

        return str_starts_with($sql, 'COMMIT') ||
               str_starts_with($sql, 'ROLLBACK') ||
               str_starts_with($sql, 'START TRANSACTION') ||
               str_starts_with($sql, 'BEGIN');
    }

    private function getQueryType(QueryContext $context): string
    {
        $sql = strtoupper(trim($context->sql));

        return match (true) {
            str_starts_with($sql, 'SELECT') => 'select',
            str_starts_with($sql, 'INSERT') => 'insert',
            str_starts_with($sql, 'UPDATE') => 'update',
            str_starts_with($sql, 'DELETE') => 'delete',
            str_starts_with($sql, 'CREATE') => 'ddl',
            str_starts_with($sql, 'ALTER') => 'ddl',
            str_starts_with($sql, 'DROP') => 'ddl',
            default => 'other'
        };
    }

    public function getPriority(): int
    {
        return 100;
    }
}
