<?php

use Gls\GlsPoland\Controller\Admin\ConfigurationController;
use Gls\GlsPoland\Controller\Admin\ConsignmentController;
use Gls\GlsPoland\PrestaShop\Checkout\DeliveryOptionAvailabilityCheckerInterface;
use Gls\GlsPoland\PrestaShop\Hook\HookExecutorInterface;
use Gls\GlsPoland\PrestaShop\Installer\InstallerException;
use Gls\GlsPoland\PrestaShop\Installer\InstallerFactory;
use Gls\GlsPoland\PrestaShop\Installer\InstallerInterface;
use Gls\GlsPoland\PrestaShop\Installer\UninstallerInterface;
use Gls\GlsPoland\Translation\TranslationLoaderTrait;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    require_once __DIR__ . '/vendor/autoload.php';
}

class GlsPoland extends \CarrierModule
{
    use TranslationLoaderTrait;

    /**
     * @var int|numeric-string identifier of the module's carrier for which PrestaShop is currently performing shipping cost calculation
     * @see \Cart::getPackageShippingCostFromModule
     */
    public $id_carrier;

    public function __construct()
    {
        $this->name = 'glspoland';
        $this->version = '1.0.0';
        $this->author = 'GLS Poland Sp. z o.o.';
        $this->tab = 'shipping_logistics';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = ['min' => '1.7.6', 'max' => '8.1.99'];

        parent::__construct();

        $this->initTranslations();
        $this->displayName = $this->trans('GLS Poland', [], 'Modules.Glspoland.Admin');
        $this->description = $this->trans('Official GLS Poland integration module for PrestaShop', [], 'Modules.Glspoland.Admin');

        if (!extension_loaded('soap')) {
            $this->warning = $this->trans('Communication with the API requires the PHP SOAP extension. Please enable it on your server.', [], 'Modules.Glspoland.Installer');
        }
    }

    /**
     * @return true
     */
    public function isUsingNewTranslationSystem()
    {
        return true;
    }

    /**
     * @return bool
     */
    public function install()
    {
        if (70200 > PHP_VERSION_ID) {
            $this->_errors[] = $this->trans('This module requires PHP 7.2 or later.', [], 'Modules.Glspoland.Installer');

            return false;
        }

        $this->setUpRoutingLoaderResolver();

        if (!parent::install()) {
            return false;
        }

        $this->clearStaticCircularReferences();

        try {
            $this->getInstaller()->install($this);

            return true;
        } catch (InstallerException $exception) {
            $this->_errors[] = $exception->getMessage();
        }

        return false;
    }

    /**
     * @return bool
     */
    public function uninstall()
    {
        $this->setUpRoutingLoaderResolver();

        try {
            $this->getUninstaller()->uninstall($this, $this->isResetModuleAction());
        } catch (InstallerException $exception) {
            $this->_errors[] = $exception->getMessage();

            return false;
        }

        return parent::uninstall();
    }

    public function getContent()
    {
        try {
            /** @var UrlGeneratorInterface $router */
            $router = $this->get('router');

            Tools::redirectAdmin($router->generate('admin_gls_poland_config_api'));
        } catch (RouteNotFoundException $e) {
            if (!$this->active && Tools::version_compare(_PS_VERSION_, '8.0.0', '>=') && !$this->hasShopAssociations()) {
                /** @var Session $session */
                $session = $this->get('session');
                $session->getFlashBag()->add('error', $this->trans('To access the configuration page, the module must be enabled.', [], 'Modules.Glspoland.Admin'));

                Tools::redirectAdmin($router->generate('admin_module_manage'));
            }

            if (Tools::getValue('cache_cleared')) {
                throw $e;
            }

            Tools::clearSf2Cache();
            Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, null, [
                'configure' => $this->name,
                'cache_cleared' => true,
            ]));
        }
    }

    /**
     * @param Cart $params
     * @param float $shipping_cost
     *
     * @return float|false
     */
    public function getOrderShippingCost($params, $shipping_cost)
    {
        if (!$params instanceof Cart) {
            throw new InvalidArgumentException(sprintf('Argument 1 passed to %s() must be an instance of "%s", "%s" given.', __METHOD__, Cart::class, get_debug_type($params)));
        }

        if (!$this->active || 0 >= $carrierId = (int) $this->id_carrier) {
            return false;
        }

        if (!$this->get(DeliveryOptionAvailabilityCheckerInterface::class)->isAvailable($carrierId, $params)) {
            return false;
        }

        return (float) $shipping_cost;
    }

    /**
     * @param Cart $params
     *
     * @return false
     */
    public function getOrderShippingCostExternal($params)
    {
        if (!$params instanceof Cart) {
            throw new InvalidArgumentException(sprintf('Argument 1 passed to %s() must be an instance of "%s", "%s" given.', __METHOD__, Cart::class, get_debug_type($params)));
        }

        return false;
    }

    /**
     * @param string $methodName
     */
    public function __call($methodName, array $arguments)
    {
        $hookName = str_starts_with($methodName, 'hook')
            ? lcfirst(Tools::substr($methodName, 4))
            : $methodName;

        $parameters = isset($arguments[0]) ? $arguments[0] : [];
        if (!isset($parameters['request'])) {
            $parameters['request'] = $this->getCurrentRequest();
        }

        try {
            return $this
                ->get(HookExecutorInterface::class)
                ->execute($hookName, $parameters);
        } catch (Throwable $e) {
            $this->getLogger()->critical('Error executing hook "{hookName}"', [
                'hookName' => $hookName,
                'error' => $e,
            ]);

            if (!defined('_PS_MODE_DEV_') || _PS_MODE_DEV_) {
                throw $e;
            }

            return null;
        }
    }

    /**
     * @return array
     */
    public function getTabs()
    {
        if ([] !== $this->tabs) {
            return $this->tabs;
        }

        return $this->tabs = [
            [
                'class_name' => ConfigurationController::TAB_NAME,
                'route_name' => 'admin_gls_poland_config_api',
                'visible' => false,
                'name' => [
                    'en' => $this->trans('GLS Poland', [], 'Modules.Glspoland.Admin', 'en-US'),
                    'pl' => $this->trans('GLS Poland', [], 'Modules.Glspoland.Admin', 'pl-PL'),
                ],
                'wording' => 'GLS Poland',
                'wording_domain' => 'Modules.Glspoland.Admin',
            ],
            [
                'class_name' => ConsignmentController::TAB_NAME,
                'parent_class_name' => ConsignmentController::PARENT_TAB_NAME,
                'route_name' => 'admin_gls_poland_consignments_index',
                'name' => [
                    'en' => $this->trans('GLS consignments', [], 'Modules.Glspoland.Admin', 'en-US'),
                    'pl' => $this->trans('GLS consignments', [], 'Modules.Glspoland.Admin', 'pl-PL'),
                ],
                'wording' => 'GLS consignments',
                'wording_domain' => 'Modules.Glspoland.Admin',
            ],
        ];
    }

    /**
     * @template T
     *
     * @param string|class-string<T> $serviceName
     *
     * @return T|object
     */
    public function get($serviceName)
    {
        try {
            $service = parent::get($serviceName);
        } catch (ServiceNotFoundException $exception) {
            if ($this->isSymfonyContext() || null === $container = SymfonyContainer::getInstance()) {
                throw $exception;
            }

            return $container->get($serviceName);
        }

        if (false === $service) {
            throw new RuntimeException('DI container was not found.');
        }

        return $service;
    }

    /**
     * @return InstallerInterface
     */
    private function getInstaller()
    {
        try {
            return $this->get(InstallerInterface::class);
        } catch (ServiceNotFoundException $e) {
            if (null === $installer = $this->createInstaller()) {
                throw $e;
            }

            return $installer;
        }
    }

    /**
     * @return UninstallerInterface
     */
    private function getUninstaller()
    {
        try {
            return $this->get(UninstallerInterface::class);
        } catch (ServiceNotFoundException $e) {
            if (null === $installer = $this->createInstaller()) {
                throw $e;
            }

            return $installer;
        }
    }

    /**
     * @return (InstallerInterface&UninstallerInterface)|null
     */
    private function createInstaller()
    {
        if (null === $container = SymfonyContainer::getInstance()) {
            return null;
        }

        $installer = (new InstallerFactory($this, $container))->createInstaller();
        $container->set(InstallerInterface::class, $installer);
        $container->set(UninstallerInterface::class, $installer);

        return $installer;
    }

    private function initTranslations()
    {
        if (null !== $this->id || null === $translator = $this->getTranslator()) {
            return;
        }

        $catalogue = $this->loadTranslations($this->getLocalPath() . 'translations/', $translator->getLocale());
        if (null !== $catalogue) {
            $translator->getCatalogue()->addCatalogue($catalogue);
        }
    }

    /**
     * @return Request
     */
    private function getCurrentRequest()
    {
        try {
            /** @var RequestStack $requestStack */
            $requestStack = $this->get('request_stack');
            $request = $requestStack->getCurrentRequest();
        } catch (ServiceNotFoundException $e) {
            $request = null;
        }

        return $request ?? $this->createRequestFromGlobals();
    }

    /**
     * @return Request
     */
    private function createRequestFromGlobals()
    {
        static $request;

        return $request ?? ($request = Request::createFromGlobals());
    }

    /**
     * Accesses the public "routing.loader" service in order to provide a @see \Symfony\Component\Config\Loader\LoaderResolverInterface
     * to the routing configuration loader used by @see \PrestaShop\PrestaShop\Adapter\Module\Tab\ModuleTabRegister
     */
    private function setUpRoutingLoaderResolver()
    {
        if (Tools::version_compare(_PS_VERSION_, '1.7.7')) {
            return;
        }

        try {
            $this->get('routing.loader');
        } catch (Exception $e) {
            // ignore silently
        }
    }

    /**
     * @return bool
     */
    private function isResetModuleAction()
    {
        $request = $this->getCurrentRequest();

        return 'admin_module_manage_action' === $request->attributes->get('_route')
            && 'reset' === $request->attributes->get('action');
    }

    /**
     * @return LoggerInterface
     */
    private function getLogger()
    {
        try {
            return $this->get('gls_poland.general_logger');
        } catch (\Exception $e) {
            return new \Psr\Log\NullLogger();
        }
    }

    /**
     * Dumping container metadata on PHP versions before 8.1 might cause the script to exceed memory limit
     * in @see \Symfony\Component\Config\Resource\ReflectionClassResource::generateSignature()
     * due to https://bugs.php.net/bug.php?id=80821 if @see \Module::$_INSTANCE is not empty.
     */
    private function clearStaticCircularReferences()
    {
        if (80100 <= PHP_VERSION_ID || Tools::version_compare(_PS_VERSION_, '1.7.8')) {
            return;
        }

        try {
            /** @var EventDispatcher $eventDispatcher */
            $eventDispatcher = $this->get('event_dispatcher');
            $eventDispatcher->addListener(KernelEvents::TERMINATE, static function () {
                self::resetStaticCache();
            }, -10000);
        } catch (Exception $e) {
            // ignore silently
        }
    }
}
