<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Commerce\Catalog;

use CpCreation\VitiCore\Commerce\Catalog\Api\Resource\CatalogProductReview;
use CpCreation\VitiCore\Commerce\Catalog\Api\Resource\CatalogProductReviews;
use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\ProductReviewConfigurationProvider;
use CpCreation\VitiCore\Marketing\PublishedProductReviewsProvider;
use CpCreation\VitiCore\Product\Model\Product;

final readonly class CatalogProductReviewsProjector
{
    public function __construct(
        private ProductReviewConfigurationProvider $configurationProvider,
        private PublishedProductReviewsProvider $reviewsProvider,
    ) {}

    public function project(
        Product $product,
        ?\DateTimeImmutable $now = null,
    ): CatalogProductReviews {
        if (!$this->configurationProvider->isEnabled()) {
            return $this->disabled();
        }

        return $this->fromReviews(
            $this->reviewsProvider->findPublishedByProduct($product, $now),
        );
    }

    /**
     * @param iterable<Product> $products
     *
     * @return array<string, CatalogProductReviews>
     */
    public function projectCollection(
        iterable $products,
        ?\DateTimeImmutable $now = null,
    ): array {
        $products = \is_array($products) ? array_values($products) : iterator_to_array($products, false);
        if ([] === $products) {
            return [];
        }

        if (!$this->configurationProvider->isEnabled()) {
            $projections = [];
            foreach ($products as $product) {
                $projections[$product->getId()->toString()] = $this->disabled();
            }

            return $projections;
        }

        $reviewsByProduct = [];
        foreach ($this->reviewsProvider->findPublishedByProducts($products, $now) as $review) {
            $reviewsByProduct[$review->getProduct()->getId()->toString()][] = $review;
        }

        $projections = [];
        foreach ($products as $product) {
            $projections[$product->getId()->toString()] = $this->fromReviews(
                $reviewsByProduct[$product->getId()->toString()] ?? [],
            );
        }

        return $projections;
    }

    /** @param list<ProductReview> $reviews */
    private function fromReviews(array $reviews): CatalogProductReviews
    {
        $items = [];
        $ratingTotal = 0;

        foreach ($reviews as $review) {
            $publishedAt = $review->getPublishedAt();
            if (!$publishedAt instanceof \DateTimeImmutable) {
                continue;
            }

            $ratingTotal += $review->getRating();
            $items[] = new CatalogProductReview(
                id: $review->getId()->toString(),
                rating: $review->getRating(),
                author_display_name: $review->getAuthorDisplayName(),
                title: $review->getTitle(),
                comment: $review->getComment(),
                verified_purchase: true,
                purchased_at: $this->timestamp($review->getPurchasedAt()),
                published_at: $this->timestamp($publishedAt),
                merchant_reply: $review->getMerchantReply(),
            );
        }

        $reviewCount = \count($items);

        return new CatalogProductReviews(
            enabled: true,
            review_count: $reviewCount,
            average_rating: 0 < $reviewCount ? round($ratingTotal / $reviewCount, 2) : null,
            items: $items,
        );
    }

    private function disabled(): CatalogProductReviews
    {
        return new CatalogProductReviews(
            enabled: false,
            review_count: 0,
            average_rating: null,
            items: [],
        );
    }

    private function timestamp(\DateTimeInterface $date): string
    {
        return \DateTimeImmutable::createFromInterface($date)
            ->setTimezone(new \DateTimeZone('UTC'))
            ->format(\DATE_ATOM);
    }
}
