<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use App\Framework\Cache\Events\CacheClear;
use App\Framework\Cache\Events\CacheDelete;
use App\Framework\Cache\Events\CacheHit;
use App\Framework\Cache\Events\CacheMiss;
use App\Framework\Cache\Events\CacheSet;
use App\Framework\EventBus\EventBus;
use App\Framework\Core\ValueObjects\Duration;

/**
 * Cache-Decorator für Event-Dispatching
 *
 * Dieser Decorator feuert Events für alle Cache-Operationen,
 * was eine lose gekoppelte Überwachung und Reaktion ermöglicht.
 */
final readonly class EventCacheDecorator implements Cache
{
    public function __construct(
        private Cache $innerCache,
        private EventBus $eventBus
    ) {
    }

    public function get(CacheIdentifier ...$identifiers): CacheResult
    {
        $result = $this->innerCache->get(...$identifiers);

        foreach ($result->getItems() as $item) {
            if ($item->isHit) {
                $valueSize = $this->calculateValueSize($item->value);
                $this->eventBus->dispatch(CacheHit::create($item->key, $item->value, $valueSize));
            } else {
                $this->eventBus->dispatch(CacheMiss::create($item->key));
            }
        }

        return $result;
    }

    public function set(CacheItem ...$items): bool
    {
        $result = $this->innerCache->set(...$items);

        foreach ($items as $item) {
            $valueSize = $this->calculateValueSize($item->value);
            $this->eventBus->dispatch(CacheSet::create($item->key, $item->value, $item->ttl, $result, $valueSize));
        }

        return $result;
    }

    public function has(CacheIdentifier ...$identifiers): array
    {
        return $this->innerCache->has(...$identifiers);
    }

    public function forget(CacheIdentifier ...$identifiers): bool
    {
        $result = $this->innerCache->forget(...$identifiers);

        foreach ($identifiers as $identifier) {
            if ($identifier instanceof CacheKey) {
                $this->eventBus->dispatch(CacheDelete::create($identifier, $result));
            }
        }

        return $result;
    }

    public function clear(): bool
    {
        $result = $this->innerCache->clear();

        $this->eventBus->dispatch(CacheClear::create($result));

        return $result;
    }

    public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
    {
        // Check if already cached
        $existingResult = $this->innerCache->get($key);
        $existing = $existingResult->getItem($key);

        if ($existing->isHit) {
            $valueSize = $this->calculateValueSize($existing->value);
            $this->eventBus->dispatch(CacheHit::create($key, $existing->value, $valueSize));

            return $existing;
        }

        // Cache miss - execute callback
        $this->eventBus->dispatch(CacheMiss::create($key));

        $result = $this->innerCache->remember($key, $callback, $ttl);

        if (! $result->isHit) {
            $valueSize = $this->calculateValueSize($result->value);
            $this->eventBus->dispatch(CacheSet::create($key, $result->value, $ttl, true, $valueSize));
        }

        return $result;
    }

    /**
     * Calculates the approximate size of a value
     */
    private function calculateValueSize(mixed $value): int
    {
        if (is_string($value)) {
            return strlen($value);
        }

        if (is_int($value) || is_float($value)) {
            return 8;
        }

        if (is_bool($value)) {
            return 1;
        }

        if ($value === null) {
            return 0;
        }

        try {
            return strlen(serialize($value));
        } catch (\Throwable) {
            return 0;
        }
    }
}
