<?php
/**
 * 2018 Paysera
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author Paysera <plugins@paysera.com>
 * @copyright 2018 Paysera
 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Paysera
 */
declare(strict_types=1);

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

require_once __DIR__ . '/vendor/autoload.php';

use GuzzleHttp6\Exception\GuzzleException;
use Paysera\Delivery\Model\ShipmentRequestModel;
use Paysera\Delivery\PayseraDeliveryCarriers;
use Paysera\Delivery\PayseraDeliveryConfiguration;
use Paysera\Delivery\Service\Logger;
use PrestaShop\PrestaShop\Adapter\Entity\Address;
use PrestaShop\PrestaShop\Adapter\Entity\Customer;
use PrestaShop\ModuleLibServiceContainer\DependencyInjection\ServiceContainer;
use Paysera\Delivery\OrderHookHandlerInterface;

class PayseraDelivery extends CarrierModule
{
    /**
     * @var ServiceContainer
     */
    private $serviceContainer;
    /**
     * @var PayseraDeliveryConfiguration
     */
    private $configuration;
    /**
     * @var Logger
     */
    private $logger;

    public function __construct()
    {
        $this->name = 'payseradelivery';
        $this->tab = 'shipping_logistics';
        $this->module_key = '226ba9ed9092abc00b17b54b7fc65e20';
        $this->version = '1.5.5';
        $this->author = 'Paysera';

        $this->initializeModule();
        parent::__construct();
        $this->initializeServices();
        $this->preLaunchCheck();
    }

    public function getName(): string
    {
        return $this->name;
    }

    private function initializeModule(): void
    {
        $this->need_instance = 0;
        $this->ps_versions_compliancy = ['min' => '1.7', 'max' => _PS_VERSION_];
        $this->bootstrap = true;
        $this->displayName = $this->l('Paysera Delivery');
        $this->description = $this->l('Paysera offers delivery for your e-shops.');
        $this->confirmUninstall = $this->l('Are your sure?');
    }

    private function initializeServices(): void
    {
        $this->serviceContainer = new ServiceContainer(
            $this->name . str_replace(['.', '-', '+'], '', $this->version),
            $this->getLocalPath()
        );

        $this->configuration = $this->serviceContainer->getService('paysera.payseradelivery.paysera_delivery_configuration');
        $this->logger = $this->serviceContainer->getService('paysera.payseradelivery.service.logger');
    }

    private function preLaunchCheck(): void
    {
       $this->ensureResolvedProjectId();
    }

    private function ensureResolvedProjectId(): void
    {
        $resolvedProjectId = $this->configuration->getResolvedProjectId();

        if ($this->configuration->getProjectId() !== null
            && $this->configuration->getProjectPassword() !== null
            && $resolvedProjectId === null) {

            $resolvedProjectId = $this->serviceContainer
                ->getService('paysera.payseradelivery.service.delivery_api_client')
                ->getResolvedProjectId()
            ;

            if ($resolvedProjectId !== null) {
                $this->configuration->setResolvedProjectId($resolvedProjectId);
                return;
            }

            $this->logger->error('Cannot resolve project id');
        }
    }

    public function viewAccess(): bool
    {
        return true;
    }

    public function install(): bool
    {
        if (Shop::isFeatureActive() === true) {
            Shop::setContext(Shop::CONTEXT_ALL);
        }

        $this->configuration->install();

        return
            parent::install()
            && $this->registerHooks()
            && $this->createTables();
    }

    private function registerHooks()
    {
        return $this->registerHook('UpdateCarrier')
            && $this->registerHook('displayBackOfficeHeader')
            && $this->registerHook('OrderConfirmation')
            && $this->registerHook('DisplayBeforeCarrier')
            && $this->registerHook('header')
            && $this->registerHook('ActionOrderStatusPostUpdate')
            && $this->registerHook('payseraPaymentConfirmed')
            && $this->registerHook('actionFilterDeliveryOptionList');
    }

    private function createTables(): bool
    {
        try {
            Db::getInstance()->execute(
                sprintf(
                    'CREATE TABLE IF NOT EXISTS `%s`
                (
                    id                     INT(11) NOT NULL AUTO_INCREMENT,
                    order_id               VARCHAR(255) NOT NULL,
                    delivery_order_api_id  VARCHAR(255),
                    carrier_code           VARCHAR(255) NOT NULL,
                    carrier_type           VARCHAR(255) NOT NULL,
                    status                 VARCHAR(255) NOT NULL,
                    gateway_iso_code_2     VARCHAR(3),
                    gateway_city           VARCHAR(255),
                    gateway_terminal       VARCHAR(255),
                    PRIMARY KEY (`id`),
                    UNIQUE KEY unique_order_id (order_id)
                ) ENGINE=MyISAM DEFAULT CHARSET=utf8;',
                    ShipmentRequestModel::PREFIXED_TABLE_NAME
                )
            );
        } catch (Exception $e) {
            $this->logger->error($e->getMessage());
            return false;
        }

        return true;
    }

    private function deleteTables()
    {
        try {
            Db::getInstance()->execute(
                sprintf('DROP TABLE `%s`;', ShipmentRequestModel::PREFIXED_TABLE_NAME)
            );
        } catch (Exception $e) {
            $this->logger->error($e->getMessage());
        }
    }

    public function hookDisplayBackOfficeHeader(): void
    {
        $this->context->controller->addCss($this->_path . 'views/css/tab.css');

        if ($this->context->controller instanceof AdminCarrierWizardController) {
            $this->context->controller->addJs(
                $this->_path . 'views/js/validations.js'
            );
        }
    }

    public function uninstall(): bool
    {
        $this->configuration->uninstall();

        $this->unregisterHook('payseraPaymentConfirmed');
        $this->deleteTables();

        return parent::uninstall();
    }

    public function enable($force_all = false)
    {
        if (!parent::enable($force_all)) {
            return false;
        }

        $this->configuration->enable();

        return parent::enable($force_all);
    }

    public function disable($force_all = false)
    {
        if (!parent::disable($force_all)) {
            return false;
        }

        $this->configuration->disable();

        return parent::disable($force_all);
    }

    public function getContent(): void
    {
        Tools::redirectAdmin($this->context->link->getAdminLink('AdminPayseraDelivery'));
    }

    /**
     * @param mixed $params TODO: we do not use multiple types (https://bit.ly/psg-return-and-argument-types)
     * @param mixed $shippingCost TODO: we do not use multiple types (https://bit.ly/psg-return-and-argument-types)
     *
     * @return array<mixed, mixed>
     */
    public function getOrderShippingCost($params, $shippingCost): array
    {
        return $this->getOrderShippingCost($params, $shippingCost);
    }

    /**
     * @param mixed $params TODO: we do not use multiple types (https://bit.ly/psg-return-and-argument-types)
     *
     * @return array<mixed, mixed>
     */
    public function getOrderShippingCostExternal($params): array
    {
        return $this->getOrderShippingCostExternal($params);
    }

    /**
     * @param array<string, int|Carrier|Cookie> $params
     */
    public function hookUpdateCarrier(array $params): void
    {
        if (!($params['carrier'] instanceof Carrier)) {
            $this->logger->error('Carrier not found');
            exit;
        }

        (new PayseraDeliveryCarriers($this, $this->configuration))
            ->updateCarrierId((int) $params['carrier']->id_reference, (int) $params['carrier']->id)
        ;
    }

    public function hookHeader(): void
    {
        if ($this->context->controller->php_self === 'order') {
            $this->registerAssets();
        }
    }

    private function registerAssets()
    {
        $javascriptPaths = [
            'modules/' . $this->name . '/views/js/frontend/terminal_selection.js',
            'modules/' . $this->name . '/views/js/frontend/select2.min.js',
        ];

        $stylesheetPaths = [
            'modules/' . $this->name . '/views/css/frontend/terminal_selection.css',
            'modules/' . $this->name . '/views/css/frontend/select2.min.css',
        ];

        if (method_exists($this->context->controller, 'registerStylesheet')) {
            foreach ($stylesheetPaths as $stylesheetPath) {
                $this->context->controller->registerStylesheet(
                    sha1($stylesheetPath),
                    $stylesheetPath,
                    ['media' => 'all', 'priority' => 80]
                );
            }
        }

        if (method_exists($this->context->controller, 'registerJavascript')) {
            foreach ($javascriptPaths as $javascriptPath) {
                $this->context->controller->registerJavascript(
                    sha1($javascriptPath),
                    $javascriptPath,
                    ['position' => 'bottom', 'priority' => 150]
                );
            }
        }
    }

    /**
     * @param array<string, mixed> $params
     *
     * @return string
     */
    public function hookDisplayBeforeCarrier(array $params): string
    {
        $this->context->smarty->assign([
            'choose_terminal' => $this->l('label_terminal'),
            'text_country' => $this->l('entry_country'),
            'text_city' => $this->l('entry_city'),
            'text_parcel_locker' => $this->l('entry_terminal'),
            'error_warning' => $this->l('error_shipping_method_not_exist'),
        ]);

        return $this->context->smarty->fetch(
            sprintf(
                '%s%s/views/templates/front/terminal_selections.tpl',
                _PS_MODULE_DIR_,
                $this->name
            )
        );
    }

    /**
     * @param array<string, mixed> $params
     *
     * @throws GuzzleException
     */
    public function hookOrderConfirmation(array $parameters): void
    {
        $this->getHookHandler()->handleHookOrderConfirmation($parameters);
    }

    /**
     * @param array<string, mixed> $params
     *
     * @throws GuzzleException
     */
    public function hookActionOrderStatusPostUpdate(array $parameters): void
    {
        $this->getHookHandler()->handleHookActionOrderStatusPostUpdate($parameters);
    }

    /**
     * @param array<string, mixed> $params
     *
     * @throws GuzzleException
     */
    public function hookPayseraPaymentConfirmed($params): void
    {
        $this->getHookHandler()->handleHookPayseraPaymentConfirmed($params);
    }

    public function hookActionFilterDeliveryOptionList($params)
    {
        $deliveryOptionsList = $params['delivery_option_list'] ?? null;
        $cart = $params['cart'] ?? null;

        if ($deliveryOptionsList === null || $cart === null) {
            return;
        }

        $address = new \Address($cart->id_address_delivery);
        $country = new \Country($address->id_country);
        $payseraCarriers = \Configuration::get(PayseraDeliveryConfiguration::DELIVERY_CARRIER_LIST);

        if (in_array($country->iso_code, PayseraDeliveryConfiguration::DELIVERY_COUNTRIES)) {
            return;
        }

        try {
            $payseraCarriers = array_column(
                json_decode($payseraCarriers, true, 512,JSON_THROW_ON_ERROR),
                'carrier_id'
            );

            foreach ($deliveryOptionsList as $key => $options) {
                foreach ($options as $id => $option) {
                    if (in_array($id, $payseraCarriers)) {
                        unset($params['delivery_option_list'][$key][$id]);
                    }
                }
            }
        } catch (JsonException $e) {
            return;
        }
    }

    /**
     *
     * @return OrderHookHandlerInterface
     */
    private function getHookHandler(): OrderHookHandlerInterface
    {
        return $this->serviceContainer->getService('paysera.payseradelivery.hook_handler.order_hook_handler');
    }

    /**
     * @return Context
     */
    public function getContext(): Context
    {
        return $this->context;
    }

    /**
     * @return ServiceContainer
     */
    public function getServiceContainer(): ServiceContainer
    {
        return $this->serviceContainer;
    }
}
