<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Tests\Marketing;

use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\Repository\ProductReviewRepository;
use CpCreation\VitiCore\Marketing\ReviewPublicationPolicy;
use CpCreation\VitiCore\Product\Model\Product;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Doctrine\UuidType;
use Ramsey\Uuid\Uuid;

final class ProductReviewRepositoryTest extends TestCase
{
    private ?Connection $connection = null;

    public function testAdminQueryAppliesSearchProductAndRatingFilters(): void
    {
        $repository = $this->createRepository();
        $productId = Uuid::uuid4()->toString();
        $queryBuilder = $repository->createAdminQueryBuilder(
            search: '  Livraison lente  ',
            productId: $productId,
            rating: 1,
        );
        $dql = $queryBuilder->getDQL();

        self::assertStringContainsString('review.comment', $dql);
        self::assertStringContainsString('review.authorDisplayName', $dql);
        self::assertStringContainsString('productTranslation.name', $dql);
        self::assertStringContainsString('product.id = :reviewProductId', $dql);
        self::assertStringContainsString('review.rating = :reviewRating', $dql);
        self::assertSame('%livraison lente%', $queryBuilder->getParameter('reviewSearch')?->getValue());
        self::assertSame($productId, $queryBuilder->getParameter('reviewProductId')?->getValue());
        self::assertSame(1, $queryBuilder->getParameter('reviewRating')?->getValue());
    }

    public function testAdminQueryFiltersPendingReviews(): void
    {
        $queryBuilder = $this->createRepository()->createAdminQueryBuilder(
            status: ProductReview::STATUS_PENDING,
        );

        self::assertStringContainsString('review.status = :reviewStatus', $queryBuilder->getDQL());
        self::assertSame(
            ProductReview::STATUS_PENDING,
            $queryBuilder->getParameter('reviewStatus')?->getValue(),
        );
    }

    public function testAggregateOnlyIncludesPublicReviewsInsideTheRetentionWindow(): void
    {
        $repository = $this->createRepository();
        $connection = $this->connection;
        self::assertNotNull($connection);
        $connection->executeStatement(<<<'SQL'
            CREATE TABLE marketing_product_review (
                id VARCHAR(36) NOT NULL,
                product_id VARCHAR(36) NOT NULL,
                rating SMALLINT NOT NULL,
                status VARCHAR(24) NOT NULL,
                publishedAt DATETIME DEFAULT NULL,
                PRIMARY KEY (id)
            )
            SQL);

        $product = new Product();
        $otherProduct = new Product();
        $now = new \DateTimeImmutable('2026-07-23 10:00:00');

        $this->insertReview($connection, $product, 4, ProductReview::STATUS_PUBLISHED, '2026-07-01 10:00:00');
        $this->insertReview($connection, $product, 5, ProductReview::STATUS_PUBLISHED, '2021-07-23 10:00:00');
        $this->insertReview($connection, $product, 1, ProductReview::STATUS_PUBLISHED, '2021-07-23 09:59:59');
        $this->insertReview($connection, $product, 1, ProductReview::STATUS_PENDING, '2026-07-01 10:00:00');
        $this->insertReview($connection, $product, 1, ProductReview::STATUS_PUBLISHED, '2026-07-23 10:00:01');
        $this->insertReview($connection, $otherProduct, 1, ProductReview::STATUS_PUBLISHED, '2026-07-01 10:00:00');

        $summary = $repository->getPublishedRatingSummary($product, $now);

        self::assertNotNull($summary);
        self::assertSame(2, $summary->reviewCount);
        self::assertSame(4.5, $summary->ratingValue);
        self::assertSame([
            1 => 0,
            2 => 0,
            3 => 0,
            4 => 1,
            5 => 1,
        ], $summary->ratingCounts);
        self::assertNull($repository->getPublishedRatingSummary(new Product(), $now));

        $summaries = $repository->getPublishedRatingSummaries([$product, $otherProduct], $now);

        self::assertCount(2, $summaries);
        self::assertSame(2, $summaries[$product->getId()->toString()]->reviewCount);
        self::assertSame(4.5, $summaries[$product->getId()->toString()]->ratingValue);
        self::assertSame(1, $summaries[$otherProduct->getId()->toString()]->reviewCount);
        self::assertSame(1.0, $summaries[$otherProduct->getId()->toString()]->ratingValue);
    }

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

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

        return new ProductReviewRepository($registry, new ReviewPublicationPolicy());
    }

    private function insertReview(
        \Doctrine\DBAL\Connection $connection,
        Product $product,
        int $rating,
        string $status,
        string $publishedAt,
    ): void {
        $connection->insert('marketing_product_review', [
            'id' => Uuid::uuid4()->toString(),
            'product_id' => $product->getId()->toString(),
            'rating' => $rating,
            'status' => $status,
            'publishedAt' => $publishedAt,
        ]);
    }
}
