<?php

namespace NoahVet\Reef\Plugin\Cache;

use Http\Discovery\Psr17FactoryDiscovery;
use Http\Promise\Promise;
use NoahVet\Reef\Domain\Http\CacheHeaderParser;
use NoahVet\Reef\Plugin\Cache\Model\CacheableResponse;
use NoahVet\Reef\Plugin\Cache\Model\Factory\CacheableResponseFactory;
use NoahVet\Reef\Plugin\Cache\Promise\CacheDeferredBuilder;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class HttpResponseCache implements HttpResponseCacheInterface
{
    /**
     * 512 ko max.
     */
    public const MAX_RESPONSE_CACHE_SIZE = 524288;

    public function __construct(
        protected readonly CacheItemPoolInterface $cache,
        protected readonly CacheableResponseFactory $cacheableResponseFactory,
    ) {
    }

    #[\Override]
    public function cacheResponse(RequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (
            'get' !== \strtolower($request->getMethod())
            || ($response->getBody()->getSize() ?? \PHP_INT_MAX) > self::MAX_RESPONSE_CACHE_SIZE
        ) {
            return $response;
        }

        $expiresAt = CacheHeaderParser::getExpirationDate($response);

        // No expiration for cache, just pass the response
        if (!$expiresAt) {
            return $response;
        }

        $cacheableResponse = $this->cacheableResponseFactory->create($response);

        $cacheItem = $this->getCacheItem($request);

        $cacheItem->expiresAt($expiresAt);
        $cacheItem->set($cacheableResponse);

        if (!$this->cache->save($cacheItem)) {
            return $response;
        }

        $cacheableResponse->getBody()->rewind();

        return $this->createResponse($cacheableResponse);
    }

    #[\Override]
    public function cacheResponsePromise(RequestInterface $request, Promise $responsePromise): Promise
    {
        return (new CacheDeferredBuilder($responsePromise, $request, $this))->getDeferred();
    }

    #[\Override]
    public function getCachedResponse(RequestInterface $request): ?ResponseInterface
    {
        if ('get' !== \strtolower($request->getMethod())) {
            return null;
        }

        $cacheItem = $this->getCacheItem($request);
        if ($cacheItem->isHit()) {
            $response = $cacheItem->get();

            if ($response instanceof CacheableResponse) {
                $response->getBody()->rewind();

                return $this->createResponse($response);
            }
        }

        return null;
    }

    protected function createResponse(CacheableResponse $response): ResponseInterface
    {
        $retResponse = Psr17FactoryDiscovery::findResponseFactory()->createResponse(
            $response->getStatusCode(),
            $response->getReasonPhrase(),
        );

        $retResponse = $retResponse
            ->withBody($response->getBody())
            ->withProtocolVersion($response->getProtocolVersion())
        ;

        foreach ($response->getHeaders() as $header => $values) {
            $retResponse = $retResponse->withHeader($header, $values);
        }

        return $retResponse;
    }

    protected function computeCacheKey(RequestInterface $request): string
    {
        // Gather all information for cache hit
        $fullCacheKey = $request->getMethod().'|'.$request->getUri()->__toString();

        if ($request->hasHeader('Authorization')) {
            $fullCacheKey .= '|'.\implode(',', $request->getHeader('Authorization'));
        }

        if ($request->hasHeader('.X-Reef-Client-ID') && $request->hasHeader('X-Reef-Client-HMAC')) {
            $fullCacheKey .= '|'.$request->getHeader('X-Reef-Client-ID')[0].'-'.$request->getHeader('X-Reef-Client-HMAC')[0];
        }

        // Generate a unique cache key with fixed size
        return 'http_cache_'.\hash('xxh128', $fullCacheKey);
    }

    protected function getCacheItem(RequestInterface $request): CacheItemInterface
    {
        $cacheKey = $this->computeCacheKey($request);

        return $this->cache->getItem($cacheKey);
    }
}
