<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use Countable;
use IteratorAggregate;
use Traversable;

/**
 * Collection of cache items returned from multi-key cache operations
 * Provides convenient access to hits, misses, and individual items
 *
 * Backward compatibility: For single-key operations, provides direct access
 * to isHit and value properties of the first item
 */
final readonly class CacheResult implements Countable, IteratorAggregate
{
    /**
     * Backward compatibility: TRUE if at least one item is a hit
     */
    public readonly bool $isHit;

    /**
     * Backward compatibility: Value of the first item (for single-key operations)
     */
    public readonly mixed $value;

    /**
     * @param array<string, CacheItem> $items Keyed by cache key string
     */
    private function __construct(
        private array $items
    ) {
        // Initialize backward compatibility properties
        $firstKey = array_key_first($this->items);
        $firstItem = $firstKey !== null ? $this->items[$firstKey] : CacheItem::miss(CacheKey::fromString('_empty_'));

        $this->isHit = $firstItem->isHit;
        $this->value = $firstItem->value;
    }

    /**
     * Create from CacheItems using spread operator
     */
    public static function fromItems(CacheItem ...$items): self
    {
        // EMERGENCY: Ultra-strict limits to prevent memory exhaustion
        $itemCount = count($items);

        // Check memory before processing
        $memoryUsage = memory_get_usage(true);
        if ($memoryUsage > 400 * 1024 * 1024) { // >400MB
            error_log("EMERGENCY: CacheResult refused - memory usage: " . round($memoryUsage / 1024 / 1024, 2) . "MB");

            throw new \RuntimeException("EMERGENCY: Memory usage too high for CacheResult creation");
        }

        // Ultra-strict item limit
        if ($itemCount > 100) { // Reduced from 1000 to 100
            error_log("EMERGENCY: Too many cache items ($itemCount) - max 100 allowed");

            throw new \RuntimeException("EMERGENCY: Too many cache items ($itemCount) - max 100 allowed");
        }

        $indexed = [];
        $count = 0;
        foreach ($items as $item) {
            // EMERGENCY: Memory check during iteration
            if ($count % 10 === 0) {
                $currentMemory = memory_get_usage(true);
                if ($currentMemory > 450 * 1024 * 1024) { // >450MB
                    error_log("EMERGENCY: CacheResult iteration stopped at item $count - memory critical");

                    break;
                }
            }

            // EMERGENCY: Ultra-strict iteration limit
            if (++$count > 100) {
                error_log("EMERGENCY: CacheResult truncated at 100 items (was $itemCount)");

                break;
            }

            $indexed[$item->key->toString()] = $item;
        }

        return new self($indexed);
    }

    /**
     * Create from key-value pairs
     * @param array<CacheKey, mixed> $data
     */
    public static function fromData(array $data): self
    {
        $items = [];
        foreach ($data as $key => $value) {
            $keyString = $key->toString();
            $items[$keyString] = $value !== null
                ? CacheItem::hit($key, $value)
                : CacheItem::miss($key);
        }

        return new self($items);
    }

    /**
     * Create empty result
     */
    public static function empty(): self
    {
        return new self([]);
    }

    /**
     * Get cache item for specific key
     */
    public function getItem(CacheKey $key): CacheItem
    {
        $keyString = $key->toString();

        return $this->items[$keyString] ?? CacheItem::miss($key);
    }

    /**
     * Get all cache items
     * @return array<string, CacheItem> Keyed by cache key string
     */
    public function getItems(): array
    {
        return $this->items;
    }

    /**
     * Get only cache hits as new CacheResult
     */
    public function getHits(): self
    {
        $hits = array_filter($this->items, fn (CacheItem $item) => $item->isHit);

        return new self($hits);
    }

    /**
     * Get only cache misses as new CacheResult
     */
    public function getMisses(): self
    {
        $misses = array_filter($this->items, fn (CacheItem $item) => ! $item->isHit);

        return new self($misses);
    }

    /**
     * Check if specific key resulted in a cache hit
     */
    public function hasHit(CacheKey $key): bool
    {
        return $this->getItem($key)->isHit;
    }

    /**
     * Check if specific key resulted in a cache miss
     */
    public function hasMiss(CacheKey $key): bool
    {
        return ! $this->hasHit($key);
    }

    /**
     * Get hit ratio as percentage (0.0 to 1.0)
     */
    public function getHitRatio(): float
    {
        if (empty($this->items)) {
            return 0.0;
        }

        $hitCount = count($this->getHits()->items);

        return $hitCount / count($this->items);
    }

    /**
     * Get values for all cache hits
     * @return array<string, mixed> Keyed by cache key string
     */
    public function getHitValues(): array
    {
        $values = [];
        foreach ($this->getHits()->items as $keyString => $item) {
            $values[$keyString] = $item->value;
        }

        return $values;
    }

    /**
     * Get cache keys that resulted in hits
     */
    public function getHitKeys(): CacheKeyCollection
    {
        $keys = [];
        foreach ($this->getHits()->items as $item) {
            $keys[] = $item->key;
        }

        return CacheKeyCollection::fromKeys(...$keys);
    }

    /**
     * Get cache keys that resulted in misses
     */
    public function getMissKeys(): CacheKeyCollection
    {
        $keys = [];
        foreach ($this->getMisses()->items as $item) {
            $keys[] = $item->key;
        }

        return CacheKeyCollection::fromKeys(...$keys);
    }

    /**
     * Get all cache keys
     */
    public function getKeys(): CacheKeyCollection
    {
        $keys = [];
        foreach ($this->items as $item) {
            $keys[] = $item->key;
        }

        return CacheKeyCollection::fromKeys(...$keys);
    }

    /**
     * Check if result contains any items
     */
    public function isEmpty(): bool
    {
        return empty($this->items);
    }

    /**
     * Check if all requested keys were cache hits
     */
    public function isCompleteHit(): bool
    {
        if (empty($this->items)) {
            return false;
        }

        foreach ($this->items as $item) {
            if (! $item->isHit) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check if all requested keys were cache misses
     */
    public function isCompleteMiss(): bool
    {
        if (empty($this->items)) {
            return true;
        }

        foreach ($this->items as $item) {
            if ($item->isHit) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get value for specific key, or null if miss
     */
    public function getValue(CacheKey $key): mixed
    {
        $item = $this->getItem($key);

        return $item->isHit ? $item->value : null;
    }

    /**
     * Filter results by predicate, returns new CacheResult
     */
    public function filter(callable $predicate): self
    {
        $filtered = array_filter($this->items, $predicate);

        return new self($filtered);
    }

    /**
     * Map over cache items, returns new CacheResult
     */
    public function map(callable $mapper): self
    {
        $mapped = array_map($mapper, $this->items);

        return self::fromItems(...$mapped);
    }

    /**
     * Merge with another CacheResult
     */
    public function merge(self $other): self
    {
        return new self(array_merge($this->items, $other->items));
    }

    /**
     * Countable implementation
     */
    public function count(): int
    {
        return count($this->items);
    }

    /**
     * IteratorAggregate implementation
     * @return Traversable<string, CacheItem>
     */
    public function getIterator(): Traversable
    {
        foreach ($this->items as $keyString => $item) {
            yield $keyString => $item;
        }
    }

    /**
     * Convert to array for debugging
     * @return array<string, array{key: string, value: mixed, hit: bool}>
     */
    public function toArray(): array
    {
        $result = [];
        foreach ($this->items as $keyString => $item) {
            $result[$keyString] = [
                'key' => $item->key->toString(),
                'value' => $item->value,
                'hit' => $item->isHit,
            ];
        }

        return $result;
    }
}
