<?php

namespace AutoAdmin\Bundle\Admin\Finder;

use AutoAdmin\Bundle\Admin\Model\EntityFilterModel;
use AutoAdmin\Bundle\Content\Repository\AbstractContentRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use ReflectionClass;

class EntityFinder
{
    const FIELDS_TO_SEARCH = [
        'string',
        'text',
        'integer',
    ];

    private EntityManagerInterface $manager;

    public function __construct(EntityManagerInterface $manager)
    {
        $this->manager = $manager;
    }

    /**
     * @throws NonUniqueResultException
     * @throws NoResultException
     */
    public function find(EntityFilterModel $filter, array $entityConfig, bool $counterMode = false)
    {
        $className = $entityConfig['class'];
        $repo = $this->manager->getRepository($className);
        $repoRefl = new ReflectionClass($repo);
        $alias = '_';

        if ($repoRefl->isSubclassOf(AbstractContentRepository::class)) {
            $qb = $repo->createContentQueryBuilder($alias, null, ['disable_locale_filter' => true]);
        } else {
            $qb = $repo->createQueryBuilder($alias);
        }

        foreach ($filter->getQbPrepareCallbacks() as $qbPrepareCallback) {
            call_user_func_array($qbPrepareCallback, [$qb]);
        }

        // todo, rework qb to give access to other sites (remove site_id "WHERE" condition) entities if user is allowed

        if ($counterMode) {
            $qb->select('COUNT(DISTINCT ' . $alias . ')');
        }

        if ($globalSearchTerm = $filter->getGlobalSearch()) {
            $orX = $qb->expr()->orX();

            foreach ($this->addGlobalSearch($qb, $entityConfig, $globalSearchTerm) as $expression) {
                $orX->add($expression);
            }

            $qb->andWhere($orX);
            $qb->setParameter('search', '%' . $globalSearchTerm . '%');
        }

        foreach ($filter->getFieldsOrders() as $key => $value) {
            $qb->addOrderBy($key, $value);
        }

        // default sorting if none is defined will be DESC id
        if (empty($qb->getDQLPart('orderBy'))) {
            $qb->addOrderBy($alias . '.id', 'DESC');
        }

        foreach ($filter->getFieldsSearch() as $key => $value) {
            if (empty($value)) {
                continue;
            }

            $keyAlias = $alias;
            $key = str_replace('_.', '', $key);
            if (substr($key, 0, 4) === '_tr.') {
                $keyAlias = '_tr';
                $key = str_replace('_tr.', '', $key);
            }

            if (preg_match('/(.*)_empty$/', $key, $matches)) {
                // Find by null or empty
                $qb->andWhere($keyAlias . '.' . $matches[1] . ' IS NULL OR ' . $keyAlias . '.' . $matches[1] . ' = \'\'');
                continue;
            }

            $qb->andWhere($keyAlias . '.' . $key . ' LIKE :p_' . $key . '_search');
            $qb->setParameter('p_' . $key . '_search', '%' . $value . '%');
        }

        foreach ($filter->getQbAlterateCallbacks() as $qbAlterateCallback) {
            call_user_func_array($qbAlterateCallback, [$qb]);
        }

        if ($counterMode) {
            return $qb->getQuery()->getSingleScalarResult();
        }

        $qb->groupBy($alias . '.id');

        $qb->setMaxResults($filter->getMaxItems());
        $qb->setFirstResult(($filter->getCurrentPage() - 1) * $filter->getMaxItems());

        return $qb->getQuery()->getResult();
    }

    private function addGlobalSearch(QueryBuilder $qb, array $classConfig, string $searchGlobals): array
    {
        $classMetadatas = $qb->getEntityManager()->getClassMetadata($classConfig['class']);

        $expressions = [];
        foreach ($classMetadatas->getFieldNames() as $fieldName) {
            if (in_array($classMetadatas->getTypeOfField($fieldName), self::FIELDS_TO_SEARCH)) {
                $expressions[] = $qb->expr()->like('_.' . $fieldName, ':search');
            }
        }

        if ($classConfig['translation_class']) {
            $classMetadatas = $qb->getEntityManager()->getClassMetadata($classConfig['translation_class']);

            $expressions = [];
            foreach ($classMetadatas->getFieldNames() as $fieldName) {
                if (in_array($classMetadatas->getTypeOfField($fieldName), self::FIELDS_TO_SEARCH)) {
                    $expressions[] = $qb->expr()->like('_tr.' . $fieldName, ':search');
                }
            }
        }

        return $expressions;
    }
}
