<?php


namespace Shared\RightManagement\SQLFilter;


use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Shared\Authentication\Entity\RelUserSite;
use Shared\Authentication\Entity\User;
use Shared\RightManagement\Annotation\AdvancedRight;
use Shared\RightManagement\Entity\AbstractAdvancedRight;
use Shared\RightManagement\EventSubscriber\FilterConfigurator;
use Symfony\Component\HttpFoundation\Request;

class AdvancedRightFilter extends SQLFilter
{
    public const NAME = 'advanced_right';

    protected ?FilterConfigurator $configurator = null;
    protected ?EntityManagerInterface $entityManager = null;

    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
    {
        /** @var AdvancedRight $rightAnnotation */
        $rightAnnotation = $this->getListener()->getAnnotationReader()->getClassAnnotation($targetEntity->getReflectionClass(), 'Shared\\RightManagement\\Annotation\\AdvancedRight');
        if (null === $rightAnnotation) {
            return '';
        }
        /** @var User $connectedUser */
        $connectedUser = $this->getListener()->getSecurity()->getUser();

        // Does not apply filter when request is done with a worker
        if ($connectedUser === null) {
            return '';
        }

        $requestModification = $this->getListener()->getRequestStack()->getCurrentRequest()->getMethod() !== Request::METHOD_GET;

        $platform = $this->getEntityManager()->getConnection()->getDatabasePlatform();

        $advancedGroupRightClassMetadata = $this->getEntityManager()->getClassMetadata('Shared\RightManagement\Entity\AdvancedGroupRight');
        $advancedGroupRightTableName = $this->getListener()->getQuoteStrategy()->getTableName($advancedGroupRightClassMetadata, $platform);
        $groupIdColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('groupId', $advancedGroupRightClassMetadata, $platform);

        $userGroupClassMetadata = $this->getEntityManager()->getClassMetadata('Webapp\Core\Entity\UserGroup');
        $userGroupTableName = $this->getListener()->getQuoteStrategy()->getJoinTableName($userGroupClassMetadata->getAssociationMapping('users'), $userGroupClassMetadata, $platform);
        $userGroupGroupIdColumnName = $this->getListener()->getQuoteStrategy()->getJoinColumnName($userGroupClassMetadata->getAssociationMapping('users')['joinTable']['joinColumns'][0], $userGroupClassMetadata, $platform);
        $userGroupUserIdColumnName = $this->getListener()->getQuoteStrategy()->getJoinColumnName($userGroupClassMetadata->getAssociationMapping('users')['joinTable']['inverseJoinColumns'][0], $userGroupClassMetadata, $platform);

        $advancedUserRightClassMetadata = $this->getEntityManager()->getClassMetadata('Shared\RightManagement\Entity\AdvancedUserRight');
        $advancedUserRightTableName = $this->getListener()->getQuoteStrategy()->getTableName($advancedUserRightClassMetadata, $platform);
        $userIdColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('userId', $advancedUserRightClassMetadata, $platform);

        $objectIdColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('objectId', $advancedUserRightClassMetadata, $platform);
        $classIdentifierColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('classIdentifier', $advancedUserRightClassMetadata, $platform);
        $rightColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('right', $advancedUserRightClassMetadata, $platform);

        $relUserSiteClassMetadata = $this->getEntityManager()->getClassMetadata('Shared\Authentication\Entity\RelUserSite');
        $relUserSiteTableName = $this->getListener()->getQuoteStrategy()->getTableName($relUserSiteClassMetadata, $platform);
        $siteColumnName = $relUserSiteClassMetadata->getSingleAssociationJoinColumnName('site');
        $userColumnName = $relUserSiteClassMetadata->getSingleAssociationJoinColumnName('user');
        $roleColumnName = $this->getListener()->getQuoteStrategy()->getColumnName('role', $relUserSiteClassMetadata, $platform);


        $filterConstraintPart = [];

        if (isset($rightAnnotation->siteAttribute)) {
            $objectSiteColumn = $targetEntity->getSingleAssociationJoinColumnName($rightAnnotation->siteAttribute);

            $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS r WHERE r.%s = %s.%s AND r.%s = %s AND r.%s = \'%s\' )',
                $relUserSiteTableName, $siteColumnName, $targetTableAlias, $objectSiteColumn, $userColumnName, $connectedUser->getId(), $roleColumnName, RelUserSite::SITE_ADMIN);
        }

        if (isset($rightAnnotation->ownerField) && isset($rightAnnotation->classIdentifier)) {
            $ownerColumn = $targetEntity->getSingleAssociationJoinColumnName($rightAnnotation->ownerField);
            $filterConstraintPart[] = sprintf('%s.%s = %s',
                $targetTableAlias, $ownerColumn, $connectedUser->getId());
            if ($requestModification) {
                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS u WHERE %s.id = u.%s AND %s = u.%s AND \'%s\' = u.%s AND u.%s = \'%s\')',
                    $advancedUserRightTableName, $targetTableAlias, $objectIdColumnName, $connectedUser->getId(), $userIdColumnName, $rightAnnotation->classIdentifier, $classIdentifierColumnName, $rightColumnName, AbstractAdvancedRight::ADVANCED_RIGHT_EDIT);

                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS g INNER JOIN %s AS r ON g.%s = r.%s WHERE %s.id = g.%s AND %s = r.%s AND \'%s\' = g.%s AND g.%s = \'%s\')',
                    $advancedGroupRightTableName, $userGroupTableName, $groupIdColumnName, $userGroupGroupIdColumnName, $targetTableAlias, $objectIdColumnName, $connectedUser->getId(), $userGroupUserIdColumnName, $rightAnnotation->classIdentifier, $classIdentifierColumnName, $rightColumnName, AbstractAdvancedRight::ADVANCED_RIGHT_EDIT);
            } else {
                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS u WHERE %s.id = u.%s AND %s = u.%s AND \'%s\' = u.%s)',
                    $advancedUserRightTableName, $targetTableAlias, $objectIdColumnName, $connectedUser->getId(), $userIdColumnName, $rightAnnotation->classIdentifier, $classIdentifierColumnName);

                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS g INNER JOIN %s AS r ON g.%s = r.%s WHERE %s.id = g.%s AND %s = r.%s AND \'%s\' = g.%s)',
                    $advancedGroupRightTableName, $userGroupTableName, $groupIdColumnName, $userGroupGroupIdColumnName, $targetTableAlias, $objectIdColumnName, $connectedUser->getId(), $userGroupUserIdColumnName, $rightAnnotation->classIdentifier, $classIdentifierColumnName);
            }
        } elseif (isset($rightAnnotation->siteAttribute)) {
            $objectSiteColumn = $targetEntity->getSingleAssociationJoinColumnName($rightAnnotation->siteAttribute);

            $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS r WHERE r.%s = %s.%s AND r.%s = %s)',
                $relUserSiteTableName, $siteColumnName, $targetTableAlias, $objectSiteColumn, $userColumnName, $connectedUser->getId());
        }

        $addParentFilters = function (array &$filterConstraintPart, ClassMetadata $childEntity, string $field, string $originAssociationColumn, string $previousInnerJoin = '', int $count = 0) use ($roleColumnName, $userColumnName, $siteColumnName, $relUserSiteTableName, $rightColumnName, $requestModification, $targetTableAlias, &$addParentFilters, $userGroupUserIdColumnName, $classIdentifierColumnName, $userIdColumnName, $platform, $connectedUser, $userGroupGroupIdColumnName, $groupIdColumnName, $userGroupTableName, $advancedGroupRightTableName, $objectIdColumnName, $advancedUserRightTableName) {
            $parentTableAlias = 'p' . $count;

            $parentClassMetadata = $this->getEntityManager()->getClassMetadata($childEntity->getAssociationTargetClass($field));
            $parentRightAnnotation = $this->getListener()->getAnnotationReader()->getClassAnnotation($parentClassMetadata->getReflectionClass(), 'Shared\\RightManagement\\Annotation\\AdvancedRight');
            $parentClassMetadataTableName = $this->getListener()->getQuoteStrategy()->getTableName($parentClassMetadata, $platform);

            if (isset($parentRightAnnotation->siteAttribute)) {
                $objectSiteColumn = $parentClassMetadata->getSingleAssociationJoinColumnName($parentRightAnnotation->siteAttribute);

                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s as r INNER JOIN %s AS %s ON r.%s = %s.%s %s WHERE %s.%s = p0.id AND r.%s = %s AND r.%s = \'%s\')',
                    $relUserSiteTableName, $parentClassMetadataTableName, $parentTableAlias, $siteColumnName, $parentTableAlias, $objectSiteColumn, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $userColumnName, $connectedUser->getId(), $roleColumnName, RelUserSite::SITE_ADMIN);
            }

            if (isset($parentRightAnnotation->ownerField) && isset($parentRightAnnotation->classIdentifier)) {
                $ownerColumn = $parentClassMetadata->getSingleAssociationJoinColumnName($parentRightAnnotation->ownerField);

                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS %s %s WHERE %s.%s = p0.id AND %s.%s = %s)',
                    $parentClassMetadataTableName, $parentTableAlias, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $parentTableAlias, $ownerColumn, $connectedUser->getId());

                if ($requestModification) {
                    $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS u INNER JOIN %s AS %s ON %s.id = u.%s %s WHERE %s.%s = p0.id AND %s = u.%s AND \'%s\' = u.%s AND u.%s = \'%s\')',
                        $advancedUserRightTableName, $parentClassMetadataTableName, $parentTableAlias, $parentTableAlias, $objectIdColumnName, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $connectedUser->getId(), $userIdColumnName, $parentRightAnnotation->classIdentifier, $classIdentifierColumnName, $rightColumnName, AbstractAdvancedRight::ADVANCED_RIGHT_EDIT);

                    $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS g INNER JOIN %s AS %s ON %s.id = g.%s INNER JOIN %s AS r ON g.%s = r.%s %s WHERE %s.%s = p0.id AND %s = r.%s AND \'%s\' = g.%s AND g.%s = \'%s\')',
                        $advancedGroupRightTableName, $parentClassMetadataTableName, $parentTableAlias, $parentTableAlias, $objectIdColumnName, $userGroupTableName, $groupIdColumnName, $userGroupGroupIdColumnName, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $connectedUser->getId(), $userGroupUserIdColumnName, $parentRightAnnotation->classIdentifier, $classIdentifierColumnName, $rightColumnName, AbstractAdvancedRight::ADVANCED_RIGHT_EDIT);
                } else {
                    $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS u INNER JOIN %s AS %s ON %s.id = u.%s %s WHERE %s.%s = p0.id AND %s = u.%s AND \'%s\' = u.%s)',
                        $advancedUserRightTableName, $parentClassMetadataTableName, $parentTableAlias, $parentTableAlias, $objectIdColumnName, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $connectedUser->getId(), $userIdColumnName, $parentRightAnnotation->classIdentifier, $classIdentifierColumnName);

                    $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s AS g INNER JOIN %s AS %s ON %s.id = g.%s INNER JOIN %s AS r ON g.%s = r.%s %s WHERE %s.%s = p0.id AND %s = r.%s AND \'%s\' = g.%s)',
                        $advancedGroupRightTableName, $parentClassMetadataTableName, $parentTableAlias, $parentTableAlias, $objectIdColumnName, $userGroupTableName, $groupIdColumnName, $userGroupGroupIdColumnName, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $connectedUser->getId(), $userGroupUserIdColumnName, $parentRightAnnotation->classIdentifier, $classIdentifierColumnName);
                }

            } elseif (isset($parentRightAnnotation->siteAttribute)) {
                $objectSiteColumn = $parentClassMetadata->getSingleAssociationJoinColumnName($parentRightAnnotation->siteAttribute);

                $filterConstraintPart[] = sprintf('EXISTS ( SELECT 1 from %s as r INNER JOIN %s AS %s ON r.%s = %s.%s %s WHERE %s.%s = p0.id AND r.%s = %s)',
                    $relUserSiteTableName, $parentClassMetadataTableName, $parentTableAlias, $siteColumnName, $parentTableAlias, $objectSiteColumn, $previousInnerJoin, $targetTableAlias, $originAssociationColumn, $userColumnName, $connectedUser->getId());
            }

            foreach ($parentRightAnnotation->parentFields as $grandParentField) {
                $grandParentAssociationColumn = $this->getListener()->getQuoteStrategy()->getJoinColumnName($parentClassMetadata->getAssociationMapping($grandParentField)['joinColumns'][0], $parentClassMetadata, $platform);
                $addParentFilters($filterConstraintPart, $parentClassMetadata, $grandParentField, $originAssociationColumn, sprintf('INNER JOIN %s AS %s ON %s.id = %s.%s ', $parentClassMetadataTableName, $parentTableAlias, 'p' . ($count + 1), $parentTableAlias, $grandParentAssociationColumn) . $previousInnerJoin, $count + 1);
            }
        };

        if (isset($rightAnnotation->parentFields)) {
            foreach ($rightAnnotation->parentFields as $field) {
                $parentAssociationColumn = $this->getListener()->getQuoteStrategy()->getJoinColumnName($targetEntity->getAssociationMapping($field)['joinColumns'][0], $targetEntity, $platform);;
                $addParentFilters($filterConstraintPart, $targetEntity, $field, $parentAssociationColumn);
            }
        }

        return join(' OR ', $filterConstraintPart);
    }

    protected function getListener()
    {
        if (null === $this->configurator) {
            $em = $this->getEntityManager();
            $evm = $em->getEventManager();

            foreach ($evm->getListeners() as $listeners) {
                foreach ($listeners as $listener) {
                    if ($listener instanceof FilterConfigurator) {
                        $this->configurator = $listener;

                        break 2;
                    }
                }
            }

            if (null === $this->configurator) {
                throw new \RuntimeException('Listener "FilterConfigurator" was not added to the EventManager!');
            }
        }

        return $this->configurator;
    }

    /**
     * @return EntityManagerInterface
     */
    protected function getEntityManager()
    {
        if (null === $this->entityManager) {
            $refl = new \ReflectionProperty('Doctrine\ORM\Query\Filter\SQLFilter', 'em');
            $refl->setAccessible(true);
            $this->entityManager = $refl->getValue($this);
        }

        return $this->entityManager;
    }
}
