<?php

declare(strict_types=1);

namespace App\Framework\CircuitBreaker\Registry;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;

/**
 * Cache-based implementation of ServiceRegistry
 */
final class CacheBasedServiceRegistry implements ServiceRegistry
{
    private const string REGISTRY_PREFIX = 'circuit_breaker_registry:';
    private const string NAMESPACE_KEY = 'namespaces';
    private const string SERVICES_SUFFIX = ':services';

    private Duration $registryTtl;

    public function __construct(
        private readonly Cache $cache,
        ?Duration $registryTtl = null,
    ) {
        $this->registryTtl = $registryTtl ?? Duration::fromHours(24);
    }

    public function discoverServices(string $namespace): array
    {
        $servicesKey = $this->getServicesKey($namespace);
        $cacheItem = $this->cache->get($servicesKey);

        if (! $cacheItem->isHit) {
            return [];
        }

        $services = $cacheItem->value;

        return is_array($services) ? $services : [];
    }

    public function registerService(string $service, string $namespace): void
    {
        // Register namespace
        $this->registerNamespace($namespace);

        // Add service to namespace
        $servicesKey = $this->getServicesKey($namespace);
        $services = $this->discoverServices($namespace);

        if (! in_array($service, $services, true)) {
            $services[] = $service;
            $this->cache->set($servicesKey, $services, $this->registryTtl);
        }
    }

    public function unregisterService(string $service, string $namespace): void
    {
        $servicesKey = $this->getServicesKey($namespace);
        $services = $this->discoverServices($namespace);

        $filteredServices = array_values(array_filter(
            $services,
            fn (string $s) => $s !== $service
        ));

        if (empty($filteredServices)) {
            $this->cache->forget($servicesKey);
            $this->unregisterNamespaceIfEmpty($namespace);
        } else {
            $this->cache->set($servicesKey, $filteredServices, $this->registryTtl);
        }
    }

    public function isServiceRegistered(string $service, string $namespace): bool
    {
        $services = $this->discoverServices($namespace);

        return in_array($service, $services, true);
    }

    public function getNamespaces(): array
    {
        $namespacesKey = $this->getNamespacesKey();
        $cacheItem = $this->cache->get($namespacesKey);

        if (! $cacheItem->isHit) {
            return [];
        }

        $namespaces = $cacheItem->value;

        return is_array($namespaces) ? $namespaces : [];
    }

    public function getAllServices(): array
    {
        $result = [];
        $namespaces = $this->getNamespaces();

        foreach ($namespaces as $namespace) {
            $services = $this->discoverServices($namespace);
            $result[$namespace] = $services;
        }

        return $result;
    }

    public function clearNamespace(string $namespace): void
    {
        $servicesKey = $this->getServicesKey($namespace);
        $this->cache->forget($servicesKey);
        $this->unregisterNamespace($namespace);
    }

    public function clearAll(): void
    {
        $namespaces = $this->getNamespaces();

        foreach ($namespaces as $namespace) {
            $this->clearNamespace($namespace);
        }

        $namespacesKey = $this->getNamespacesKey();
        $this->cache->forget($namespacesKey);
    }

    private function registerNamespace(string $namespace): void
    {
        $namespacesKey = $this->getNamespacesKey();
        $namespaces = $this->getNamespaces();

        if (! in_array($namespace, $namespaces, true)) {
            $namespaces[] = $namespace;
            $this->cache->set($namespacesKey, $namespaces, $this->registryTtl);
        }
    }

    private function unregisterNamespace(string $namespace): void
    {
        $namespacesKey = $this->getNamespacesKey();
        $namespaces = $this->getNamespaces();

        $filteredNamespaces = array_values(array_filter(
            $namespaces,
            fn (string $ns) => $ns !== $namespace
        ));

        if (empty($filteredNamespaces)) {
            $this->cache->forget($namespacesKey);
        } else {
            $this->cache->set($namespacesKey, $filteredNamespaces, $this->registryTtl);
        }
    }

    private function unregisterNamespaceIfEmpty(string $namespace): void
    {
        $services = $this->discoverServices($namespace);
        if (empty($services)) {
            $this->unregisterNamespace($namespace);
        }
    }

    private function getNamespacesKey(): CacheKey
    {
        return CacheKey::fromString(self::REGISTRY_PREFIX . self::NAMESPACE_KEY);
    }

    private function getServicesKey(string $namespace): CacheKey
    {
        return CacheKey::fromString(self::REGISTRY_PREFIX . $namespace . self::SERVICES_SUFFIX);
    }
}
