<?php

/*
 * This file is part of the VITICORE2 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 CpCreation\VitiCore\User\Repository;

use CpCreation\VitiCore\Repository\BaseRepository;
use CpCreation\VitiCore\User\Model\User;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

class UserRepository extends BaseRepository
{
    /**
     * UserRepository constructor.
     *
     * @throws \ReflectionException
     */
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, User::class);
    }

    public function findVerifiedByEmail(string $email): ?User
    {
        return $this->createQueryBuilder('user')
            ->andWhere('user.email = :email')
            ->andWhere('user.isVerified = :verified')
            ->setParameter('email', $email)
            ->setParameter('verified', true)
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function findLatestUnverifiedByEmail(string $email): ?User
    {
        return $this->createQueryBuilder('user')
            ->andWhere('user.email = :email')
            ->andWhere('user.isVerified = :verified')
            ->setParameter('email', $email)
            ->setParameter('verified', false)
            ->orderBy('user.createdAt', 'DESC')
            ->addOrderBy('user.updatedAt', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function deleteOtherUnverifiedAccountsByEmail(string $email, ?User $userToKeep = null): void
    {
        $users = $this->createQueryBuilder('user')
            ->andWhere('user.email = :email')
            ->andWhere('user.isVerified = :verified')
            ->setParameter('email', $email)
            ->setParameter('verified', false)
            ->getQuery()
            ->getResult();

        $entityManager = $this->getEntityManager();
        $hasDeletion = false;
        $keptUserId = $userToKeep?->getId()->toString();

        foreach ($users as $user) {
            if (!$user instanceof User) {
                continue;
            }

            if (null !== $keptUserId && $user->getId()->toString() === $keptUserId) {
                continue;
            }

            $entityManager->remove($user);
            $hasDeletion = true;
        }

        if ($hasDeletion) {
            $entityManager->flush();
        }
    }

    /**
     * @return list<User>
     */
    public function findAdminUsers(): array
    {
        return array_values(array_filter(
            $this->findUsersForBackOfficeList(),
            static fn (User $user): bool => [] !== $user->getAssignableRoles()
        ));
    }

    /**
     * @return list<User>
     */
    public function findClientUsers(): array
    {
        return array_values(array_filter(
            $this->findUsersForBackOfficeList(),
            static fn (User $user): bool => \in_array('ROLE_USER', $user->getRoles(), true) || \in_array('ROLE_ADMIN', $user->getRoles(), true)
        ));
    }

    /**
     * @return list<User>
     */
    public function findMarketingOptInUsers(): array
    {
        return $this->createQueryBuilder('user')
            ->addSelect('invoiceAddress', 'deliveryAddress')
            ->leftJoin('user.invoiceAddress', 'invoiceAddress')
            ->leftJoin('user.deliveryAddress', 'deliveryAddress')
            ->andWhere('user.marketingOptIn = TRUE')
            ->andWhere('user.email IS NOT NULL')
            ->orderBy('user.email', 'ASC')
            ->getQuery()
            ->getResult();
    }

    public function createRegisteredClientsQueryBuilder(?bool $isVerified = null): QueryBuilder
    {
        $qb = $this->createQueryBuilder('user')
            ->leftJoin('user.group', 'price_group')
            ->addSelect('price_group')
            ->orderBy('user.email', 'ASC');

        if (null !== $isVerified) {
            $qb
                ->andWhere('user.isVerified = :verified')
                ->setParameter('verified', $isVerified);
        }

        return $qb;
    }

    /**
     * @return list<User>
     */
    public function findRegisteredClientsFiltered(
        ?string $groupId = null,
        ?bool $marketingOptIn = null,
        ?bool $hasOrders = null,
        ?bool $isVerified = null,
        ?string $search = null,
    ): array {
        $qb = $this->createRegisteredClientsQueryBuilder($isVerified);

        if (null !== $groupId) {
            if ('__none__' === $groupId) {
                $qb->andWhere('user.group IS NULL');
            } else {
                $qb
                    ->andWhere('price_group.id = :groupId')
                    ->setParameter('groupId', $groupId);
            }
        }

        if (null !== $marketingOptIn) {
            $qb
                ->andWhere('user.marketingOptIn = :marketingOptIn')
                ->setParameter('marketingOptIn', $marketingOptIn);
        }

        if (null !== $search) {
            $qb
                ->leftJoin('user.invoiceAddress', 'invoice_address')
                ->leftJoin('user.deliveryAddress', 'delivery_address')
                ->andWhere($qb->expr()->orX(
                    'LOWER(user.email) LIKE :search',
                    'LOWER(user.lastLoginIp) LIKE :search',
                    'LOWER(price_group.name) LIKE :search',
                    'LOWER(invoice_address.email) LIKE :search',
                    'LOWER(invoice_address.firstname) LIKE :search',
                    'LOWER(invoice_address.lastname) LIKE :search',
                    'LOWER(invoice_address.company) LIKE :search',
                    'LOWER(invoice_address.postal) LIKE :search',
                    'LOWER(invoice_address.city) LIKE :search',
                    'LOWER(delivery_address.firstname) LIKE :search',
                    'LOWER(delivery_address.lastname) LIKE :search',
                    'LOWER(delivery_address.company) LIKE :search',
                    'LOWER(delivery_address.postal) LIKE :search',
                    'LOWER(delivery_address.city) LIKE :search',
                ))
                ->setParameter('search', '%'.mb_strtolower($search).'%');
        }

        if (null !== $hasOrders) {
            $ordersExistsDql = 'EXISTS (
                SELECT 1
                FROM CpCreation\VitiCore\Cart\Model\Cart cart
                LEFT JOIN cart.cartCheckout checkout
                WHERE cart.user = user
                    AND EXISTS (
                        SELECT 1
                        FROM CpCreation\VitiCore\Cart\Model\CartItem item
                        WHERE item.cart = cart
                    )
                    AND (
                        checkout.payedAt IS NOT NULL
                        OR checkout.orderedAt IS NOT NULL
                    )
            )';

            $qb->andWhere($hasOrders ? $ordersExistsDql : 'NOT ('.$ordersExistsDql.')');
        }

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

    /**
     * @return list<User>
     */
    private function findUsersForBackOfficeList(): array
    {
        return $this->createQueryBuilder('user')
            ->orderBy('user.createdAt', 'DESC')
            ->addOrderBy('user.updatedAt', 'DESC')
            ->getQuery()
            ->getResult();
    }
}
