<?php declare(strict_types=1);

namespace Newland\NeosCommon\ConfigurationValidation;

use Neos\ContentRepository\Domain\Model\NodeData;
use Neos\ContentRepository\Domain\Repository\NodeDataRepository;
use Neos\Error\Messages\Error;
use Neos\Error\Messages\Result;
use Neos\Flow\Annotations as Flow;
use function Safe\preg_match;

class ConfigurationValidationHelper
{
    protected const UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';

    /**
     * @var array
     * @Flow\InjectConfiguration(type="Policy")
     */
    protected $policies;

    /**
     * @var NodeDataRepository
     * @Flow\Inject()
     */
    protected $nodeDataRepository;

    public function getAdditionalValidationResults(): Result
    {
        return $this->validatePrivilegeTargetsForExpensiveOperations();
    }

    private function validatePrivilegeTargetsForExpensiveOperations(): Result
    {
        $result = new Result();
        foreach ($this->allPrivilegeTargets() as $key => $target) {
            $matcher = $target['matcher'] ?? null;

            $descendantRegex = sprintf('/^isDescendantNodeOf\([\'"](%s)[\'"]\)/', static::UUID_REGEX);
            if (preg_match($descendantRegex, $matcher, $matches)) {
                $result->addError(new Error($this->errorMessage($key, $matches[1])));
            }
        }

        return $result;
    }

    private function errorMessage(string $path, string $uuid): string
    {
        $message = sprintf('Policy.%s -> very expensive `isDescendantNodeOf("%s")` matcher. ', $path, $uuid);

        /** @var NodeData|null $node */
        $node = $this->nodeDataRepository->findByNodeIdentifier($uuid)->getFirst();

        if ($node === null) {
            $message .= 'No node with this id found anyways - is this matcher still up to date?';
        } else {
            $message .= sprintf('Use isDescendantNodeOf("%s") instead', $node->getPath());
        }

        return $message;
    }

    private function allPrivilegeTargets(): array
    {
        $targets = [];
        foreach ($this->policies['privilegeTargets'] ?? [] as $privilege => $groups) {
            foreach ($groups as $group => $target) {
                $key = sprintf('privilegeTargets.%s.%s', $privilege, $group);
                $targets[$key] = $target;
            }
        }
        return $targets;
    }
}
