<?php

declare (strict_types=1);
namespace Worldline\Vendor\Worldline\WorldlineForWoocommerce\Webhooks\Queue;

use Worldline\Vendor\Worldline\WorldlineForWoocommerce\Webhooks\Handler\WebhookHandlerInterface;
use Worldline\Vendor\Worldline\WorldlineForWoocommerce\Webhooks\Helper\WebhookHelper;
use Worldline\Vendor\Worldline\WorldlineForWoocommerce\WorldlinePaymentGateway\Api\WlopWcOrderFactory;
use Worldline\Vendor\Worldline\WorldlineForWoocommerce\WorldlinePaymentGateway\OrderMetaKeys;
use Worldline\Vendor\Worldline\WorldlineForWoocommerce\WorldlinePaymentGateway\WlopWcOrder;
use Worldline\Vendor\Worldline\WorldlineForWoocommerce\Utils\LockerFactoryInterface;
use Worldline\Vendor\OnlinePayments\Sdk\Domain\WebhooksEvent;
use Exception;
use WC_Meta_Data;
class WebhookHandlerExecutor implements WebhookHandlerExecutorInterface
{
    const VOUCHER_IDS = [3112, 5402, 5403];
    const VOUCHER_SKIP_EVENTS = ['payment.authorization_requested', 'payment.pending_approval', 'payment.pending_completion', 'payment.pending_capture', 'payment.captured', 'payment.cancelled', 'payment.rejected'];
    /**
     * @var WebhookHandlerInterface[]
     */
    private array $handlers;
    private LockerFactoryInterface $lockerFactory;
    /**
     * @param WebhookHandlerInterface[] $handlers
     */
    public function __construct(array $handlers, LockerFactoryInterface $lockerFactory)
    {
        $this->handlers = $handlers;
        $this->lockerFactory = $lockerFactory;
    }
    public function handle(WebhooksEvent $webhook) : void
    {
        $ref = WebhookHelper::reference($webhook);
        if ($ref === null) {
            throw new Exception('Merchant reference not found.');
        }
        $locker = $this->lockerFactory->create((int) $ref);
        if (!$locker->lockBlocking()) {
            \do_action('wlop.webhook_lock_timeout', ['id' => $webhook->id, 'type' => $webhook->type, 'ref' => $ref]);
            return;
        }
        try {
            $this->handleLocked($webhook, $ref);
        } finally {
            $locker->unlock();
        }
    }
    /**
     * Runs while the per-order lock is held. The order is loaded here, not
     * before locking, so a request that just finished waiting for the lock
     * always sees the other request's already-committed changes instead of
     * a stale pre-lock snapshot.
     */
    private function handleLocked(WebhooksEvent $webhook, string $ref) : void
    {
        $wlopWcOrderFactory = new WlopWcOrderFactory();
        $wlopWcOrder = $wlopWcOrderFactory->create($webhook);
        if ($this->webhookProcessed($wlopWcOrder, $webhook)) {
            return;
        }
        $eventData = ['id' => $webhook->id, 'type' => $webhook->type, 'ref' => $ref, 'object' => $webhook];
        \do_action('wlop.webhook_handling_start', $eventData);
        foreach ($this->handlers as $handler) {
            if (!$handler->accepts($webhook)) {
                continue;
            }
            if ($this->shouldSkipVoucherWebhook($webhook)) {
                continue;
            }
            $handlerEventData = \array_merge($eventData, ['handler' => (new \ReflectionClass($handler))->getShortName()]);
            \do_action('wlop.webhook_handler_found', $handlerEventData);
            try {
                $handler->handle($webhook, $wlopWcOrder);
            } catch (\Throwable $exception) {
                \do_action('wlop.webhook_handler_error', ['exception' => $exception]);
            }
            \do_action('wlop.webhook_handled', $handlerEventData);
        }
        $this->addProcessedWebhook($wlopWcOrder, $webhook);
        \do_action('wlop.webhook_handling_end', $eventData);
    }
    protected function wlopWebhookId(WebhooksEvent $webhook) : string
    {
        $transactionId = WebhookHelper::transactionId($webhook);
        $statusCode = (string) WebhookHelper::statusCode($webhook);
        return $webhook->type . '_' . $transactionId . '_' . $statusCode;
    }
    protected function addProcessedWebhook(WlopWcOrder $wlopWcOrder, WebhooksEvent $webhook) : void
    {
        $wlopWcOrder->order()->add_meta_data(OrderMetaKeys::PROCESSED_WEBHOOKS, $this->wlopWebhookId($webhook));
        $wlopWcOrder->order()->save();
    }
    public function webhookProcessed(WlopWcOrder $wlopWcOrder, WebhooksEvent $webhook) : bool
    {
        /** @var $webhookIdsMeta */
        $webhookIdsMeta = $wlopWcOrder->order()->get_meta(OrderMetaKeys::PROCESSED_WEBHOOKS, \false);
        if (!\is_array($webhookIdsMeta)) {
            return \false;
        }
        $processedWebhooks = \array_map(static function (WC_Meta_Data $webhookIdMeta) : string {
            return (string) $webhookIdMeta->get_data()['value'];
        }, $webhookIdsMeta);
        return \in_array($this->wlopWebhookId($webhook), $processedWebhooks, \true);
    }
    /**
     * @param WebhooksEvent $webhook
     *
     * @return bool
     */
    private function shouldSkipVoucherWebhook(WebhooksEvent $webhook) : bool
    {
        $type = $webhook->type;
        if (!\in_array($type, self::VOUCHER_SKIP_EVENTS, \true)) {
            return \false;
        }
        $paymentOutput = $webhook->getPayment()->getPaymentOutput();
        $redirectOutput = $paymentOutput->getRedirectPaymentMethodSpecificOutput();
        $productId = $redirectOutput ? $redirectOutput->getPaymentProductId() : null;
        if (!\in_array((int) $productId, self::VOUCHER_IDS, \true)) {
            return \false;
        }
        $amount = $paymentOutput->getAmountOfMoney()->getAmount();
        $acquired = $paymentOutput->getAcquiredAmount() ? $paymentOutput->getAcquiredAmount()->getAmount() : 0;
        return $amount !== $acquired;
    }
}
