<?php

declare(strict_types=1);

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\CoreBundle\Shared\Doctrine\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Knp\Component\Pager\Pagination\PaginationInterface;
use Knp\Component\Pager\PaginatorInterface;
use Serenity\CoreBundle\Search\SearchInterface;
use Serenity\CoreBundle\Shared\Feature\DisabledInterface;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\Service\Attribute\Required;

/**
 * @template TEntity of object
 *
 * @extends ServiceEntityRepository<TEntity>
 *
 * @implements BaseRepositoryInterface<TEntity>
 */
abstract class BaseRepository extends ServiceEntityRepository implements BaseRepositoryInterface
{
    private \ReflectionClass $entity;
    private PaginatorInterface $paginator;

    /**
     * @param class-string<TEntity> $entityClass
     *
     * @throws \ReflectionException
     */
    public function __construct(ManagerRegistry $registry, string $entityClass, private readonly string $alias = 'o')
    {
        parent::__construct($registry, $entityClass);
        $this->entity = new \ReflectionClass($entityClass);
    }

    public function save(object $object): void
    {
        $this->getEntityManager()->persist($object);
        $this->getEntityManager()->flush();
    }

    public function remove(object $object): void
    {
        $this->getEntityManager()->remove($object);
    }

    public function delete(object $object): void
    {
        $this->getEntityManager()->remove($object);
        $this->getEntityManager()->flush();
    }

    public function persist(object $object): void
    {
        $this->getEntityManager()->persist($object);
    }

    public function flush(): void
    {
        $this->getEntityManager()->flush();
    }

    /**
     * @return list<TEntity>
     */
    public function findByPositionOrder(): array
    {
        $qb = $this->createQueryBuilder('r')
            ->orderBy('r.position', 'ASC');
        if (\in_array(DisabledInterface::class, $this->entity->getInterfaceNames(), true)) {
            $qb->andWhere('r.disabled = FALSE');
        }
        $query = $qb->getQuery();

        return $query->execute();
    }

    /**
     * @throws NonUniqueResultException
     *
     * @return TEntity|null
     */
    public function findOneById(int|Uuid $id): ?object
    {
        $qb = $this->createQueryBuilder($this->alias)
            ->where($this->alias.'.id = :id')
            ->setParameter('id', $id);

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

    #[Required]
    public function setPaginator(PaginatorInterface $paginator): void
    {
        $this->paginator = $paginator;
    }

    /**
     * @return PaginationInterface<TEntity>
     */
    protected function pagination(Query|QueryBuilder $query, int $page, int $limit = 30): PaginationInterface
    {
        return $this->paginator->paginate(
            $query, /* query NOT result */
            $page, /* page number */
            $limit /* limit per page */
        );
    }

    /**
     * @return PaginationInterface<TEntity>
     */
    public function findAllPagination(int $page, int $limit = 30, ?QueryBuilder $queryBuilder = null): PaginationInterface
    {
        if (!$queryBuilder) {
            $queryBuilder = $qb = $this
                ->createQueryBuilder('o');
        }

        return $this->pagination($queryBuilder->getQuery(), $page, $limit);
    }

    /**
     * @param array<string, string|array{property: string, join?: string, method?: string, sorter?: string}> $listSorter
     */
    public function generateSorterQueryBuilder(array $listSorter, SearchInterface $search, QueryBuilder $queryBuilder): QueryBuilder
    {
        $joins = [];
        $groupByFields = [];
        $rootAlias = $queryBuilder->getAllAliases()[0];

        foreach ($search->getSorter() as $sorter) {
            foreach ($listSorter as $key => $choice) {
                // 🔹 Cas simple : 'createdAt'
                if (\is_string($choice) && $choice === $sorter) {
                    $field = $rootAlias.'.'.$choice;
                    if ($search->getDirection()) {
                        $queryBuilder->addOrderBy($field, $search->getDirection());
                    }
                    $groupByFields[] = $field;
                    break;
                }

                // 🔹 Cas avancé : ['property' => ..., 'join' => ..., 'method' => ...]
                if (\is_array($choice)) {
                    $property = $choice['property'];
                    $join = $choice['join'] ?? null;
                    $method = $choice['method'] ?? null;
                    $sorterDirection = $choice['sorter'] ?? null; // Substitue à Direction si sorter n'est pas multiple

                    // Convertit en format 'join.property' ou 'property'
                    // $expectedSorter = $join ? $join.'.'.$property : $property;
                    $expectedSorter = $key;
                    if ($expectedSorter === $sorter) {
                        if (null !== $join) {
                            if (!isset($joins[$join])) {
                                $joinAlias = $join.\count($joins);
                                $joins[$join] = $joinAlias;
                                $queryBuilder->leftJoin($rootAlias.'.'.$join, $joinAlias);
                            }

                            $joinAlias = $joins[$join];
                            if ('count' === $method) {
                                $countAlias = str_replace('.', '_', $sorter).'_count';
                                $queryBuilder->addSelect('COUNT('.$joinAlias.'.'.$property.') AS HIDDEN '.$countAlias);
                                if ($search->getDirection()) {
                                    $queryBuilder->addOrderBy($countAlias, $search->getDirection());
                                }
                                if ($sorterDirection) {
                                    $queryBuilder->addOrderBy($countAlias, $sorterDirection);
                                }

                                $groupByFields[] = $rootAlias.'.id';
                            } else {
                                $field = $joinAlias.'.'.$property;
                                if ($search->getDirection()) {
                                    $queryBuilder->addOrderBy($field, $search->getDirection());
                                }
                                if ($sorterDirection) {
                                    $queryBuilder->addOrderBy($field, $sorterDirection);
                                }
                                $groupByFields[] = $field;
                            }
                        } else {
                            $field = $rootAlias.'.'.$property;
                            if ($search->getDirection()) {
                                $queryBuilder->addOrderBy($field, $search->getDirection());
                            }
                            if ($sorterDirection) {
                                $queryBuilder->addOrderBy($field, $sorterDirection);
                            }
                            $groupByFields[] = $field;
                        }

                        break; // on arrête après match
                    }
                }
            }
        }

        // 🔸 Ajout des champs du root entity (PostgreSQL exige tout dans GROUP BY)
        $rootEntity = $queryBuilder->getRootEntities()[0];
        $metadata = $this->getEntityManager()->getClassMetadata($rootEntity);

        foreach ($metadata->getFieldNames() as $field) {
            $groupByFields[] = $rootAlias.'.'.$field;
        }

        foreach ($metadata->getAssociationNames() as $assoc) {
            if (!$metadata->isCollectionValuedAssociation($assoc)) {
                $groupByFields[] = $rootAlias.'.'.$assoc;
            }
        }

        foreach (array_unique($groupByFields) as $field) {
            $queryBuilder->addGroupBy($field);
        }

        return $queryBuilder;
    }
}
