<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use InvalidArgumentException;

/**
 * Value Object für Cache-Schlüssel
 * Stellt sicher, dass Cache-Schlüssel gültig und konsistent sind
 */
final readonly class CacheKey implements CacheIdentifier
{
    private const int MAX_KEY_LENGTH = 250;
    private const string NAMESPACE_SEPARATOR = ':';

    private function __construct(
        private string $key
    ) {
        $this->validate($key);
    }

    /**
     * Erstellt einen CacheKey aus einem String
     */
    public static function fromString(string $key): self
    {
        return new self($key);
    }

    /**
     * Create cache key with automatic truncation for very long keys
     */
    public static function fromStringSafe(string $key): self
    {
        if (strlen($key) > self::MAX_KEY_LENGTH) {
            // Truncate and hash to ensure uniqueness while staying within limits
            $key = substr($key, 0, 200) . '_' . md5($key);
        }

        return new self($key);
    }

    /**
     * Erstellt einen CacheKey mit Namespace
     */
    public static function fromNamespace(string $namespace, string $key): self
    {
        return new self($namespace . self::NAMESPACE_SEPARATOR . $key);
    }

    /**
     * Erstellt einen CacheKey für ein bestimmtes Objekt
     */
    public static function forObject(object $object, string $suffix = ''): self
    {
        $className = self::getShortClassName($object::class);
        $objectId = method_exists($object, 'getId') ? $object->getId() : spl_object_id($object);

        $key = $className . self::NAMESPACE_SEPARATOR . $objectId;

        if (! empty($suffix)) {
            $key .= self::NAMESPACE_SEPARATOR . $suffix;
        }

        return new self($key);
    }

    /**
     * Erstellt einen CacheKey für eine Klasse
     */
    public static function forClass(string $class, string $suffix = ''): self
    {
        $className = self::getShortClassName($class);
        $key = $className;

        if (! empty($suffix)) {
            $key .= self::NAMESPACE_SEPARATOR . $suffix;
        }

        return new self($key);
    }

    /**
     * Erstellt einen CacheKey für eine Abfrage
     */
    public static function forQuery(string $query, array $parameters = []): self
    {
        $normalizedQuery = self::normalizeString($query);
        $queryHash = md5($normalizedQuery);

        $key = 'query' . self::NAMESPACE_SEPARATOR . $queryHash;

        if (! empty($parameters)) {
            $paramHash = md5(serialize($parameters));
            $key .= self::NAMESPACE_SEPARATOR . $paramHash;
        }

        return new self($key);
    }

    /**
     * Erstellt einen CacheKey für eine Sammlung von Daten
     */
    public static function forCollection(string $type, array $criteria = []): self
    {
        $shortType = self::getShortClassName($type);
        $key = 'collection' . self::NAMESPACE_SEPARATOR . $shortType;

        if (! empty($criteria)) {
            $criteriaHash = md5(serialize($criteria));
            $key .= self::NAMESPACE_SEPARATOR . $criteriaHash;
        }

        return new self($key);
    }

    /**
     * Gibt den Schlüssel als String zurück
     */
    public function toString(): string
    {
        return $this->key;
    }

    /**
     * Gibt den Schlüssel als String zurück
     */
    public function __toString(): string
    {
        return $this->key;
    }

    /**
     * Fügt einen Suffix zum Schlüssel hinzu
     */
    public function withSuffix(string $suffix): self
    {
        return new self($this->key . self::NAMESPACE_SEPARATOR . $suffix);
    }

    /**
     * Fügt einen Namespace zum Schlüssel hinzu
     */
    public function withNamespace(string $namespace): self
    {
        return new self($namespace . self::NAMESPACE_SEPARATOR . $this->key);
    }

    /**
     * Prüft, ob der Schlüssel einem Muster entspricht
     */
    public function matches(string $pattern): bool
    {
        return fnmatch($pattern, $this->key);
    }

    /**
     * Prüft, ob zwei CacheKeys gleich sind
     */
    public function equals(CacheIdentifier $other): bool
    {
        return $other instanceof self && $this->key === $other->key;
    }

    /**
     * Get the type of cache identifier
     */
    public function getType(): CacheIdentifierType
    {
        return CacheIdentifierType::KEY;
    }

    /**
     * Check if this identifier matches a cache key (CacheIdentifier interface)
     */
    public function matchesKey(CacheKey $key): bool
    {
        return $this->equals($key);
    }

    /**
     * Get a normalized string for internal cache operations
     */
    public function getNormalizedString(): string
    {
        return $this->key;
    }

    /**
     * Validiert den Schlüssel
     */
    private function validate(string $key): void
    {
        if (empty($key)) {
            throw new InvalidArgumentException('Cache key cannot be empty');
        }

        if (strlen($key) > self::MAX_KEY_LENGTH) {
            throw new InvalidArgumentException(sprintf(
                'Cache key length exceeds maximum of %d characters (got %d)',
                self::MAX_KEY_LENGTH,
                strlen($key)
            ));
        }

        // Prüfe auf ungültige Zeichen (z.B. Leerzeichen, Steuerzeichen)
        if (preg_match('/[\s\n\r\t\0\x0B]/', $key)) {
            throw new InvalidArgumentException('Cache key contains invalid characters');
        }
    }

    /**
     * Normalisiert einen String für konsistente Hashes
     */
    private static function normalizeString(string $input): string
    {
        // Entferne überflüssige Leerzeichen
        return preg_replace('/\s+/', ' ', trim($input));
    }

    /**
     * Extrahiert den Klassennamen ohne Namespace
     */
    private static function getShortClassName(string $class): string
    {
        $parts = explode('\\', $class);

        return end($parts);
    }
}
