<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use InvalidArgumentException;

/**
 * Cache pattern identifier for wildcard-based operations
 * Supports patterns like "user:*", "cache.*.data", etc.
 */
final readonly class CachePattern implements CacheIdentifier
{
    private const int MAX_PATTERN_LENGTH = 150;
    private const string PATTERN_MARKER = 'pattern:';

    private function __construct(
        private string $pattern,
        private string $compiledRegex
    ) {
        $this->validate($pattern);
    }

    /**
     * Create cache pattern from wildcard string
     *
     * Supports:
     * - user:* (matches user:123, user:456, etc.)
     * - cache.*.data (matches cache.sessions.data, cache.users.data)
     * - temp:** (matches temp:anything:nested:deeply)
     */
    public static function fromWildcard(string $pattern): self
    {
        $regex = self::compilePattern($pattern);

        return new self($pattern, $regex);
    }

    /**
     * Create pattern for all keys with prefix
     */
    public static function withPrefix(string $prefix): self
    {
        return self::fromWildcard($prefix . '*');
    }

    /**
     * Create pattern for all user-related keys
     */
    public static function forUser(string|int $userId): self
    {
        return self::fromWildcard("user:{$userId}:*");
    }

    /**
     * Create pattern for all session keys
     */
    public static function forSessions(): self
    {
        return self::fromWildcard('session:*');
    }

    /**
     * Create pattern for temporary keys
     */
    public static function forTemporary(): self
    {
        return self::fromWildcard('temp:**');
    }

    /**
     * Create pattern for namespace
     */
    public static function forNamespace(string $namespace): self
    {
        return self::fromWildcard("{$namespace}:**");
    }

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

    public function getType(): CacheIdentifierType
    {
        return CacheIdentifierType::PATTERN;
    }

    public function equals(CacheIdentifier $other): bool
    {
        return $other instanceof self && $this->pattern === $other->pattern;
    }

    public function matchesKey(CacheKey $key): bool
    {
        return preg_match($this->compiledRegex, $key->toString()) === 1;
    }

    public function getNormalizedString(): string
    {
        return self::PATTERN_MARKER . $this->pattern;
    }

    /**
     * Get the original wildcard pattern
     */
    public function getPattern(): string
    {
        return $this->pattern;
    }

    /**
     * Get compiled regex pattern
     */
    public function getCompiledRegex(): string
    {
        return $this->compiledRegex;
    }

    /**
     * Check if pattern is simple prefix (ends with single *)
     */
    public function isSimplePrefix(): bool
    {
        return str_ends_with($this->pattern, '*') &&
               substr_count($this->pattern, '*') === 1 &&
               ! str_contains($this->pattern, '**');
    }

    /**
     * Get prefix part for simple prefix patterns
     */
    public function getPrefix(): ?string
    {
        if (! $this->isSimplePrefix()) {
            return null;
        }

        return substr($this->pattern, 0, -1);
    }

    /**
     * Check if pattern matches deep nesting (**)
     */
    public function isDeepPattern(): bool
    {
        return str_contains($this->pattern, '**');
    }

    /**
     * Estimate selectivity (0.0 = matches everything, 1.0 = very specific)
     */
    public function getSelectivity(): float
    {
        $wildcardCount = substr_count($this->pattern, '*');
        $deepCount = substr_count($this->pattern, '**');
        $length = strlen($this->pattern);

        // More specific patterns have higher selectivity
        $specificity = $length / max(1, $wildcardCount * 5 + $deepCount * 10);

        return min(1.0, $specificity / 20); // Normalize to 0-1 range
    }

    /**
     * Compile wildcard pattern to regex
     */
    private static function compilePattern(string $pattern): string
    {
        // Escape special regex characters except * and **
        $escaped = preg_quote($pattern, '/');

        // Replace escaped wildcards back
        $escaped = str_replace('\\*\\*', '__DEEP_WILDCARD__', $escaped);
        $escaped = str_replace('\\*', '__WILDCARD__', $escaped);

        // Convert to regex
        $regex = str_replace('__DEEP_WILDCARD__', '.*', $escaped);
        $regex = str_replace('__WILDCARD__', '[^:]*', $regex);

        return '/^' . $regex . '$/';
    }

    /**
     * Validate the pattern
     */
    private function validate(string $pattern): void
    {
        if (empty($pattern)) {
            throw new InvalidArgumentException('Cache pattern cannot be empty');
        }

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

        // Check for invalid characters
        if (preg_match('/[\s\n\r\t\0\x0B]/', $pattern)) {
            throw new InvalidArgumentException('Cache pattern contains invalid characters');
        }

        // Validate wildcard usage
        if (str_contains($pattern, '***')) {
            throw new InvalidArgumentException('Cache pattern cannot contain more than two consecutive wildcards');
        }
    }
}
