<?php

declare(strict_types=1);

namespace App\Framework\Database\Middleware;

use App\Framework\Database\Cache\CacheStrategy;
use App\Framework\Database\Cache\QueryCacheKey;
use App\Framework\Database\Exception\DatabaseException;

final readonly class CacheMiddleware implements QueryMiddleware
{
    public function __construct(
        private CacheStrategy $cacheStrategy,
        private int $defaultTtlSeconds = 300, // 5 Minuten
        private bool $enabled = true,
        private array $cacheableOperations = ['query', 'queryOne', 'queryColumn', 'queryScalar']
    ) {}

    public function process(QueryContext $context, callable $next): mixed
    {
        if (!$this->enabled || !$this->isCacheable($context)) {
            return $next($context);
        }

        $cacheKey = $this->generateCacheKey($context);

        // Versuche aus Cache zu lesen
        $cachedResult = $this->cacheStrategy->get($cacheKey);
        if ($cachedResult !== null) {
            // Cache Hit - speichere Statistik
            $context = $context->withMetadata('cache_hit', true);
            $context = $context->withMetadata('cache_key', $cacheKey->toString());

            return $this->deserializeResult($cachedResult, $context->operation);
        }

        // Cache Miss - führe Query aus
        $result = $next($context);

        // Speichere Ergebnis im Cache
        $ttl = $this->determineTtl($context);
        $serializedResult = $this->serializeResult($result, $context->operation);

        $this->cacheStrategy->set($cacheKey, $serializedResult, $ttl);

        // Speichere Cache-Metadaten
        $context = $context->withMetadata('cache_hit', false);
        $context = $context->withMetadata('cache_key', $cacheKey->toString());
        $context = $context->withMetadata('cache_ttl', $ttl);

        return $result;
    }

    public function getPriority(): int
    {
        return 30; // Niedrige Priorität - nach HealthCheck und Retry
    }

    private function isCacheable(QueryContext $context): bool
    {
        // Nur bestimmte Operationen sind cacheable
        if (!in_array($context->operation, $this->cacheableOperations, true)) {
            return false;
        }

        // Transaktions-Queries nicht cachen
        if ($context->connection->inTransaction()) {
            return false;
        }

        // Prüfe auf non-cacheable SQL-Patterns
        $sql = strtoupper(trim($context->sql));

        // SELECT-Statements sind normalerweise cacheable
        if (!str_starts_with($sql, 'SELECT')) {
            return false;
        }

        // Bestimmte SELECT-Patterns nicht cachen
        $nonCacheablePatterns = [
            'NOW()',
            'CURRENT_TIMESTAMP',
            'RAND()',
            'RANDOM()',
            'UUID()',
            'CURRENT_USER',
            'CONNECTION_ID()',
        ];

        foreach ($nonCacheablePatterns as $pattern) {
            if (str_contains($sql, $pattern)) {
                return false;
            }
        }

        return true;
    }

    private function generateCacheKey(QueryContext $context): QueryCacheKey
    {
        return new QueryCacheKey(
            $context->sql,
            $context->parameters,
            $context->connection
        );
    }

    private function determineTtl(QueryContext $context): int
    {
        // Prüfe auf custom TTL in Metadaten
        if ($context->hasMetadata('cache_ttl')) {
            return (int) $context->getMetadata('cache_ttl');
        }

        // Intelligente TTL basierend auf Query-Pattern
        $sql = strtoupper(trim($context->sql));

        // Lange TTL für statische/referenz Daten
        if (str_contains($sql, 'INFORMATION_SCHEMA') ||
            str_contains($sql, 'SHOW TABLES') ||
            str_contains($sql, 'DESCRIBE ')) {
            return 3600; // 1 Stunde
        }

        // Mittlere TTL für Aggregationen
        if (str_contains($sql, 'COUNT(') ||
            str_contains($sql, 'SUM(') ||
            str_contains($sql, 'AVG(') ||
            str_contains($sql, 'GROUP BY')) {
            return 900; // 15 Minuten
        }

        // Standard TTL
        return $this->defaultTtlSeconds;
    }

    private function serializeResult(mixed $result, string $operation): array
    {
        return [
            'operation' => $operation,
            'data' => $result,
            'timestamp' => time(),
            'serialized_at' => microtime(true),
        ];
    }

    private function deserializeResult(array $cachedData, string $operation): mixed
    {
        // Validiere dass Operation übereinstimmt
        if ($cachedData['operation'] !== $operation) {
            throw new DatabaseException(
                "Cache operation mismatch: expected {$operation}, got {$cachedData['operation']}"
            );
        }

        return $cachedData['data'];
    }

    public function invalidatePattern(string $pattern): int
    {
        return $this->cacheStrategy->invalidatePattern($pattern);
    }

    public function invalidateAll(): void
    {
        $this->cacheStrategy->clear();
    }

    public function getCacheStats(): array
    {
        return $this->cacheStrategy->getStats();
    }
}
