<?php

namespace Lc\SovBundle\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Knp\Component\Pager\PaginatorInterface;
use Lc\SovBundle\Doctrine\EntityInterface;
use Lc\SovBundle\Model\User\UserInterface;

abstract class AbstractRepositoryQuery implements RepositoryQueryInterface
{

    protected ServiceEntityRepository $repository;
    protected QueryBuilder $query;
    protected PaginatorInterface $paginator;
    protected string $id;

    public function __construct(ServiceEntityRepository $repository, string $id, PaginatorInterface $paginator = null)
    {
        $this->repository = $repository;
        $this->query = $repository->createQueryBuilder($id);
        $this->paginator = $paginator;
        $this->id = $id;
    }

    public function __call(string $name, $params): self
    {
        foreach ($params as $key => $value) {
            $this->populateDqlId($params[$key]);
        }

        call_user_func_array([$this->query, $name], $params);

        return $this;
    }

    public function create()
    {
        $class = get_called_class();

        return new $class($this->repository, $this->paginator);
    }

    public function call(callable $fn): self
    {
        $fn($this->query, $this);

        return $this;
    }

    public function count()
    {
        return $this->query->getQuery()
                ->getSingleScalarResult();
    }

    public function findOne()
    {
        return $this->query->getQuery()
                ->setMaxResults(1)
                ->getOneOrNullResult();
    }

    public function find(): array
    {
        return $this->query->getQuery()->getResult();
    }

    public function limit(int $maxResults):self
    {
        $this->query->setMaxResults($maxResults);
        return $this;
    }

    public function paginate(int $page = 1, int $limit = 20)
    {
        return $this->paginator->paginate($this->query->getQuery(), $page, $limit);
    }

    public function getRepository(): ServiceEntityRepository
    {
        return $this->repository;
    }

    public function getQueryBuilder(): QueryBuilder
    {
        return $this->query;
    }

    protected function populateDqlId(&$data)
    {
        if (is_string($data)) {
            $words = explode(' ', $data);

            foreach ($words as $k => $v) {
                if (isset($v[0]) && '.' === $v[0]) {
                    $words[$k] = $this->id . $v;
                }
            }

            $data = implode(' ', $words);
        } elseif (is_array($data)) {
            foreach ($data as $k => $v) {
                $this->populateDqlId($data[$k]);
            }
        }

        return $data;
    }

    public function groupBy(string $field): self
    {
        if (strpos($field,  '.')!==false) {
            $this->addGroupBy($field) ;

        } else {
            $this->addGroupBy('.'.$field) ;
        }
        return $this;
    }
/*
    public function addGroupBy(string $field): self
    {
        if (strpos($field,  '.')!==false) {
            $this->query->addGroupBy($field) ;

        } else {
            $this->query->addGroupBy('.'.$field) ;
        }
        return $this;
    }*/

    public function orderBy(string $field, string $sort = 'ASC'): self
    {
        if (strpos($field,  '.')!==false) {
            return $this->addOrderBy($field, $sort);
        } else {
            return $this->addOrderBy('.' . $field, $sort);
        }
    }

    public function setOrderBy(string $field, string $sort = 'ASC'): self
    {
        $this->resetDQLParts(['orderBy']);
        return $this->orderBy($field, $sort);
    }

    public function filterById(int $id):self
    {
        return $this
                ->andWhere('.id = :id')
                ->setParameter('id', $id);
    }

    public function filterByCreatedBy(UserInterface $user):self
    {
        return $this
                ->andWhere('.createdBy = :user')
                ->setParameter('user', $user);
    }

    public function filterByUpdatedBy(UserInterface $user):self
    {
        return $this
                ->andWhere('.updatedBy = :user')
                ->setParameter('user', $user);
    }

    public function andWhereEqual($field, $value)
    {
        return $this->andWhere('.'.$field.' = :'.$field)->setParameter($field, $value);
    }

    public function filterByOldUrl(string $oldUrl): self
    {
        return $this->andWhere('.oldUrls LIKE :oldUrl')->setParameter('oldUrl', '%'.$oldUrl.'%');
    }

    public function resetRelationsJoin(): void
    {

    }

    /*
     *  DEVALIAS
     */
    public function filterByDevAlias(string $devAlias): self
    {
        return $this
                ->andWhere('.devAlias = :devAlias')
                ->setParameter('devAlias', $devAlias);
    }

    /*
     *  SLUG
     */
    public function filterBySlug(string $slug):self
    {
        return $this
                ->andWhere('.slug = :slug')
                ->setParameter('slug', $slug);
    }

    /*
     *  TREE
     */
    public function filterIsParent():self
    {
        return $this->andWhere('.parent IS NULL');
    }

    public function filterIsChildren():self
    {
        return $this->andWhere('.parent IS NOT NULL');
    }

    public function filterByParent(EntityInterface $parent = null):self
    {
        return $this->andWhere('.parent = :parent')->setParameter('parent', $parent);
    }

    /*
     *  STATUS
     */
    public function filterIsOffline():self
    {
        return  $this->andWhereStatus($this->id, 0);
    }

    public function filterIsOnline():self
    {
        return  $this->andWhereStatus($this->id, 1);
    }

    public function filterIsDeleted():self
    {
        return  $this->andWhereStatus($this->id, -1);
    }

    public function filterIsOnlineAndOffline():self
    {
        return  $this->andWhere('.status >= 0');
    }

    /*
     * POSITION
     */

    public function filterByPositionBiggerThan(int $position)
    {
        return $this->andWhere('.position > :position')->setParameter('position', $position);
    }

    public function filterByPositionSmallerThan(int $position)
    {
        return $this->andWhere('.position < :position')->setParameter('position', $position);
    }

    public function enableCache($lifetime, $idCache)
    {
        return $this->getQueryBuilder()->getQuery()
            ->useQueryCache(true)
            ->setQueryCacheLifetime($lifetime)
            ->enableResultCache($lifetime, $idCache);
    }

}

