<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\Checkout;

use Gls\GlsPoland\Entity\Carrier;
use Gls\GlsPoland\Entity\ShopDeliveryCarrier;
use Gls\GlsPoland\PrestaShop\Asset\DTO\Assets;
use Gls\GlsPoland\PrestaShop\Checkout\Presenter\CheckoutConfigPresenter;
use Gls\GlsPoland\PrestaShop\Helper\CarrierFinder;
use Gls\GlsPoland\PrestaShop\Templating\RendererInterface;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

abstract class AbstractCheckout implements CheckoutInterface
{
    use TranslatorAwareTrait;

    public const MAP_SDK_JS_URI = 'https://mapa.szybkapaczka.pl/js/v3.1/maps_sdk.js';
    public const MAP_SDK_CSS_URI = 'https://mapa.szybkapaczka.pl/css/v3.1/maps_sdk.css';

    /**
     * @var \FrontController|null
     */
    protected $controller;

    private $renderer;
    private $dataPresenter;
    private $carrierFinder;
    private $validator;
    private $persister;

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

    public function setController(\FrontController $controller): CheckoutInterface
    {
        $this->controller = $controller;

        return $this;
    }

    public function getAssets(\Cart $cart): Assets
    {
        return (new Assets())
            ->addJavaScript(self::MAP_SDK_JS_URI)
            ->addStyleSheet(self::MAP_SDK_CSS_URI)
            ->addStyleSheet('css/front/checkout.css')
            ->addJavaScriptVariable('glsPolandCheckoutConfig', $this->dataPresenter->present($cart));
    }

    public function renderDeliveryOptionsForm(Carrier $carrier, \Cart $cart, int $carrierId): string
    {
        if (!$carrier instanceof ShopDeliveryCarrier) {
            return '';
        }

        return $this->renderer->render('module:glspoland/views/templates/hook/carrier_extra_content.tpl', [
            'carrier_id' => $carrierId,
            'reference_id' => $carrier->getId()->getValue(),
        ]);
    }

    public function renderMapModal(): string
    {
        return $this->renderer->render('module:glspoland/views/templates/hook/after_carrier.tpl');
    }

    protected function validateDeliveryOption(\Cart $cart, array $requestParameters): bool
    {
        if (!isset($requestParameters['delivery_option']) || !is_array($requestParameters['delivery_option'])) {
            $this->addError($this->getTranslator()->trans('Malformed request.', [], 'Modules.Glspoland.Checkout'));

            return false;
        }

        if ([] === $carriers = $this->getCarriersByDeliveryOption($cart, $requestParameters['delivery_option'])) {
            return true;
        }

        foreach ($carriers as $carrierId => $carrier) {
            if (!$this->validateCarrierData($carrier, $requestParameters['gls_poland'][$carrierId] ?? null)) {
                return false;
            }
        }

        return true;
    }

    protected function saveDeliveryOption(\Cart $cart, Request $request): void
    {
        if (!$request->request->has('delivery_option')) {
            return;
        }

        $carriers = $this->getCarriersByDeliveryOption($cart, $request->request->get('delivery_option'));
        $data = $request->request->get('gls_poland', []);

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

    protected function getCarriersByDeliveryOption(\Cart $cart, array $deliveryOption): array
    {
        $addressId = (int) $cart->id_address_delivery;

        if (!isset($deliveryOption[$addressId])) {
            return [];
        }

        $carrierIdsList = trim($deliveryOption[$addressId], ',');
        $carrierIds = array_map('intval', explode(',', $carrierIdsList));

        return $this->carrierFinder->findByCarrierIds(...$carrierIds);
    }

    abstract protected function addError(string $error);

    private function validateCarrierData(Carrier $carrier, $data): bool
    {
        try {
            $violations = $this->validator->validate($carrier, $data);
        } catch (UnexpectedTypeException $e) {
            $this->addError($this->getTranslator()->trans('Malformed request.', [], 'Modules.Glspoland.Checkout'));

            return false;
        }

        if (0 === count($violations)) {
            return true;
        }

        foreach ($violations as $violation) {
            $this->addError($violation->getMessage());
        }

        return false;
    }
}
