<?php declare(strict_types=1); 
 
namespace Shopware\Core\Checkout\Promotion\DataAbstractionLayer; 
 
use Doctrine\DBAL\Connection; 
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent; 
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity; 
use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor; 
use Shopware\Core\Defaults; 
use Shopware\Core\Framework\Context; 
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\RetryableQuery; 
use Shopware\Core\Framework\Uuid\Uuid; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
 
class PromotionRedemptionUpdater implements EventSubscriberInterface 
{ 
    /** 
     * @var Connection 
     */ 
    private $connection; 
 
    /** 
     * @internal 
     */ 
    public function __construct(Connection $connection) 
    { 
        $this->connection = $connection; 
    } 
 
    /** 
     * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>> 
     */ 
    public static function getSubscribedEvents() 
    { 
        return [ 
            CheckoutOrderPlacedEvent::class => 'orderPlaced', 
        ]; 
    } 
 
    /** 
     * @param array<string> $ids 
     */ 
    public function update(array $ids, Context $context): void 
    { 
        $ids = array_unique(array_filter($ids)); 
 
        if (empty($ids) || $context->getVersionId() !== Defaults::LIVE_VERSION) { 
            return; 
        } 
 
        $sql = <<<'SQL' 
                SELECT LOWER(HEX(order_line_item.promotion_id)) as promotion_id, 
                       COUNT(DISTINCT order_line_item.id) as total, 
                       LOWER(HEX(order_customer.customer_id)) as customer_id 
                FROM order_line_item 
                INNER JOIN order_customer 
                    ON order_customer.order_id = order_line_item.order_id 
                    AND order_customer.version_id = order_line_item.version_id 
                WHERE order_line_item.type = :type 
                AND order_line_item.promotion_id IN (:ids) 
                AND order_line_item.version_id = :versionId 
                GROUP BY order_line_item.promotion_id, order_customer.customer_id 
SQL; 
 
        $promotions = $this->connection->fetchAll( 
            $sql, 
            ['type' => PromotionProcessor::LINE_ITEM_TYPE, 'ids' => Uuid::fromHexToBytesList($ids), 'versionId' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)], 
            ['ids' => Connection::PARAM_STR_ARRAY] 
        ); 
 
        if (empty($promotions)) { 
            return; 
        } 
        $update = new RetryableQuery( 
            $this->connection, 
            $this->connection->prepare('UPDATE promotion SET order_count = :count, orders_per_customer_count = :customerCount WHERE id = :id') 
        ); 
 
        // group the promotions to update each promotion with a single update statement 
        $promotions = $this->groupByPromotion($promotions); 
 
        foreach ($promotions as $id => $totals) { 
            $total = array_sum($totals); 
 
            $update->execute([ 
                'id' => Uuid::fromHexToBytes($id), 
                'count' => (int) $total, 
                'customerCount' => json_encode($totals), 
            ]); 
        } 
    } 
 
    public function orderPlaced(CheckoutOrderPlacedEvent $event): void 
    { 
        $lineItems = $event->getOrder()->getLineItems(); 
        $customer = $event->getOrder()->getOrderCustomer(); 
 
        if (!$lineItems || !$customer) { 
            return; 
        } 
 
        $promotionIds = []; 
        /** @var OrderLineItemEntity $lineItem */ 
        foreach ($lineItems as $lineItem) { 
            if ($lineItem->getType() !== PromotionProcessor::LINE_ITEM_TYPE) { 
                continue; 
            } 
 
            $promotionId = $lineItem->getPromotionId(); 
            if ($promotionId === null) { 
                continue; 
            } 
 
            $promotionIds[] = $promotionId; 
        } 
 
        if (!$promotionIds) { 
            return; 
        } 
 
        $allCustomerCounts = $this->getAllCustomerCounts($promotionIds); 
 
        $update = new RetryableQuery( 
            $this->connection, 
            $this->connection->prepare('UPDATE promotion SET order_count = order_count + 1, orders_per_customer_count = :customerCount WHERE id = :id') 
        ); 
 
        foreach ($promotionIds as $promotionId) { 
            $customerId = $customer->getCustomerId(); 
            if ($customerId !== null) { 
                $allCustomerCounts[$promotionId][$customerId] = 1 + ($allCustomerCounts[$promotionId][$customerId] ?? 0); 
            } 
 
            $update->execute([ 
                'id' => Uuid::fromHexToBytes($promotionId), 
                'customerCount' => json_encode($allCustomerCounts[$promotionId]), 
            ]); 
        } 
    } 
 
    /** 
     * @param array<mixed> $promotions 
     * 
     * @return array<mixed> 
     */ 
    private function groupByPromotion(array $promotions): array 
    { 
        $grouped = []; 
        foreach ($promotions as $promotion) { 
            $id = $promotion['promotion_id']; 
            $customerId = $promotion['customer_id']; 
            $grouped[$id][$customerId] = (int) $promotion['total']; 
        } 
 
        return $grouped; 
    } 
 
    /** 
     * @param array<string> $promotionIds 
     * 
     * @return array<string> 
     */ 
    private function getAllCustomerCounts(array $promotionIds): array 
    { 
        $allCustomerCounts = []; 
        $countResult = $this->connection->fetchAllAssociative( 
            'SELECT `id`, `orders_per_customer_count` FROM `promotion` WHERE `id` IN (:ids)', 
            ['ids' => Uuid::fromHexToBytesList($promotionIds)], 
            ['ids' => Connection::PARAM_STR_ARRAY] 
        ); 
 
        foreach ($countResult as $row) { 
            if (!\is_string($row['orders_per_customer_count'])) { 
                $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = []; 
 
                continue; 
            } 
 
            $customerCount = json_decode($row['orders_per_customer_count'], true); 
            if (!$customerCount) { 
                $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = []; 
 
                continue; 
            } 
 
            $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = $customerCount; 
        } 
 
        return $allCustomerCounts; 
    } 
}