<?php

declare(strict_types=1);

namespace Apexl\Io\Cache\Services;

use Apexl\Config\ConfigurationInterface;
use Apexl\Config\Exceptions\MissingConfigException;
use Apexl\Io\Cache\Collections\CacheStreamCollection;
use Apexl\Io\Cache\Exceptions\CacheException;
use Apexl\Io\Cache\Interfaces\CacheManagerInterface;
use Apexl\Io\Cache\Interfaces\CacheStreamInterface;
use Apexl\Io\HookManager;
use Exception;

final readonly class CacheManager implements CacheManagerInterface
{
    public const string HOOK__REGISTER_CACHE_STREAMS = 'cache.register_cache_streams';

    private string $defaultStream;
    private CacheStreamCollection $streams;

    /**
     * @throws MissingConfigException
     * @throws CacheException
     */
    public function __construct(
        private ConfigurationInterface $configuration,
        private CacheStreamRegistry $cacheStreamRegistry,
        private HookManager $hookManager,
    ) {
        $this->streams = $this->makeCacheStreamCollection();
        $this->initDefaultStream();
    }

    private function makeCacheStreamCollection(): CacheStreamCollection
    {
        $streams = new CacheStreamCollection();

        $this->hookManager->processHook(self::HOOK__REGISTER_CACHE_STREAMS, $streams);

        return $streams;
    }

    /**
     * @throws CacheException
     * @throws MissingConfigException
     */
    private function initDefaultStream(): void
    {
        if ($this->configuration->get('cache.enabled') !== true) {
            $this->defaultStream = 'blackhole';

            return;
        }


        $stream = $this->configuration->get('cache.default_stream');

        if (!$this->streams->has($stream)) {
            $this->streamNotFoundException($stream);
        }

        $this->defaultStream = $stream;

    }

    /**
     * @throws CacheException
     */
    private function streamNotFoundException($value): void
    {
        throw new CacheException(sprintf('Cache stream \'%s\' not found.', $value));
    }

    /**
     * @return CacheStreamInterface
     * @throws CacheException
     */
    public function default(): CacheStreamInterface
    {
        return $this->stream($this->defaultStream);
    }

    /**
     * @throws CacheException
     */
    public function stream(string $name): CacheStreamInterface
    {
        try {
            if ($this->configuration->get('cache.enabled') === false) {
                return $this->cacheStreamRegistry->blackHole();
            }
        } catch (Exception $e) {
            throw new CacheException($e->getMessage(), $e->getCode(), $e);
        }

        return $this->streams->get($name) ?? $this->streamNotFoundException($name);
    }

    public function clearAll(): void
    {
        $this->streams->keys()->each(function (string $key): bool {
            return $this->stream($key)->clear();
        });
    }
}
