<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use App\Framework\Cache\Contracts\DriverAccessible;
use App\Framework\Core\ValueObjects\Duration;
use ReflectionException;
use ReflectionMethod;

final readonly class ServiceCacheDecorator implements DriverAccessible
{
    public function __construct(
        private object $service,
        private Cache  $cache
    ) {
    }

    /**
     * @throws ReflectionException
     */
    public function __call(string $name, array $args): mixed
    {
        $method = new ReflectionMethod($this->service, $name);
        $attrs = $method->getAttributes(Cacheable::class);

        if ($attrs) {
            $attr = $attrs[0]->newInstance();
            $keyString = $attr->key ?? $method->getName() . ':' . md5(serialize($args));

            // Replace placeholders in custom key templates
            if ($attr->key !== null) {
                $keyString = $this->replacePlaceholders($keyString, $method, $args);
            }

            $cacheKey = CacheKey::fromString($keyString);
            $ttl = Duration::fromSeconds($attr->ttl ?? 3600);

            $cacheItem = $this->cache->remember($cacheKey, fn () => $method->invokeArgs($this->service, $args), $ttl);
            return $cacheItem->value;
        }

        return $method->invokeArgs($this->service, $args);
    }

    /**
     * Replace placeholders in cache key template with actual argument values
     */
    private function replacePlaceholders(string $template, ReflectionMethod $method, array $args): string
    {
        $parameters = $method->getParameters();
        $result = $template;

        foreach ($parameters as $index => $parameter) {
            $paramName = $parameter->getName();
            $paramValue = $args[$index] ?? null;

            if ($paramValue !== null) {
                // Convert value to string for cache key
                $stringValue = is_scalar($paramValue) ? (string) $paramValue : md5(serialize($paramValue));
                $result = str_replace('{' . $paramName . '}', $stringValue, $result);
            }
        }

        return $result;
    }

    /**
     * Get the underlying cache driver
     */
    public function getDriver(): ?CacheDriver
    {
        if ($this->cache instanceof DriverAccessible) {
            return $this->cache->getDriver();
        }

        if ($this->cache instanceof CacheDriver) {
            return $this->cache;
        }

        return null;
    }

    /**
     * Check if the underlying driver supports a specific interface
     */
    public function driverSupports(string $interface): bool
    {
        $driver = $this->getDriver();

        return $driver !== null && $driver instanceof $interface;
    }

    /**
     * Get the wrapped cache instance
     */
    public function getCache(): Cache
    {
        return $this->cache;
    }
}
