<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\MessageHandler\Carrier;

use Doctrine\ORM\EntityRepository;
use Gls\GlsPoland\Carrier\CarrierType;
use Gls\GlsPoland\Carrier\TrackingUrlGeneratorInterface;
use Gls\GlsPoland\DataProvider\ServiceInformationProvider;
use Gls\GlsPoland\PrestaShop\DTO;
use Gls\GlsPoland\PrestaShop\Message\CreateCarrierCommandInterface;
use Gls\GlsPoland\PrestaShop\MessageHandler\Carrier\Assembler\CarrierAssemblerInterface;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;
use PrestaShopBundle\Entity\Lang;
use PrestaShopBundle\Entity\Repository\LangRepository;

final class CarrierFactory implements CarrierFactoryInterface
{
    use TranslatorAwareTrait;

    private $module;
    /**
     * @var EntityRepository<Lang>
     */
    private $languageRepository;
    private $trackingUrlGenerator;
    private $assemblers;
    private $infoProvider;

    private $languages;

    /**
     * @param iterable<CarrierAssemblerInterface> $assemblers
     */
    public function __construct(\Module $module, LangRepository $languageRepository, TrackingUrlGeneratorInterface $trackingUrlGenerator, iterable $assemblers, ?ServiceInformationProvider $informationProvider = null)
    {
        $this->module = $module;
        $this->languageRepository = $languageRepository;
        $this->trackingUrlGenerator = $trackingUrlGenerator;
        $this->assemblers = $assemblers;
        $this->infoProvider = $informationProvider;
    }

    public function create(CreateCarrierCommandInterface $command): DTO\Carrier
    {
        $model = $this->createCarrierModel($command);
        $carrier = new DTO\Carrier($model);

        foreach ($this->assemblers as $assembler) {
            assert($assembler instanceof CarrierAssemblerInterface);
            $assembler->assemble($carrier, $command);
        }

        return $carrier;
    }

    private function createCarrierModel(CreateCarrierCommandInterface $command): \Carrier
    {
        $carrier = new \Carrier();

        $isDomestic = $this->isDomesticCarrier($command);

        $carrier->is_module = true;
        $carrier->external_module_name = $this->module->name;
        $carrier->name = $command->getName();
        $carrier->delay = $this->getDelay($isDomestic);
        $carrier->url = $this->trackingUrlGenerator->generate('@', $isDomestic ? 'pl' : 'en');
        $carrier->shipping_external = true;
        $carrier->need_range = true;
        $carrier->shipping_method = \Carrier::SHIPPING_METHOD_WEIGHT;
        $carrier->max_weight = $this->getMaxPackageWeight($command, $isDomestic);
        $carrier->range_behavior = true; // disable carrier if the package weight exceeds the value configured for the last price range

        return $carrier;
    }

    private function getMaxPackageWeight(CreateCarrierCommandInterface $command, bool $isDomestic): float
    {
        $intl = !$isDomestic && CarrierType::SHOP_DELIVERY !== (string) $command->getType();

        if (null === $this->infoProvider) {
            return $intl ? ServiceInformationProvider::MAX_INTL_PARCEL_WEIGHT_KG : ServiceInformationProvider::MAX_DOMESTIC_PARCEL_WEIGHT_KG;
        }

        return $intl
            ? $this->infoProvider->getMaxInternationalParcelWeight()
            : $this->infoProvider->getMaxDomesticParcelWeight();
    }

    private function getDelay(bool $isDomestic): array
    {
        $delay = [];

        foreach ($this->getLanguages() as $language) {
            $delay[$language->getId()] = $isDomestic
                ? $this->getTranslator()->trans('24 - 48 hours', [], 'Modules.Glspoland.Carrier', $language->getLocale())
                : $this->getTranslator()->trans('48 - 96 hours', [], 'Modules.Glspoland.Carrier', $language->getLocale());
        }

        return $delay;
    }

    /**
     * @return iterable<Lang>
     */
    private function getLanguages(): iterable
    {
        return $this->languages ?? ($this->languages = $this->languageRepository->findAll());
    }

    private function isDomesticCarrier(CreateCarrierCommandInterface $command): bool
    {
        return ['PL'] === $command->getCountryIsoCodes();
    }
}
