<?php

namespace AutoAdmin\Bundle\Cache\Service;

use AutoAdmin\Bundle\Cache\Configuration\ConfigManager;
use AutoAdmin\Bundle\Cache\Entity\CacheRule;
use AutoAdmin\Bundle\Core\Helper\DoctrineHelper;
use DateTime;
use Doctrine\Common\Cache\Psr6\CacheItem;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class CacheService
{
    private const CACHE_NAMESPACE = 'aa.cache';
    private const CACHE_LIFETIME = 3600 * 24;
    private const CACHE_RULES_KEY = 'cache_rules';
    private const CACHE_RESPONSE_PREFIX = 'cache_response_';

    private FilesystemAdapter $cache;
    private ManagerRegistry $doctrine;
    private ConfigManager $configManager;

    public function __construct(
        ConfigManager $configManager,
        ManagerRegistry $doctrine,
        string $projectDirectory
    ) {
        $this->cache = new FilesystemAdapter(
            self::CACHE_NAMESPACE,
            self::CACHE_LIFETIME,
            $projectDirectory . '/var/cache/autoadmin'
        );

        $this->doctrine = $doctrine;
        $this->configManager = $configManager;
    }

    public function onORMEvent(object $object): void
    {
        if (!$this->doClearCache($object)) {
            return;
        }

        $this->clearCachedResponse();
    }

    public function doClearCache(object $object): bool
    {
        if (!$this->configManager->cacheEnabled()) {
            return false;
        }

        if (!$this->configManager->clearOnFlush()) {
            return false;
        }

        if (in_array(DoctrineHelper::getClass($object), $this->configManager->getExcludedEntities())) {
            return false;
        }

        return true;
    }

    public function supportRequest(Request $request): bool
    {
        if (preg_match('/^\/admin/', $request->getRequestUri())) {
            return false;
        }

        if ($request->getMethod() !== 'GET') {
            return false;
        }

        if ($request->isXmlHttpRequest()) {
            return false;
        }

        return true;
    }

    public function regenerateCachedRules(): void
    {
        $this->cache->deleteItem(self::CACHE_RULES_KEY);
        $this->getCacheRules();
    }

    public function clearCachedResponse(): void
    {
        $this->cache->clear(self::CACHE_RESPONSE_PREFIX);
    }

    public function getCacheRules(): array
    {
        /** @var CacheItem $item */
        $item = $this->cache->getItem(self::CACHE_RULES_KEY);
        if (!$item->isHit()) {
            $cacheRulesItems = $this->doctrine->getRepository(CacheRule::class)->contentFindAll();
            $item->set($cacheRulesItems);
            $this->cache->save($item);
        }
        return $item->get();
    }

    public function isCachable(Request $request): bool
    {
        if (!$this->configManager->cacheEnabled()) {
            return false;
        }

        if (!$this->supportRequest($request)) {
            return false;
        }

        $canBeCached = false;
        foreach ($this->getCacheRules() as $rule) {
            $voteResult = $this->voteRule($rule, $request->getRequestUri());

            if ($voteResult === false) {
                $canBeCached = false;
                break;
            }

            if ($voteResult === true) {
                $canBeCached = true;
            }
        }

        return $canBeCached;
    }

    public function isCached(Request $request): bool
    {
        $cacheKey = $this->generateResponseCacheKey($request);

        $item = $this->cache->getItem($cacheKey);
        if (!$item->isHit()) {
            return false;
        }

        return true;
    }

    public function getCached(Request $request): ?Response
    {
        $cacheKey = $this->generateResponseCacheKey($request);

        $item = $this->cache->getItem($cacheKey);
        if (!$item->isHit()) {
            return null;
        }

        return $item->get();
    }

    public function setCached(Request $request, Response $response): void
    {
        $cacheKey = $this->generateResponseCacheKey($request);

        /** @var CacheItem $item */
        $item = $this->cache->getItem($cacheKey);
        $response->headers->set('cache-aa', (new DateTime())->format('Y-m-d H:i:s'));
        $item->set($response);

        $item->expiresAfter(3600 * 24);
        $this->cache->save($item);
    }

    private function voteRule(CacheRule $rule, string $uri): ?bool
    {
        if ($rule->isExactMatch()) {
            $matchPattern = $rule->getPattern() === $uri;
        } else {
            $matchPattern = (bool) preg_match('#' . $rule->getPattern() . '#', $uri);
        }

        if ($matchPattern && $rule->isExcluded()) {
            return false;
        }

        if ($matchPattern && !$rule->isExcluded()) {
            return true;
        }

        return null;
    }

    public function generateResponseCacheKey(Request $request): string
    {
        return
            self::CACHE_RESPONSE_PREFIX
            . $request->getHttpHost()
            . urlencode($request->getRequestUri())
            ;
    }
}
