<?php

declare(strict_types=1);

namespace App\Framework\Database\Cache;

use App\Framework\Database\ConnectionInterface;

final readonly class QueryCacheKey
{
    private string $hashedKey;

    public function __construct(
        private string $sql,
        private array $parameters,
        private ConnectionInterface $connection
    ) {
        $this->hashedKey = $this->generateHash();
    }

    public function toString(): string
    {
        return $this->hashedKey;
    }

    public function __toString(): string
    {
        return $this->toString();
    }

    private function generateHash(): string
    {
        // Normalisiere SQL (Whitespace, Case)
        $normalizedSql = $this->normalizeSql($this->sql);

        // Sortiere Parameter für konsistente Hashes
        $sortedParams = $this->normalizeParameters($this->parameters);

        // Verwende Connection-spezifische Daten
        $connectionInfo = $this->getConnectionInfo();

        // Generiere deterministische Hash
        $keyData = [
            'sql' => $normalizedSql,
            'params' => $sortedParams,
            'connection' => $connectionInfo,
        ];

        $serialized = serialize($keyData);

        return 'query_cache:' . hash('sha256', $serialized);
    }

    private function normalizeSql(string $sql): string
    {
        // Entferne überflüssige Zeichen und normalisiere
        $normalized = preg_replace('/\s+/', ' ', trim($sql));

        return strtolower($normalized);
    }

    private function normalizeParameters(array $parameters): array
    {
        // Für assoziative Arrays: nach Schlüssel sortieren
        if ($this->isAssociativeArray($parameters)) {
            ksort($parameters);
        }

        // Normalisiere Werte
        return array_map([$this, 'normalizeValue'], $parameters);
    }

    private function normalizeValue(mixed $value): mixed
    {
        // Strings trimmen
        if (is_string($value)) {
            return trim($value);
        }

        // Floats mit fester Präzision
        if (is_float($value)) {
            return round($value, 10);
        }

        // Objects zu String konvertieren wenn möglich
        if (is_object($value) && method_exists($value, '__toString')) {
            return (string) $value;
        }

        return $value;
    }

    private function isAssociativeArray(array $array): bool
    {
        return array_keys($array) !== range(0, count($array) - 1);
    }

    private function getConnectionInfo(): array
    {
        try {
            $pdo = $this->connection->getPdo();

            return [
                'driver' => $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME),
                'server_version' => $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION),
                // Keine sensitive Daten wie Host/Database für bessere Portabilität
            ];
        } catch (\Throwable) {
            return ['driver' => 'unknown'];
        }
    }

    public function getSql(): string
    {
        return $this->sql;
    }

    public function getParameters(): array
    {
        return $this->parameters;
    }

    public function matches(string $pattern): bool
    {
        // Ermöglicht Pattern-basierte Invalidierung
        return fnmatch($pattern, $this->hashedKey) ||
               str_contains($this->normalizeSql($this->sql), strtolower($pattern));
    }

    public function containsTable(string $tableName): bool
    {
        $normalizedSql = $this->normalizeSql($this->sql);

        // Einfache Heuristik für Tabellenerkennung
        $patterns = [
            " from {$tableName} ",
            " join {$tableName} ",
            " into {$tableName} ",
            " update {$tableName} ",
        ];

        foreach ($patterns as $pattern) {
            if (str_contains($normalizedSql, strtolower($pattern))) {
                return true;
            }
        }

        return false;
    }
}
