<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\Checkout\MessageHandler;

use Gls\GlsPoland\Entity\Carrier;
use Gls\GlsPoland\Entity\ShopDeliveryCarrier;
use Gls\GlsPoland\PrestaShop\Checkout\DeliveryOptionDataPersisterInterface;
use Gls\GlsPoland\PrestaShop\Checkout\DeliveryOptionValidatorInterface;
use Gls\GlsPoland\PrestaShop\Checkout\Exception\ValidationException;
use Gls\GlsPoland\PrestaShop\Checkout\Message\UpdateParcelShopChoiceCommand;
use Gls\GlsPoland\PrestaShop\Helper\CarrierFinder;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;

final class UpdateParcelShopChoiceHandler implements UpdateParcelShopChoiceHandlerInterface
{
    use TranslatorAwareTrait;

    /**
     * @var CarrierFinder
     */
    private $carrierFinder;

    /**
     * @var DeliveryOptionValidatorInterface
     */
    private $validator;

    /**
     * @var DeliveryOptionDataPersisterInterface
     */
    private $persister;

    public function __construct(CarrierFinder $carrierFinder, DeliveryOptionValidatorInterface $validator, DeliveryOptionDataPersisterInterface $persister)
    {
        $this->carrierFinder = $carrierFinder;
        $this->validator = $validator;
        $this->persister = $persister;
    }

    public function handle(UpdateParcelShopChoiceCommand $command): void
    {
        $cart = $command->getCart();
        $carrierReferenceId = $command->getCarrierReferenceId();
        $carriers = [];

        if (null !== $carrierReferenceId) {
            $carrier = $this->carrierFinder->findOneByReferenceId($carrierReferenceId);
            if ($carrier instanceof ShopDeliveryCarrier) {
                $carriers[] = $carrier;
            }
        }

        if ([] === $carriers) {
            $deliveryOption = null;
            $deliveryOptions = $cart->getDeliveryOption();
            if (is_array($deliveryOptions)) {
                $deliveryOption = $deliveryOptions[(int) ($cart->id_address_delivery ?? 0)] ?? null;
            }

            $carriers = $this->getShopDeliveryCarriers($deliveryOption, (int) ($cart->id_carrier ?? 0));
        }

        if ([] === $carriers) {
            throw new ValidationException($this->getTranslator()->trans('The selected delivery option in not associated with a GLS ParcelShop carrier.', [], 'Modules.Glspoland.Checkout'));
        }

        $parcelShopId = $command->getParcelShopId();

        foreach ($carriers as $carrier) {
            $this->validateParcelShopChoice($carrier, $parcelShopId);
        }

        $data = array_map(static function () use ($parcelShopId): array {
            return ['parcel_shop_id' => $parcelShopId];
        }, $carriers);

        $this->persister->save($cart, $carriers, $data);
    }

    private function getShopDeliveryCarriers(?string $deliveryOption, int $fallbackCarrierId = 0): array
    {
        $carrierIds = [];

        if (is_string($deliveryOption) && '' !== trim($deliveryOption, ',')) {
            $carrierIds = array_map('intval', explode(',', trim($deliveryOption, ',')));
        } elseif (0 < $fallbackCarrierId) {
            $carrierIds = [$fallbackCarrierId];
        }

        if ([] === $carrierIds) {
            return [];
        }

        $carriers = $this->carrierFinder->findByCarrierIds(...$carrierIds);

        return array_filter($carriers, static function (Carrier $carrier): bool {
            return $carrier instanceof ShopDeliveryCarrier;
        });
    }

    private function validateParcelShopChoice(Carrier $carrier, string $parcelShopId): void
    {
        $violations = $this->validator->validate($carrier, [
            'parcel_shop_id' => $parcelShopId,
        ]);

        if (0 !== count($violations)) {
            throw new ValidationException($violations[0]->getMessage());
        }
    }
}
