<?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\Cart\Repository;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Cart\Model\Promo;
use CpCreation\VitiCore\Repository\BaseRepository;
use CpCreation\VitiCore\Shop\Model\ShippingZone;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

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

    /**
     * @return array<int, Promo>
     */
    public function findAdminFiltered(
        ?string $search = null,
        string $application = 'all',
        string $assignment = 'all',
        string $benefit = 'all',
        string $status = 'all',
    ): array {
        $qb = $this->createQueryBuilder('p')
            ->leftJoin('p.assignedUser', 'au')
            ->addSelect('au')
            ->orderBy('p.endAt', 'DESC');

        if (null !== $search) {
            $search = '%'.mb_strtolower(mb_trim($search)).'%';
            $qb
                ->andWhere('LOWER(p.title) LIKE :search OR LOWER(p.notificationEmail) LIKE :search OR LOWER(au.email) LIKE :search')
                ->setParameter('search', $search);
        }

        if ('auto' === $application) {
            $qb->andWhere('p.auto = TRUE');
        } elseif ('manual' === $application) {
            $qb->andWhere('p.auto = FALSE');
        }

        if ('user' === $assignment) {
            $qb->andWhere('p.assignedUser IS NOT NULL');
        } elseif ('notification' === $assignment) {
            $qb->andWhere('p.notificationEmail IS NOT NULL');
            $qb->andWhere("TRIM(p.notificationEmail) <> ''");
        } elseif ('unassigned' === $assignment) {
            $qb->andWhere('p.assignedUser IS NULL');
            $qb->andWhere('p.notificationEmail IS NULL OR TRIM(p.notificationEmail) = :emptyEmail');
            $qb->setParameter('emptyEmail', '');
        }

        if ('cart' === $benefit) {
            $qb->andWhere('p.percent IS NOT NULL OR p.amountTTC IS NOT NULL');
        } elseif ('shipping' === $benefit) {
            $qb->andWhere('p.percentShippingCost IS NOT NULL');
        } elseif ('gift' === $benefit) {
            $qb->andWhere('p.offeredVariant IS NOT NULL');
        }

        if ('published' === $status) {
            $qb->andWhere('p.disabled = FALSE');
        } elseif ('unpublished' === $status) {
            $qb->andWhere('p.disabled = TRUE');
        }

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

    public function findManualPromoByCode(string $code, mixed $group = null): ?Promo
    {
        $qb = $this->createQueryBuilder('p');

        $qb
            ->andWhere('p.auto = FALSE')
            ->andWhere('UPPER(p.title) = :code')
            ->setParameter('code', mb_strtoupper(mb_trim($code)))
            ->setMaxResults(1);

        if ($group) {
            $qb
                ->innerJoin('p.groups', 'gr', Join::WITH, 'gr.id = :group')
                ->setParameter('group', $group);
        }

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

    public function findPromoByCode(string $code, mixed $group = null): ?Promo
    {
        $qb = $this->createQueryBuilder('p');

        $qb
            ->andWhere('UPPER(p.title) = :code')
            ->setParameter('code', mb_strtoupper(mb_trim($code)))
            ->setMaxResults(1);

        if ($group) {
            $qb
                ->innerJoin('p.groups', 'gr', Join::WITH, 'gr.id = :group')
                ->setParameter('group', $group);
        }

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

    /**
     * @return array<int, Promo>
     */
    public function findOtherPromosWithSameTitle(Promo $promo): array
    {
        $title = $promo->getTitle();

        if (null === $title || '' === $title) {
            return [];
        }

        $qb = $this->createQueryBuilder('p')
            ->andWhere('p.title = :title')
            ->setParameter('title', $title);

        $qb
            ->andWhere('p.id != :promoId')
            ->setParameter('promoId', $promo->getId());

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

    /**
     * @return array<int, Promo>
     */
    public function findPromoActiv(): array
    {
        $query = $this->createQueryBuilder('p')
            ->andWhere('p.disabled = FALSE')
            ->andWhere('p.beginAt < :datetime')
            ->andWhere('p.auto = FALSE')
            ->andWhere('p.endAt > :datetime')
            ->setParameter('datetime', new \DateTime('now'))
            ->getQuery();

        return $query->execute();
    }

    /**
     * @return Promo|array<int, Promo>
     */
    public function findPromoActivWithCode(Cart $cart, mixed $group = null, ?ShippingZone $zone = null): Promo|array
    {
        $arrayItem = $this->extractCartProductIds($cart);

        $code = mb_strtoupper($cart->getPromo());
        $qb = $this->createQueryBuilder('p');

        $qb
            ->andWhere('p.disabled = FALSE')
            ->andWhere('UPPER(p.title) = :code')
            ->andWhere('p.beginAt < :datetime')
            ->andWhere('p.endAt > :datetime')
            ->setParameter('datetime', new \DateTime('now'))
            ->setParameter('code', $code);

        $qb->leftJoin('p.products', 'pr');
        $qb->andWhere(
            $qb->expr()->orX(
                $qb->expr()->in('pr.id', "'".implode("','", $arrayItem)."'"),
                $qb->expr()->isNull('pr.id'),
            )
        );

        if ($group) {
            $qb
                ->innerJoin('p.groups', 'gr', Join::WITH, 'gr.id = :group')
                ->setParameter('group', $group);
        }

        if ($zone) {
            $qb->leftJoin('p.zones', 'z');
            $qb->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->eq('z.id', "'".$zone->getId()->toString()."'"),
                    $qb->expr()->isNull('z.id'),
                )
            );
        }

        $query = $qb->getQuery();
        $results = $query->getResult();

        if ($results) {
            return $results[0];
        }

        return $results;
    }

    /**
     * @return Promo|array<int, Promo>|null
     */
    public function findPromoActivAutoWithGroup(Cart $cart, mixed $group = null, ?ShippingZone $zone = null): Promo|array|null
    {
        if ($cart->getItems()->isEmpty()) {
            return null;
        }

        $arrayItem = $this->extractCartProductIds($cart);

        $qb = $this->createQueryBuilder('p');
        $qb
            ->andWhere('p.disabled = FALSE')
            ->andWhere('p.beginAt < :datetime')
            ->andWhere('p.endAt > :datetime')
            ->andWhere('p.auto = TRUE');

        $qb->leftJoin('p.products', 'pr');
        $qb->andWhere(
            $qb->expr()->orX(
                $qb->expr()->lte('p.minEquivalency', $cart->getQuantityAllAbsolute()),
                $qb->expr()->isNull('p.minEquivalency'),
            )
        );
        $qb->andWhere(
            $qb->expr()->orX(
                $qb->expr()->gte('p.maxEquivalency', $cart->getQuantityAllAbsolute()),
                $qb->expr()->isNull('p.maxEquivalency'),
            )
        );

        $qb->andWhere(
            $qb->expr()->orX(
                $qb->expr()->in('pr.id', "'".implode("','", $arrayItem)."'"),
                $qb->expr()->isNull('pr.id')
            )
        );

        $qb->orderBy('p.minEquivalency', 'DESC');
        $qb->setParameter('datetime', new \DateTime('now'));

        if ($group) {
            $qb
                ->innerJoin('p.groups', 'gr', Join::WITH, 'gr.id = :group')
                ->setParameter('group', $group);
        }

        if ($zone) {
            $qb->leftJoin('p.zones', 'z');
            $qb->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->eq('z.id', "'".$zone->getId()->toString()."'"),
                    $qb->expr()->isNull('z.id'),
                )
            );
        }

        $query = $qb->getQuery();
        $results = $query->getResult();

        if (!$results) {
            return $results;
        }

        /** @var Promo $result */
        foreach ($results as $result) {
            $qb2 = $this->createQueryBuilder('p');
            $qb2
                ->andWhere('p.id = :result')
                ->andWhere('p.minEquivalency <= :quantity')
                ->orderBy('p.minEquivalency', 'DESC')
                ->setParameter('result', $result->getId()->toString())
                ->setParameter('quantity', $cart->getQuantityByProducts($result->getProducts()));

            $query2 = $qb2->getQuery();
            $results2 = $query2->getResult();

            if ($results2) {
                return $results2[0];
            }
        }

        return null;
    }

    /**
     * @return array<int, Promo>
     */
    public function findActiveAutoPromosByGroup(Cart $cart, mixed $group = null, ?ShippingZone $zone = null): array
    {
        if ($cart->getItems()->isEmpty()) {
            return [];
        }

        $productIds = $this->extractCartProductIds($cart);

        if ([] === $productIds) {
            return [];
        }

        $qb = $this->createQueryBuilder('p');
        $qb
            ->distinct()
            ->leftJoin('p.products', 'pr')
            ->leftJoin('p.incompatibleAutoPromos', 'iap')
            ->leftJoin('p.incompatibleAutoPromoOf', 'iapo')
            ->addSelect('iap')
            ->addSelect('iapo')
            ->andWhere('p.disabled = FALSE')
            ->andWhere('p.auto = TRUE')
            ->andWhere('p.beginAt < :datetime')
            ->andWhere('p.endAt > :datetime')
            ->setParameter('datetime', new \DateTime('now'))
            ->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->in('pr.id', ':productIds'),
                    $qb->expr()->isNull('pr.id')
                )
            )
            ->setParameter('productIds', $productIds);

        if ($group) {
            $qb
                ->innerJoin('p.groups', 'gr_filter', Join::WITH, 'gr_filter.id = :group')
                ->setParameter('group', $group);
        }

        if ($zone) {
            $qb->leftJoin('p.zones', 'z');
            $qb->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->eq('z.id', ':zoneId'),
                    $qb->expr()->isNull('z.id')
                )
            )
                ->setParameter('zoneId', $zone->getId()->toString());
        }

        $qb
            ->addOrderBy('p.cumulative', 'DESC')
            ->addOrderBy('p.minSubtotalTTC', 'DESC')
            ->addOrderBy('p.minEquivalency', 'DESC');

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

    public function createAutoPromoSelectionQueryBuilder(): QueryBuilder
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.auto = TRUE')
            ->orderBy('p.title', 'ASC');
    }

    /**
     * @return list<string>
     */
    private function extractCartProductIds(Cart $cart): array
    {
        $productIds = [];

        /** @var CartItem $item */
        foreach ($cart->getItems() as $item) {
            $product = $item->getVariant()?->getProduct();

            if (null !== $product) {
                $productIds[$product->getId()->toString()] = true;
            }
        }

        return array_keys($productIds);
    }
}
