<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Tests\Cart;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Cart\Model\Promo;
use CpCreation\VitiCore\Cart\Repository\PromoRepository;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Product\Model\ProductVariant;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Logging\DebugStack;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Doctrine\UuidType;

final class PromoRepositoryTest extends TestCase
{
    public function testActiveAutomaticPromosLoadIncompatibilitiesWithoutCartesianJoin(): void
    {
        if (!Type::hasType('uuid')) {
            Type::addType('uuid', UuidType::class);
        }

        $configuration = ORMSetup::createAttributeMetadataConfiguration([
            \dirname(__DIR__, 2) . '/src/Cart/Model',
        ], true);
        $connection = DriverManager::getConnection([
            'driver' => 'pdo_sqlite',
            'memory' => true,
        ], $configuration);
        $entityManager = new EntityManager($connection, $configuration);
        $schemaTool = new SchemaTool($entityManager);
        $schemaTool->createSchema([
            $entityManager->getClassMetadata(Promo::class),
        ]);
        $connection->executeStatement(
            'CREATE TABLE product_product (id VARCHAR(36) NOT NULL, disabled BOOLEAN NOT NULL DEFAULT 0, PRIMARY KEY (id))'
        );

        $firstPromo = $this->createActiveAutomaticPromo('AUTO 1');
        $secondPromo = $this->createActiveAutomaticPromo('AUTO 2');
        $thirdPromo = $this->createActiveAutomaticPromo('AUTO 3');
        $firstPromo->addIncompatibleAutoPromo($secondPromo);

        $entityManager->persist($firstPromo);
        $entityManager->persist($secondPromo);
        $entityManager->persist($thirdPromo);
        $entityManager->flush();
        $entityManager->clear();

        $registry = $this->createMock(ManagerRegistry::class);
        $registry->method('getManagerForClass')
            ->with(Promo::class)
            ->willReturn($entityManager);

        $repository = new PromoRepository($registry);
        $cart = new Cart();
        $product = new Product();
        $variant = new ProductVariant();
        $variant->setProduct($product);
        $item = new CartItem();
        $item->setVariant($variant);
        $cart->addItem($item);

        $debugStack = new DebugStack();
        $connection->getConfiguration()->setSQLLogger($debugStack);

        $promos = $repository->findActiveAutoPromosByGroup($cart);

        $selectQueries = array_values(array_filter(
            $debugStack->queries,
            static fn(array $query): bool => str_starts_with(mb_strtoupper(mb_trim($query['sql'])), 'SELECT'),
        ));

        self::assertCount(3, $selectQueries);

        foreach ($selectQueries as $query) {
            self::assertLessThanOrEqual(
                1,
                substr_count($query['sql'], 'cart_promo_incompatible_auto'),
                $query['sql'],
            );
        }

        $promosByTitle = [];

        foreach ($promos as $promo) {
            $promosByTitle[$promo->getTitle()] = $promo;
        }

        self::assertCount(3, $promosByTitle);
        self::assertTrue($promosByTitle['AUTO 1']->isIncompatibleWithAutoPromo($promosByTitle['AUTO 2']));
        self::assertTrue($promosByTitle['AUTO 2']->isIncompatibleWithAutoPromo($promosByTitle['AUTO 1']));
        self::assertFalse($promosByTitle['AUTO 1']->isIncompatibleWithAutoPromo($promosByTitle['AUTO 3']));
    }

    public function testAdminFilterUsesReminderRelationToIdentifyAbandonedCartPromos(): void
    {
        $repository = $this->createQueryOnlyRepository();

        $abandonedCartDql = $repository->createAdminQueryBuilder(origin: 'abandoned_cart')->getDQL();
        self::assertStringContainsString(
            'CpCreation\VitiCore\Marketing\Model\AbandonedCartReminder abandonedCartReminder',
            $abandonedCartDql,
        );
        self::assertStringContainsString('abandonedCartReminder.promo = p', $abandonedCartDql);
        self::assertStringContainsString('abandonedCartReminder.id IS NOT NULL', $abandonedCartDql);

        $otherDql = $repository->createAdminQueryBuilder(origin: 'other')->getDQL();
        self::assertStringContainsString('abandonedCartReminder.id IS NULL', $otherDql);

        $allDql = $repository->createAdminQueryBuilder()->getDQL();
        self::assertStringNotContainsString('AbandonedCartReminder', $allDql);
    }

    private function createQueryOnlyRepository(): PromoRepository
    {
        if (!Type::hasType('uuid')) {
            Type::addType('uuid', UuidType::class);
        }

        $configuration = ORMSetup::createAttributeMetadataConfiguration([
            \dirname(__DIR__, 2) . '/src/Cart/Model',
        ], true);
        $connection = DriverManager::getConnection([
            'driver' => 'pdo_sqlite',
            'memory' => true,
        ], $configuration);
        $entityManager = new EntityManager($connection, $configuration);
        $registry = $this->createMock(ManagerRegistry::class);
        $registry->method('getManagerForClass')
            ->with(Promo::class)
            ->willReturn($entityManager);

        return new PromoRepository($registry);
    }

    private function createActiveAutomaticPromo(string $title): Promo
    {
        $promo = new Promo();
        $promo->setTitle($title);
        $promo->setAuto(true);
        $promo->setBeginAt(new \DateTimeImmutable('-1 day'));
        $promo->setEndAt(new \DateTimeImmutable('+1 day'));

        return $promo;
    }
}
